text: const { PlayerState } = require('prismarine-physics')
  
  class Physics {
    constructor (bot) {
      this.bot = bot
      this.world = { getBlock: (pos) => { return bot.blockAt(pos, false) } }
    }
  
    simulateUntil (goal, controller = () => {}, ticks = 1, state = null) {
      if (!state) {
        const simulationControl = {
          forward: this.bot.controlState.forward,
          back: this.bot.controlState.back,
          left: this.bot.controlState.left,
          right: this.bot.controlState.right,
          jump: this.bot.controlState.jump,
          sprint: this.bot.controlState.sprint,
          sneak: this.bot.controlState.sneak
        }
        state = new PlayerState(this.bot, simulationControl)
      }
  
      for (let i = 0; i < ticks; i++) {
        controller(state, i)
        this.bot.physics.simulatePlayer(state, this.world)
        if (goal(state)) return state
      }
  
      return state
    }
  
    simulateUntilNextTick () {
      return this.simulateUntil(() => false, () => {}, 1)
    }
  
    simulateUntilOnGround (ticks = 5) {
      return this.simulateUntil(state => state.onGround, () => {}, ticks)
    }
  
    canStraightLine (path, sprint = false) {
      const reached = this.getReached(path)
      const state = this.simulateUntil(reached, this.getController(path[0], false, sprint), 200)
      if (reached(state)) return true
  
      if (sprint) {
        if (this.canSprintJump(path, 0)) return false
      } else {
        if (this.canWalkJump(path, 0)) return false
      }
  
      for (let i = 1; i < 7; i++) {
        if (sprint) {
          if (this.canSprintJump(path, i)) return true
        } else {
          if (this.canWalkJump(path, i)) return true
        }
      }
      return false
    }
  
    canStraightLineBetween (n1, n2) {
      const reached = (state) => {
        const delta = n2.minus(state.pos)
        const r2 = 0.15 * 0.15
        return (delta.x * delta.x + delta.z * delta.z) <= r2 && Math.abs(delta.y) < 0.001 && (state.onGround || state.isInWater)
      }
      const simulationControl = {
        forward: this.bot.controlState.forward,
        back: this.bot.controlState.back,
        left: this.bot.controlState.left,
        right: this.bot.controlState.right,
        jump: this.bot.controlState.jump,
        sprint: this.bot.controlState.sprint,
        sneak: this.bot.controlState.sneak
      }
      const state = new PlayerState(this.bot, simulationControl)
      state.pos.update(n1)
      this.simulateUntil(reached, this.getController(n2, false, true), Math.floor(5 * n1.distanceTo(n2)), state)
      return reached(state)
    }
  
    canSprintJump (path, jumpAfter = 0) {
      const reached = this.getReached(path)
      const state = this.simulateUntil(reached, this.getController(path[0], true, true, jumpAfter), 20)
      return reached(state)
    }
  
    canWalkJump (path, jumpAfter = 0) {
      const reached = this.getReached(path)
      const state = this.simulateUntil(reached, this.getController(path[0], true, false, jumpAfter), 20)
      return reached(state)
    }
  
    getReached (path) {
      return (state) => {
        const delta = path[0].minus(state.pos)
        return Math.abs(delta.x) <= 0.35 && Math.abs(delta.z) <= 0.35 && Math.abs(delta.y) < 1
      }
    }
  
    getController (nextPoint, jump, sprint, jumpAfter = 0) {
      return (state, tick) => {
        const dx = nextPoint.x - state.pos.x
        const dz = nextPoint.z - state.pos.z
        state.yaw = Math.atan2(-dx, -dz)
  
        state.control.forward = true
        state.control.jump = jump && tick >= jumpAfter
        state.control.sprint = sprint
      }
    }
  }
  
  module.exports = Physics
  