From cadc1cab3ae147092a2f043fd78c29961d19ee0c Mon Sep 17 00:00:00 2001 From: Tom Date: Fri, 25 Sep 2020 23:24:12 -0700 Subject: [PATCH] Replicate vanilla physics, make pathing movements smooth --- bot/ingame.go | 1 - bot/mcbot.go | 11 +++ bot/path/blocks.go | 17 ++++ bot/path/path.go | 13 ++- bot/phy/phy.go | 192 ++++++++++++++++++++++++++++----------------- 5 files changed, 159 insertions(+), 75 deletions(-) diff --git a/bot/ingame.go b/bot/ingame.go index 7529201..089c5a2 100644 --- a/bot/ingame.go +++ b/bot/ingame.go @@ -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), diff --git a/bot/mcbot.go b/bot/mcbot.go index 4b5b3b7..4a5fecb 100644 --- a/bot/mcbot.go +++ b/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), + ), + ) +} diff --git a/bot/path/blocks.go b/bot/path/blocks.go index acd6636..86d8e70 100644 --- a/bot/path/blocks.go +++ b/bot/path/blocks.go @@ -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) diff --git a/bot/path/path.go b/bot/path/path.go index d3bd6b8..e125590 100644 --- a/bot/path/path.go +++ b/bot/path/path.go @@ -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 } diff --git a/bot/phy/phy.go b/bot/phy/phy.go index 8382abe..d1171ff 100644 --- a/bot/phy/phy.go +++ b/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)