WIP getting jumping to work

This commit is contained in:
Tom
2020-09-21 19:11:52 -07:00
parent bb278ddd1d
commit fcdf4bda87
6 changed files with 372 additions and 163 deletions

View File

@ -4,6 +4,7 @@ import (
"sync" "sync"
"time" "time"
"github.com/Tnze/go-mc/bot/path"
"github.com/Tnze/go-mc/bot/phy" "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"
@ -24,7 +25,7 @@ type Client struct {
settings Settings settings Settings
Wd world.World //the map data Wd world.World //the map data
Inputs phy.Inputs Inputs path.Inputs
Physics phy.State Physics phy.State
lastPosTx time.Time lastPosTx time.Time
justTeleported bool justTeleported bool

View File

@ -7,7 +7,7 @@ import (
var ( var (
safeStepBlocks = make(map[world.BlockStatus]struct{}, 1024) safeStepBlocks = make(map[world.BlockStatus]struct{}, 1024)
blocks = []block.Block{ stepBlocks = []block.Block{
block.Stone, block.Stone,
block.Granite, block.Granite,
block.PolishedGranite, block.PolishedGranite,
@ -34,10 +34,35 @@ var (
block.Sandstone, block.Sandstone,
block.RedstoneOre, block.RedstoneOre,
} }
safeWalkBlocks = make(map[world.BlockStatus]struct{}, 128)
walkBlocks = []block.Block{
block.Air,
block.Grass,
block.Torch,
block.OakSign,
block.SpruceSign,
block.BirchSign,
block.AcaciaSign,
block.JungleSign,
block.DarkOakSign,
block.OakWallSign,
block.SpruceWallSign,
block.BirchWallSign,
block.AcaciaWallSign,
block.JungleWallSign,
block.DarkOakWallSign,
block.Cornflower,
}
additionalCostBlocks = map[*block.Block]int{
&block.Rail: 120,
&block.PoweredRail: 200,
}
) )
func init() { func init() {
for _, b := range blocks { for _, b := range stepBlocks {
if b.MinStateID == b.MaxStateID { if b.MinStateID == b.MaxStateID {
safeStepBlocks[world.BlockStatus(b.MinStateID)] = struct{}{} safeStepBlocks[world.BlockStatus(b.MinStateID)] = struct{}{}
} else { } else {
@ -46,4 +71,24 @@ func init() {
} }
} }
} }
for _, b := range walkBlocks {
if b.MinStateID == b.MaxStateID {
safeWalkBlocks[world.BlockStatus(b.MinStateID)] = struct{}{}
} else {
for i := b.MinStateID; i <= b.MaxStateID; i++ {
safeWalkBlocks[world.BlockStatus(i)] = struct{}{}
}
}
}
}
func SteppableBlock(bID world.BlockStatus) bool {
_, ok := safeStepBlocks[bID]
return ok
}
func AirLikeBlock(bID world.BlockStatus) bool {
_, ok := safeWalkBlocks[bID]
return ok
} }

View File

@ -1,8 +1,10 @@
package phy package path
// Inputs describes the desired movements of the player. // Inputs describes the desired movements of the player.
type Inputs struct { type Inputs struct {
Yaw, Pitch float64 Yaw, Pitch float64
ThrottleX, ThrottleZ float64 ThrottleX, ThrottleZ float64
Jump bool
} }

179
bot/path/movement.go Normal file
View File

@ -0,0 +1,179 @@
package path
// Movement represents a single type of movement in a path.
type Movement uint8
var allMovements = []Movement{TraverseNorth, TraverseSouth, TraverseEast, TraverseWest,
TraverseNorthWest, TraverseNorthEast, TraverseSouthWest, TraverseSouthEast,
DropNorth, DropSouth, DropEast, DropWest,
AscendNorth, AscendSouth, AscendEast, AscendWest,
}
// Valid movement values.
const (
Waypoint Movement = iota
TraverseNorth
TraverseSouth
TraverseEast
TraverseWest
TraverseNorthEast
TraverseNorthWest
TraverseSouthEast
TraverseSouthWest
DropNorth
DropSouth
DropEast
DropWest
AscendNorth
AscendSouth
AscendEast
AscendWest
)
func (m Movement) Possible(nav *Nav, x, y, z int, from V3) bool {
// fmt.Printf("%s.Possible(%d,%d,%d)\n", m, x, y, z)
switch m {
case Waypoint, TraverseNorth, TraverseSouth, TraverseEast, TraverseWest:
if !SteppableBlock(nav.World.GetBlockStatus(x, y, z)) {
return false
}
return AirLikeBlock(nav.World.GetBlockStatus(x, y+1, z)) && AirLikeBlock(nav.World.GetBlockStatus(x, y+2, z))
case TraverseNorthWest, TraverseNorthEast, TraverseSouthWest, TraverseSouthEast:
if !SteppableBlock(nav.World.GetBlockStatus(x, y, z)) {
return false
}
if !AirLikeBlock(nav.World.GetBlockStatus(x, y+1, z)) || !AirLikeBlock(nav.World.GetBlockStatus(x, y+2, z)) {
return false
}
if !AirLikeBlock(nav.World.GetBlockStatus(from.X, y+1, z)) || !AirLikeBlock(nav.World.GetBlockStatus(from.X, y+2, z)) {
return false
}
return AirLikeBlock(nav.World.GetBlockStatus(x, y+1, from.Z)) && AirLikeBlock(nav.World.GetBlockStatus(x, y+2, from.Z))
case DropNorth, DropSouth, DropEast, DropWest:
for amt := 0; amt < 3; amt++ {
if !AirLikeBlock(nav.World.GetBlockStatus(x, y+amt+1, z)) {
return false
}
}
return SteppableBlock(nav.World.GetBlockStatus(x, y, z))
case AscendNorth, AscendSouth, AscendEast, AscendWest:
if !AirLikeBlock(nav.World.GetBlockStatus(x, y+1, z)) || !AirLikeBlock(nav.World.GetBlockStatus(x, y+2, z)) {
return false
}
return SteppableBlock(nav.World.GetBlockStatus(x, y, z)) &&
AirLikeBlock(nav.World.GetBlockStatus(from.X, from.Y+1, from.Z)) &&
AirLikeBlock(nav.World.GetBlockStatus(from.X, from.Y+2, from.Z))
default:
panic(m)
}
}
func (m Movement) Offset() (x, y, z int) {
switch m {
case Waypoint:
return 0, 0, 0
case TraverseNorth:
return 0, 0, -1
case TraverseSouth:
return 0, 0, 1
case TraverseEast:
return 1, 0, 0
case TraverseWest:
return -1, 0, 0
case DropNorth:
return 0, -1, -1
case DropSouth:
return 0, -1, 1
case DropEast:
return 1, -1, 0
case DropWest:
return -1, -1, 0
case AscendNorth:
return 0, 1, -1
case AscendSouth:
return 0, 1, 1
case AscendEast:
return 1, 1, 0
case AscendWest:
return -1, 1, 0
case TraverseNorthWest:
return -1, 0, -1
case TraverseNorthEast:
return 1, 0, -1
case TraverseSouthWest:
return -1, 0, 1
case TraverseSouthEast:
return 1, 0, 1
default:
panic(m)
}
}
func (m Movement) BaseCost() float64 {
switch m {
case Waypoint:
return 0
case TraverseNorth, TraverseSouth, TraverseEast, TraverseWest:
return 1
case TraverseNorthWest, TraverseNorthEast, TraverseSouthWest, TraverseSouthEast:
return 1.5
case DropNorth, DropSouth, DropEast, DropWest:
return 2
case AscendNorth, AscendSouth, AscendEast, AscendWest:
return 2.25
default:
panic(m)
}
}
func (m Movement) String() string {
switch m {
case Waypoint:
return "waypoint"
case TraverseNorth:
return "traverse-north"
case TraverseSouth:
return "traverse-south"
case TraverseEast:
return "traverse-east"
case TraverseWest:
return "traverse-west"
case DropNorth:
return "drop-north"
case DropSouth:
return "drop-south"
case DropEast:
return "drop-east"
case DropWest:
return "drop-west"
case AscendNorth:
return "jump-north"
case AscendSouth:
return "jump-south"
case AscendEast:
return "jump-east"
case AscendWest:
return "jump-west"
case TraverseNorthWest:
return "traverse-northwest"
case TraverseNorthEast:
return "traverse-northeast"
case TraverseSouthWest:
return "traverse-southwest"
case TraverseSouthEast:
return "traverse-southeast"
default:
panic(m)
}
}

View File

@ -2,8 +2,9 @@
package path package path
import ( import (
"math"
"github.com/Tnze/go-mc/bot/world" "github.com/Tnze/go-mc/bot/world"
"github.com/Tnze/go-mc/data/block"
"github.com/beefsack/go-astar" "github.com/beefsack/go-astar"
) )
@ -36,27 +37,14 @@ func (n *Nav) Path() (path []astar.Pather, distance float64, found bool) {
}) })
} }
// Movement represents a single type of movement in a path.
type Movement uint8
var allMovements = []Movement{TraverseNorth, TraverseSouth, TraverseEast, TraverseWest}
// Valid movement values.
const (
Waypoint Movement = iota
TraverseNorth
TraverseSouth
TraverseEast
TraverseWest
)
// Tile represents a point in a path. All tiles in a path are adjaceent their // Tile represents a point in a path. All tiles in a path are adjaceent their
// preceeding tiles. // preceeding tiles.
type Tile struct { type Tile struct {
Nav *Nav Nav *Nav
Movement Movement Movement Movement
Pos V3 Pos V3
ExtraCost int
} }
func (t Tile) PathNeighborCost(to astar.Pather) float64 { func (t Tile) PathNeighborCost(to astar.Pather) float64 {
@ -67,12 +55,17 @@ func (t Tile) PathNeighborCost(to astar.Pather) float64 {
func (t Tile) PathEstimatedCost(to astar.Pather) float64 { func (t Tile) PathEstimatedCost(to astar.Pather) float64 {
other := to.(Tile) other := to.(Tile)
cost := t.Pos.Cost(other.Pos) cost := t.Pos.Cost(other.Pos)
return cost + other.Movement.BaseCost() return cost + other.Movement.BaseCost()
} }
func (t Tile) PathNeighbors() []astar.Pather { func (t Tile) PathNeighbors() []astar.Pather {
possibles := make([]astar.Pather, 0, 8) possibles := make([]astar.Pather, 0, 8)
if t.PathEstimatedCost(Tile{Pos: t.Nav.Start}) > 1200 {
return nil
}
if t.Pos == t.Nav.Dest && t.Movement != Waypoint { if t.Pos == t.Nav.Dest && t.Movement != Waypoint {
dupe := t dupe := t
dupe.Movement = Waypoint dupe.Movement = Waypoint
@ -82,7 +75,7 @@ func (t Tile) PathNeighbors() []astar.Pather {
for _, m := range allMovements { for _, m := range allMovements {
x, y, z := m.Offset() x, y, z := m.Offset()
pos := V3{X: t.Pos.X + x, Y: t.Pos.Y + y, Z: t.Pos.Z + z} pos := V3{X: t.Pos.X + x, Y: t.Pos.Y + y, Z: t.Pos.Z + z}
if m.Possible(t.Nav, pos.X, pos.Y, pos.Z) { if m.Possible(t.Nav, pos.X, pos.Y, pos.Z, t.Pos) {
possibles = append(possibles, Tile{ possibles = append(possibles, Tile{
Nav: t.Nav, Nav: t.Nav,
Movement: m, Movement: m,
@ -95,63 +88,18 @@ func (t Tile) PathNeighbors() []astar.Pather {
return possibles return possibles
} }
func (m Movement) Possible(nav *Nav, x, y, z int) bool { func (t Tile) Inputs(dX, dY, dZ float64) Inputs {
// fmt.Printf("%s.Possible(%d,%d,%d)\n", m, x, y, z) // Sufficient for simple movements.
switch m { at := math.Atan2(-dX, -dZ)
case Waypoint, TraverseNorth, TraverseSouth, TraverseEast, TraverseWest: out := Inputs{
b := nav.World.GetBlockStatus(x, y, z) ThrottleX: math.Sin(at),
if _, safe := safeStepBlocks[b]; !safe { ThrottleZ: math.Cos(at),
return false
}
above1 := uint32(nav.World.GetBlockStatus(x, y+1, z))
above2 := uint32(nav.World.GetBlockStatus(x, y+2, z))
return above1 == block.Air.MinStateID && above2 == block.Air.MinStateID
default:
panic(m)
} }
}
func (m Movement) Offset() (x, y, z int) { switch t.Movement {
switch m { case AscendNorth, AscendSouth, AscendEast, AscendWest:
case Waypoint: out.Jump = math.Sqrt(dX*dX+dZ*dZ) < 1.75
return 0, 0, 0 out.Yaw = 0
case TraverseNorth:
return 0, 0, -1
case TraverseSouth:
return 0, 0, 1
case TraverseEast:
return 1, 0, 0
case TraverseWest:
return -1, 0, 0
default:
panic(m)
}
}
func (m Movement) BaseCost() float64 {
switch m {
case Waypoint:
return 0
case TraverseNorth, TraverseSouth, TraverseEast, TraverseWest:
return 1
default:
panic(m)
}
}
func (m Movement) String() string {
switch m {
case Waypoint:
return "waypoint"
case TraverseNorth:
return "traverse-north"
case TraverseSouth:
return "traverse-south"
case TraverseEast:
return "traverse-east"
case TraverseWest:
return "traverse-west"
default:
panic(m)
} }
return out
} }

View File

@ -5,6 +5,7 @@ package phy
import ( import (
"math" "math"
"github.com/Tnze/go-mc/bot/path"
"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/bot/world/entity/player"
) )
@ -17,6 +18,9 @@ const (
maxYawChange = 33 maxYawChange = 33
maxPitchChange = 11 maxPitchChange = 11
stepHeight = 0.6
minJumpTicks = 14
gravity = 0.08 gravity = 0.08
drag = 0.98 drag = 0.98
acceleration = 0.02 acceleration = 0.02
@ -43,6 +47,7 @@ type State struct {
Pos Point Pos Point
Vel Point Vel Point
Yaw, Pitch float64 Yaw, Pitch float64
lastJump uint32
// player state flags. // player state flags.
onGround bool onGround bool
@ -51,7 +56,8 @@ type State struct {
horizontal bool horizontal bool
} }
Run bool tick uint32
Run bool
} }
func (s *State) ServerPositionUpdate(player player.Pos, w World) error { func (s *State) ServerPositionUpdate(player player.Pos, w World) error {
@ -79,7 +85,7 @@ func (s *State) surroundings(query AABB, w World) Surrounds {
for y := minY; y < maxY; y++ { for y := minY; y < maxY; y++ {
for z := minZ; z < maxZ; z++ { for z := minZ; z < maxZ; z++ {
for x := minX; x < maxX; x++ { for x := minX; x < maxX; x++ {
if block := w.GetBlockStatus(x, y, z); block > 0 { if block := w.GetBlockStatus(x, y, z); !path.AirLikeBlock(block) {
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))) 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)))
} }
} }
@ -88,89 +94,6 @@ func (s *State) surroundings(query AABB, w World) Surrounds {
return out return out
} }
func (s *State) applyLookInputs(input 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 Inputs, acceleration, inertia float64) {
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) Tick(input Inputs, w World) error {
if !s.Run {
return nil
}
var inertia = inertia
var acceleration = acceleration
if s.onGround {
inertia *= slipperiness
acceleration = 0.1 * (0.1627714 / (inertia * inertia * inertia))
}
s.applyLookInputs(input)
s.applyPosInputs(input, acceleration, inertia)
// Deadzone velocities when they get too low.
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
}
// Gravity
s.Vel.Y -= gravity
// Drag & friction.
s.Vel.Y *= drag
s.Vel.X *= inertia
s.Vel.Z *= inertia
// 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 { func (s *State) BB() AABB {
return AABB{ return AABB{
X: MinMax{Min: -playerWidth / 2, Max: playerWidth / 2}, X: MinMax{Min: -playerWidth / 2, Max: playerWidth / 2},
@ -186,3 +109,114 @@ func (s *State) Position() player.Pos {
OnGround: s.onGround, OnGround: s.onGround,
} }
} }
func (s *State) Tick(input path.Inputs, w World) error {
s.tick++
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, stepHeight, 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
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) {
inertia *= slipperiness
acceleration = 0.1 * (0.1627714 / (inertia * inertia * inertia))
}
// Deadzone velocities when they get too low.
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.applyLookInputs(input)
s.applyPosInputs(input, acceleration, inertia)
// 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 Point) {
surroundings := s.surroundings(query, w)
outVel = s.Vel
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)
return bb, outVel
}