LookAtMySuitBot/js/node_modules/mineflayer-pathfinder/test/internalTest.js

1225 lines
50 KiB
JavaScript
Raw Normal View History

2023-12-24 20:08:39 -05:00
/* eslint-env mocha */
const mineflayer = require('mineflayer')
const { goals, pathfinder, Movements } = require('mineflayer-pathfinder')
const { Vec3 } = require('vec3')
const mc = require('minecraft-protocol')
const assert = require('assert')
const { v4: uuidv4 } = require('uuid')
const PEntity = require('prismarine-entity')
const { once, on } = require('events')
const { Schematic } = require('prismarine-schematic')
const { promises: fs } = require('fs')
const path = require('path')
const Physics = require('../lib/physics')
const Version = '1.16.5'
const ServerPort = 25567
/**
* Returns a flat bedrock chunk with a single gold block in it.
* @param {string} Version version
* @returns {import('prismarine-chunk').Chunk}
*/
function flatMap (Version) {
const targetBlock = new Vec3(12, 1, 8) // a gold block away from the spawn position
const Block = require('prismarine-block')(Version)
const Chunk = require('prismarine-chunk')(Version)
const mcData = require('minecraft-data')(Version)
const chunk = new Chunk()
chunk.initialize((x, y, z) => {
if (targetBlock.x === x && targetBlock.y === y && targetBlock.z === z) {
return new Block(mcData.blocksByName.gold_block.id, 1, 0)
}
return y === 0 ? new Block(mcData.blocksByName.bedrock.id, 1, 0) : new Block(mcData.blocksByName.air.id, 1, 0) // Bedrock floor
})
return chunk
}
/**
* Reads the schematic parkour1.schem and returns a chunk containing the schematic content.
* @param {string} Version version to be used
* @returns {Promise<import('prismarine-chunk').Chunk>}
*/
async function parkourMap (Version) {
const pwd = path.join(__dirname, './schematics/parkour1.schem')
const readSchem = await Schematic.read(await fs.readFile(pwd), '1.18.2')
const Block = require('prismarine-block')(Version)
const Chunk = require('prismarine-chunk')(Version)
const mcData = require('minecraft-data')(Version)
const chunk = new Chunk()
chunk.initialize((x, y, z) => {
const block = readSchem.getBlock(new Vec3(x, y, z))
if (block.name === 'air') return null
// Different versions off schematic are not compatible with each other. Assumes block names between versions stay the same.
const blockVersion = mcData.blocksByName[block.name]
if (!blockVersion) return null
return new Block(blockVersion.id, 1, 0)
})
return chunk
}
function generateChunkPacket (chunk) {
const lights = chunk.dumpLight()
return {
x: 0,
z: 0,
groundUp: true,
biomes: chunk.dumpBiomes !== undefined ? chunk.dumpBiomes() : undefined,
heightmaps: {
type: 'compound',
name: '',
value: {
MOTION_BLOCKING: { type: 'longArray', value: new Array(36).fill([0, 0]) }
}
}, // send fake heightmap
bitMap: chunk.getMask(),
chunkData: chunk.dump(),
blockEntities: [],
trustEdges: false,
skyLightMask: lights?.skyLightMask,
blockLightMask: lights?.blockLightMask,
emptySkyLightMask: lights?.emptySkyLightMask,
emptyBlockLightMask: lights?.emptyBlockLightMask,
skyLight: lights?.skyLight,
blockLight: lights?.blockLight
}
}
/**
* Create a new 1.16 server and handle when clients connect.
* @param {import('minecraft-protocol').Server} server
* @param {import('vec3').Vec3} spawnPos
* @param {string} Version
* @param {boolean} useLoginPacket
* @returns {Promise<void>}
*/
async function newServer (server, chunk, spawnPos, Version, useLoginPacket) {
const mcData = require('minecraft-data')(Version)
server = mc.createServer({
'online-mode': false,
version: Version,
// 25565 - local server, 25566 - proxy server
port: ServerPort
})
server.on('login', (client) => {
let loginPacket
if (useLoginPacket) {
loginPacket = mcData.loginPacket
} else {
loginPacket = {
entityId: 0,
levelType: 'fogetaboutit',
gameMode: 0,
previousGameMode: 255,
worldNames: ['minecraft:overworld'],
dimension: 0,
worldName: 'minecraft:overworld',
hashedSeed: [0, 0],
difficulty: 0,
maxPlayers: 20,
reducedDebugInfo: 1,
enableRespawnScreen: true
}
}
client.write('login', loginPacket)
client.write('map_chunk', generateChunkPacket(chunk))
client.write('position', {
x: spawnPos.x,
y: spawnPos.y,
z: spawnPos.z,
yaw: 0,
pitch: 0,
flags: 0x00
})
})
await once(server, 'listening')
return server
}
function add1x2Weight (entityIntersections, posX, posY, posZ, weight = 1) {
entityIntersections[`${posX},${posY},${posZ}`] = entityIntersections[`${posX},${posY},${posZ}`] ?? 0
entityIntersections[`${posX},${posY + 1},${posZ}`] = entityIntersections[`${posX},${posY + 1},${posZ}`] ?? 0
entityIntersections[`${posX},${posY},${posZ}`] += weight
entityIntersections[`${posX},${posY + 1},${posZ}`] += weight
}
describe('pathfinder Goals', function () {
const mcData = require('minecraft-data')(Version)
const targetBlock = new Vec3(12, 1, 8) // a gold block away from the spawn position
const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block
/** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
let bot
/** @type { import('minecraft-protocol').Server } */
let server
before(async () => {
const chunk = flatMap(Version)
server = await newServer(server, chunk, spawnPos, Version, true)
bot = mineflayer.createBot({
username: 'player',
version: Version,
port: ServerPort
})
await once(bot, 'chunkColumnLoad')
})
after(() => {
bot.end()
bot = null
server.close()
})
describe('Goals', () => {
beforeEach(() => {
bot.entity.position = spawnPos.clone()
})
it('GoalBlock', () => {
const goal = new goals.GoalBlock(targetBlock.x, targetBlock.y, targetBlock.z)
assert.ok(!goal.isEnd(bot.entity.position))
bot.entity.position = targetBlock.clone()
assert.ok(goal.isEnd(bot.entity.position))
})
it('GoalNear', () => {
const goal = new goals.GoalNear(targetBlock.x, targetBlock.y, targetBlock.z, 1)
assert.ok(!goal.isEnd(bot.entity.position))
bot.entity.position = targetBlock.offset(1, 0, 0)
assert.ok(goal.isEnd(bot.entity.position))
})
it('GoalXZ', () => {
const goal = new goals.GoalXZ(targetBlock.x, targetBlock.z)
assert.ok(!goal.isEnd(bot.entity.position))
bot.entity.position = targetBlock.offset(0, 1, 0)
assert.ok(goal.isEnd(bot.entity.position))
})
it('GoalNearXZ', () => {
const goal = new goals.GoalNearXZ(targetBlock.x, targetBlock.z, 1)
assert.ok(!goal.isEnd(bot.entity.position))
bot.entity.position = targetBlock.offset(1, 0, 0)
assert.ok(goal.isEnd(bot.entity.position))
})
it('GoalY', () => {
const goal = new goals.GoalY(targetBlock.y + 1)
assert.ok(!goal.isEnd(bot.entity.position))
bot.entity.position = targetBlock.offset(0, 1, 0)
assert.ok(goal.isEnd(bot.entity.position))
})
it('GoalGetToBlock', () => {
const goal = new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z)
assert.ok(!goal.isEnd(bot.entity.position))
bot.entity.position = targetBlock.offset(1, 0, 0)
assert.ok(goal.isEnd(bot.entity.position))
})
it('GoalCompositeAny', () => {
const targetBlock2 = new Vec3(10, 1, 0)
const goal1 = new goals.GoalBlock(targetBlock.x, targetBlock.y, targetBlock.z)
const goal2 = new goals.GoalBlock(targetBlock2.x, targetBlock2.y, targetBlock2.z)
const goalComposite = new goals.GoalCompositeAny()
goalComposite.goals = [goal1, goal2]
assert.ok(!goalComposite.isEnd(bot.entity.position))
bot.entity.position = targetBlock.clone()
assert.ok(goalComposite.isEnd(bot.entity.position)) // target block 1
bot.entity.position = targetBlock2.clone()
assert.ok(goalComposite.isEnd(bot.entity.position)) // target block 2
})
it('GoalCompositeAll', () => {
const targetBlock = new Vec3(2, 1, 0)
const block2 = new Vec3(3, 1, 0)
const goal1 = new goals.GoalBlock(targetBlock.x, targetBlock.y, targetBlock.z)
const goal2 = new goals.GoalNear(block2.x, block2.y, block2.z, 2)
const goalComposite = new goals.GoalCompositeAll()
goalComposite.goals = [goal1, goal2]
assert.ok(!goalComposite.isEnd(bot.entity.position))
bot.entity.position = targetBlock.offset(0, 0, 0)
assert.ok(goalComposite.isEnd(bot.entity.position))
})
it('GoalInvert', () => {
const goalBlock = new goals.GoalBlock(targetBlock.x, targetBlock.y, targetBlock.z)
const goal = new goals.GoalInvert(goalBlock)
bot.entity.position = targetBlock.clone()
assert.ok(!goal.isEnd(bot.entity.position))
bot.entity.position = new Vec3(0, 1, 0)
assert.ok(goal.isEnd(bot.entity.position))
})
it('GoalPlaceBlock', () => {
const placeTarget = targetBlock.offset(0, 1, 0)
const goal = new goals.GoalPlaceBlock(placeTarget, bot.world, {})
bot.entity.position = targetBlock.offset(-5, 0, 0) // to far away to reach
assert.ok(!goal.isEnd(bot.entity.position.floored()))
bot.entity.position = targetBlock.offset(-2, 0, 0)
assert.ok(goal.isEnd(bot.entity.position.floored()))
})
it('GoalLookAtBlock', () => {
const breakTarget = targetBlock.clone() // should be a gold block or any other block thats dig able
const goal = new goals.GoalLookAtBlock(breakTarget, bot.world, { reach: 3 })
assert.ok(!goal.isEnd(bot.entity.position.floored()))
bot.entity.position = targetBlock.offset(-2, 0, 0) // should now be close enough
assert.ok(goal.isEnd(bot.entity.position.floored()))
})
})
describe('Goals with entity', () => {
beforeEach(() => {
bot.entity.position = spawnPos.clone()
})
before((done) => {
const Entity = PEntity(Version)
const chicken = new Entity(mcData.entitiesByName.chicken.id)
const client = Object.values(server.clients)[0]
client.write('spawn_entity', { // Might only work for 1.16
entityId: chicken.id,
objectUUID: uuidv4(),
type: chicken.type,
x: targetBlock.x,
y: targetBlock.y + 1,
z: targetBlock.z,
pitch: 0,
yaw: 0,
objectData: 0,
velocityX: 0,
velocityY: 0,
velocityZ: 0
})
setTimeout(done, 100)
})
it('GoalFollow', () => {
const entity = bot.nearestEntity()
const goal = new goals.GoalFollow(entity, 1)
assert.ok(!goal.isEnd(bot.entity.position))
bot.entity.position = targetBlock.clone()
assert.ok(goal.isEnd(bot.entity.position))
})
})
})
describe('pathfinder events', function () {
const mcData = require('minecraft-data')(Version)
const targetBlock = new Vec3(12, 1, 8) // a gold block away from the spawn position
const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block
/** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
let bot
/** @type { import('minecraft-protocol').Server } */
let server
before(async () => {
const chunk = flatMap(Version)
server = await newServer(server, chunk, spawnPos, Version, true)
bot = mineflayer.createBot({
username: 'player',
version: Version,
port: ServerPort
})
await once(bot, 'chunkColumnLoad')
bot.loadPlugin(pathfinder)
bot.pathfinder.setMovements(new Movements(bot, mcData))
})
after(() => server.close())
describe('events', async function () {
beforeEach(() => {
bot.entity.position = spawnPos.clone()
})
afterEach((done) => {
bot.pathfinder.setGoal(null)
setTimeout(done)
const listeners = ['goal_reached', 'goal_updated', 'path_update', 'path_stop']
listeners.forEach(l => bot.removeAllListeners(l))
})
it('goal_reached', function (done) {
this.timeout(3000)
this.slow(1000)
bot.once('goal_reached', () => done())
bot.pathfinder.setGoal(new goals.GoalNear(targetBlock.x, targetBlock.y, targetBlock.z, 1))
})
it('goal_updated', function (done) {
this.timeout(100)
bot.once('goal_updated', () => done())
bot.pathfinder.setGoal(new goals.GoalNear(targetBlock.x, targetBlock.y, targetBlock.z, 1))
})
it('path_update', function (done) {
this.timeout(3000)
this.slow(1000)
bot.pathfinder.setGoal(new goals.GoalNear(targetBlock.x, targetBlock.y, targetBlock.z, 1))
bot.once('path_update', () => done())
})
it('path_stop', function (done) {
this.timeout(3000)
this.slow(1000)
bot.pathfinder.setGoal(new goals.GoalNear(targetBlock.x, targetBlock.y, targetBlock.z, 1))
bot.once('path_stop', () => done())
bot.pathfinder.stop()
})
})
})
describe('pathfinder util functions', function () {
const mcData = require('minecraft-data')(Version)
const Item = require('prismarine-item')(Version)
const targetBlock = new Vec3(12, 1, 8) // a gold block away from the spawn position
const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block
const itemsToGive = [new Item(mcData.itemsByName.diamond_pickaxe.id, 1), new Item(mcData.itemsByName.dirt.id, 64)]
/** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
let bot
/** @type { import('minecraft-protocol').Server } */
let server
before(async () => {
const chunk = flatMap(Version)
server = await newServer(server, chunk, spawnPos, Version, true)
bot = mineflayer.createBot({
username: 'player',
version: Version,
port: ServerPort
})
await once(bot, 'chunkColumnLoad')
itemsToGive.forEach(item => {
const slot = bot.inventory.firstEmptyHotbarSlot()
bot.inventory.slots[slot] = item
})
bot.loadPlugin(pathfinder)
bot.pathfinder.setMovements(new Movements(bot, mcData))
})
after(() => server.close())
describe('paththing', function () {
this.afterEach((done) => {
bot.pathfinder.setGoal(null)
bot.entity.position = spawnPos.clone()
bot.stopDigging()
setTimeout(() => done())
})
it('Goto', async function () {
this.timeout(3000)
this.slow(1500)
await bot.pathfinder.goto(new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z))
})
it('isMoving', function (done) {
bot.pathfinder.setGoal(new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z))
const foo = () => {
if (bot.pathfinder.isMoving()) {
bot.removeListener('physicTick', foo)
done()
}
}
bot.on('physicTick', foo)
})
// Note: Ordering seams to matter when running the isBuilding test. If run after isMining isBuilding does not seam to work.
it('isBuilding', function (done) {
this.timeout(5000)
this.slow(1500)
bot.pathfinder.setGoal(new goals.GoalBlock(targetBlock.x, targetBlock.y + 2, targetBlock.z))
const foo = () => {
if (bot.pathfinder.isBuilding()) {
bot.removeListener('physicTick', foo)
bot.stopDigging()
done()
}
}
bot.on('physicTick', foo)
})
it('isMining', function (done) {
this.timeout(5000)
this.slow(1500)
bot.pathfinder.setGoal(new goals.GoalBlock(targetBlock.x, targetBlock.y, targetBlock.z))
const foo = () => {
if (bot.pathfinder.isMining()) {
bot.removeListener('physicTick', foo)
bot.stopDigging()
done()
}
}
bot.on('physicTick', foo)
})
})
it('bestHarvestTool', function () {
const block = bot.blockAt(targetBlock)
const tool = bot.pathfinder.bestHarvestTool(block)
assert.deepStrictEqual(tool, itemsToGive[0])
})
it('getPathTo', function () {
const path = bot.pathfinder.getPathTo(bot.pathfinder.movements, new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(path.status, 'success')
assert.ok(path.visitedNodes < 5, `Generated path visited nodes to high (${path.visitedNodes} < 5)`)
assert.ok(path.generatedNodes < 30, `Generated path nodes to high (${path.generatedNodes} < 30)`)
assert.ok(path.path.length === 3, `Generated path length wrong (${path.path.length} === 3)`)
assert.ok(path.time < 50, `Generated path took too long (${path.time} < 50)`)
})
})
describe('pathfinder Movement', function () {
const mcData = require('minecraft-data')(Version)
const Item = require('prismarine-item')(Version)
const Block = require('prismarine-block')(Version)
const targetBlock = new Vec3(12, 1, 8) // a gold block away from the spawn position
const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block
/** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
let bot
/** @type { import('minecraft-protocol').Server } */
let server
/** @type { import('mineflayer-pathfinder').Movements } */
let defaultMovement
const itemsToGive = [new Item(mcData.itemsByName.diamond_pickaxe.id, 1), new Item(mcData.itemsByName.dirt.id, 64)]
before(async () => {
const chunk = flatMap(Version)
server = await newServer(server, chunk, spawnPos, Version, true)
bot = mineflayer.createBot({
username: 'player',
version: Version,
port: ServerPort
})
await once(bot, 'chunkColumnLoad')
itemsToGive.forEach(item => {
const slot = bot.inventory.firstEmptyHotbarSlot()
bot.inventory.slots[slot] = item
})
defaultMovement = new Movements(bot, mcData)
bot.loadPlugin(pathfinder)
bot.pathfinder.setMovements(defaultMovement)
})
after(() => server.close())
it('countScaffoldingItems', function () {
assert.strictEqual(defaultMovement.countScaffoldingItems(), 64)
})
it('getScaffoldingItem', function () {
assert.strictEqual(defaultMovement.getScaffoldingItem(), itemsToGive[1])
})
it('getBlock', function () {
assert.ok(defaultMovement.getBlock(targetBlock, 0, 0, 0).type === mcData.blocksByName.gold_block.id)
})
describe('safeToBreak world editing', function () {
this.afterAll(async () => {
defaultMovement.canDig = true
await bot.world.setBlock(targetBlock.offset(1, 0, 0), new Block(mcData.blocksByName.air.id, 0))
})
it('safeToBreak', async function () {
const block = bot.blockAt(targetBlock)
assert.ok(defaultMovement.safeToBreak(block))
defaultMovement.canDig = false
assert.ok(!defaultMovement.safeToBreak(block))
defaultMovement.canDig = true
await bot.world.setBlock(targetBlock.offset(1, 0, 0), new Block(mcData.blocksByName.water.id, 0, 0))
assert.ok(!defaultMovement.safeToBreak(block))
})
})
it('safeOrBreak', function () {
const block = defaultMovement.getBlock(targetBlock, 0, 0, 0)
const toBreak = []
const extraValue = defaultMovement.safeOrBreak(block, toBreak)
assert.ok(extraValue < 100, `safeOrBreak to high for block (${extraValue} < 100)`)
assert.ok(toBreak.length === 1, `safeOrBreak toBreak array wrong length ${toBreak.length} (${toBreak.length} === 1)`)
})
it('getMoveJumpUp', function () {
const block = defaultMovement.getBlock(targetBlock, -1, 0, 0)
const dir = new Vec3(1, 0, 0)
const neighbors = []
defaultMovement.getMoveJumpUp(block.position, dir, neighbors)
assert.ok(neighbors.length === 1, `getMoveJumpUp neighbors not right length (${neighbors.length} === 1)`)
})
it('getMoveForward', function () {
const dir = new Vec3(1, 0, 0)
const neighbors = []
defaultMovement.getMoveForward(targetBlock, dir, neighbors)
assert.ok(neighbors.length === 1, `getMoveForward neighbors not right length (${neighbors.length} === 1)`)
})
it('getMoveDiagonal', function () {
const dir = new Vec3(1, 0, 0)
const neighbors = []
defaultMovement.getMoveDiagonal(targetBlock, dir, neighbors)
assert.ok(neighbors.length === 1, `getMoveDiagonal neighbors not right length (${neighbors.length} === 1)`)
})
it('getLandingBlock', function () {
const node = targetBlock.offset(-1, 3, 0)
const dir = new Vec3(1, 0, 0)
const block = defaultMovement.getLandingBlock(node, dir)
assert.ok(block != null, 'Landing block is null')
if (!block) return
assert.ok(block.type === mcData.blocksByName.air.id, `getLandingBlock not the right block (${block.name} === air)`)
assert.ok(block.position.offset(0, -1, 0).distanceSquared(targetBlock) === 0, `getLandingBlock not landing (${block.position.offset(0, -1, 0).distanceSquared(targetBlock)}) on target block: ${defaultMovement.getBlock(block.position, 0, -1, 0).name}`)
})
it('getMoveDropDown', function () {
const dir = new Vec3(1, 0, 0)
const neighbors = []
defaultMovement.getMoveDropDown(targetBlock.offset(-1, 4, 0), dir, neighbors)
assert.ok(neighbors.length === 1, `getMoveDropDown neighbors not right length (${neighbors.length} === 1)`)
})
it('getMoveDown', function () {
const neighbors = []
defaultMovement.getMoveDown(targetBlock.offset(0, 4, 0), neighbors)
assert.ok(neighbors.length === 1, `getMoveDown neighbors not right length (${neighbors.length} === 1)`)
})
it('getMoveUp', function () {
const neighbors = []
defaultMovement.getMoveUp(targetBlock.offset(0, 1, 0), neighbors)
assert.ok(neighbors.length === 1, `getMoveUp neighbors not right length (${neighbors.length} === 1)`)
})
it('getNeighbors', function () {
const neighbors = defaultMovement.getNeighbors(targetBlock.offset(0, 1, 0))
assert.ok(neighbors.length > 0, 'getNeighbors length 0')
})
})
describe('Parkour path test', function () {
const mcData = require('minecraft-data')(Version)
const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block
/** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
let bot
/** @type { import('minecraft-protocol').Server } */
let server
/** @type { import('mineflayer-pathfinder').Movements } */
let defaultMovement
const parkourSpawn1 = new Vec3(0.5, 3, 12.5)
const parkourSpawn2 = new Vec3(5.5, 3, 12.5)
before(async () => {
this.timeout(5000)
const chunk = await parkourMap(Version)
server = await newServer(server, chunk, spawnPos, Version, true)
bot = mineflayer.createBot({
username: 'player',
version: Version,
port: ServerPort
})
await once(bot, 'chunkColumnLoad')
defaultMovement = new Movements(bot, mcData)
bot.loadPlugin(pathfinder)
bot.pathfinder.setMovements(defaultMovement)
})
after(() => server.close())
it('getMoveParkourForward-1', function () {
const dirs = [new Vec3(0, 0, 1), new Vec3(0, 0, -1)]
for (let i = 0; i < dirs.length; i++) {
const dir = dirs[i] // only 2 dirs as the schematic parkour1.schem only has 2 other blocks to path to.
const neighbors = []
defaultMovement.getMoveParkourForward(parkourSpawn1, dir, neighbors)
assert.ok(neighbors.length === 1, `getMoveParkourForward jump off gold block neighbors not right length (${neighbors.length} === 1)`)
}
})
it('getMoveParkourForward-2', function () {
const dirs = [new Vec3(1, 0, 0), new Vec3(0, 0, 1), new Vec3(-1, 0, 0), new Vec3(0, 0, -1)]
for (let i = 0; i < dirs.length; i++) {
const dir = dirs[i]
const neighbors = []
defaultMovement.getMoveParkourForward(parkourSpawn2, dir, neighbors)
assert.ok(neighbors.length === 1, `getMoveParkourForward jump off gold block neighbors not right length (${neighbors.length} === 1)`)
}
})
})
describe('Physics test', function () {
const mcData = require('minecraft-data')(Version)
const spawnPos = new Vec3(8.5, 1, 8.5) // Center of the chunk & center of the block
/** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
let bot
/** @type { import('minecraft-protocol').Server } */
let server
/** @type { import('mineflayer-pathfinder').Movements } */
let defaultMovement
const parkourSpawn1 = new Vec3(0.5, 3, 12.5)
// const parkourSpawn2 = new Vec3(5.5, 3, 12.5)
before(async () => {
this.timeout(5000)
const chunk = await parkourMap(Version)
server = await newServer(server, chunk, spawnPos, Version, true)
bot = mineflayer.createBot({
username: 'player',
version: Version,
port: ServerPort
})
await once(bot, 'chunkColumnLoad')
defaultMovement = new Movements(bot, mcData)
bot.loadPlugin(pathfinder)
bot.pathfinder.setMovements(defaultMovement)
})
after(() => server.close())
it('simulateUntil', async function () {
this.slow(1000)
this.timeout(2000)
const ticksToSimulate = 10
const ticksPressForward = 5
bot.entity.position = parkourSpawn1.clone()
bot.entity.velocity = new Vec3(0, 0, 0)
// Wait for the bot to be on the ground so bot.entity.onGround == true
bot.clearControlStates()
await once(bot, 'physicTick')
await once(bot, 'physicTick')
const physics = new Physics(bot)
const simulatedSteps = []
const realSteps = []
const controller = (state, counter) => {
state.control.forward = counter <= ticksPressForward
state.control.jump = counter <= ticksPressForward
simulatedSteps.push(state.pos.toString() + ' Input:' + String(counter <= ticksPressForward))
}
const state = physics.simulateUntil(() => false, controller, ticksToSimulate)
simulatedSteps.push(state.pos.toString() + ' Input:false')
// We have to be carful to not mess up the event scheduling. for await on(bot, 'physicTick') seams to work.
// A for loop with just await once(bot, 'physicTick') does not always seam to work. What also works is attaching
// a listener to bot with bot.on('physicTick', listener) but this is a lot nicer.
let tick = 0
for await (const _ of on(bot, 'physicTick')) { // eslint-disable-line no-unused-vars
bot.setControlState('forward', tick <= ticksPressForward)
bot.setControlState('jump', tick <= ticksPressForward)
realSteps.push(bot.entity.position.toString() + ' Input:' + String(tick <= ticksPressForward))
tick++
if (tick > ticksToSimulate) break
}
bot.clearControlStates()
// console.info(bot.entity.position.toString(), console.info(state.pos.toString()))
assert.ok(bot.entity.position.distanceSquared(state.pos) < 0.01,
`Simulated states don't match Bot: ${bot.entity.position.toString()} !== Simulation: ${state.pos.toString()}`
// + '\nSimulated Steps:\n'
// + simulatedSteps.join('\n') + '\n'
// + 'Real steps:\n'
// + realSteps.join('\n')
)
})
// TODO: write test for simulateUntilNextTick
})
describe('pathfinder entity avoidance test', function () {
const mcData = require('minecraft-data')(Version)
const patherOptions = { resetEntityIntersects: false }
const maxPathTime = 50
const spawnPos = new Vec3(8.5, 1.0, 8.5) // Center of the chunk & center of the block
/** @type { import('mineflayer').Bot & { pathfinder: import('mineflayer-pathfinder').Pathfinder }} */
let bot
/** @type { import('minecraft-protocol').Server } */
let server
/** @type { import('prismarine-chunk').Chunk } */
let chunk
before(async () => {
chunk = await parkourMap(Version)
server = await newServer(server, chunk, spawnPos, Version, true)
bot = mineflayer.createBot({
username: 'player',
version: Version,
port: ServerPort
})
await once(bot, 'chunkColumnLoad')
bot.loadPlugin(pathfinder)
bot.pathfinder.setMovements(new Movements(bot, mcData))
})
after(() => {
bot.end()
bot = null
server.close()
})
/**
* Ensure algorithm does not impede performance when handling a large number of entities
*/
it('entityIndexPerformance', () => {
const { performance } = require('perf_hooks')
const targetBlock = new Vec3(11.5, 2.0, 10.5) // a gold block away from the spawn position
const startPos = new Vec3(11.5, 2.0, 14.5) // Start point for test
const goal = new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z)
for (let i = 1; i <= 10000; i++) {
const pos = (i % 2) === 0 ? new Vec3(10.5, 2.0, 12.5) : new Vec3(12.5, 2.0, 12.5)
bot.entities[i] = { name: 'testEntity', position: pos, height: 2.0, width: 1.0 }
}
const beforeTime = performance.now()
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal)
const { value: { result } } = generator.next()
const timeElapsed = performance.now() - beforeTime
bot.pathfinder.movements.clearCollisionIndex()
for (let i = 1; i <= 10000; i++) {
delete bot.entities[i]
}
assert.ok(timeElapsed < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
})
/**
* Tests if bot will prefer a basic path with less entities
* The test course is a 3x3x2 with a divider in the center
* [O] = Open, [W] = Wall, [S] = Start, [E] = End
* W E W
* W O O O W
* W O W O W
* W O O O W
* W S W
*/
describe('Weighted Path Avoidance', () => {
const targetBlock = new Vec3(11.5, 2.0, 10.5) // a gold block away from the spawn position
const startPos = new Vec3(11.5, 2.0, 14.5) // Start point for test
const firstLeftNode = new Vec3(10.5, 2.0, 12.5)
const firstRightNode = new Vec3(12.5, 2.0, 12.5)
const goal = new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z)
beforeEach((done) => {
bot.pathfinder.movements.clearCollisionIndex()
setTimeout(done, 100)
})
/**
* By default, algorithm will favor the Left Path
* [X] = Ent, [O] = Open, [W] = Wall
* O O O
* O W O
* O O O
*/
it('defaultPath', () => {
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
// Look at first and second nodes incase diagonal movements are used
const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'success')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 3, `Generated path length wrong (${path.length} === 3)`)
assert.ok(leftBranch === true, `Generated path did not follow Left Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}]`)
})
/**
* Ensure path with weight is avoided
* [X] = Ent, [O] = Open, [W] = Wall
* O O O
* O W X
* O O O
*/
it('rightBranchObstructed', () => {
add1x2Weight(bot.pathfinder.movements.entityIntersections, 12, 2, 12)
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
// Look at first and second nodes incase diagonal movements are used
const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'success')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 3, `Generated path length wrong (${path.length} === 3)`)
assert.ok(leftBranch === true, `Generated path did not follow Left Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}]`)
})
/**
* Ensure path with more weight is avoided
* [X] = Ent, [O] = Open, [W] = Wall
* O O O
* X W X
* X O O
*/
it('leftBranchMoreObstructed', () => {
add1x2Weight(bot.pathfinder.movements.entityIntersections, 12, 2, 12)
add1x2Weight(bot.pathfinder.movements.entityIntersections, 10, 2, 12)
add1x2Weight(bot.pathfinder.movements.entityIntersections, 10, 2, 13)
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
// Look at first and second nodes incase diagonal movements are used
const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'success')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 3, `Generated path length wrong (${path.length} === 3)`)
assert.ok(rightBranch === true, `Generated path did not follow Right Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}]`)
})
/**
* Ensure blocks adjacent to diagonal nodes are detected
* [X] = Ent, [O] = Open, [W] = Wall
* O O X
* X W O
* O O X
*/
it('rightBranchDiagsClear', () => {
add1x2Weight(bot.pathfinder.movements.entityIntersections, 12, 2, 13)
add1x2Weight(bot.pathfinder.movements.entityIntersections, 12, 2, 11)
add1x2Weight(bot.pathfinder.movements.entityIntersections, 10, 2, 12)
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
// Look at first and second nodes incase diagonal movements are used
const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'success')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 3, `Generated path length wrong (${path.length} === 3)`)
assert.ok(leftBranch === true, `Generated path did not follow Left Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}]`)
})
/**
* Ensure blocks adjacent to diagonal nodes are detected
* [X] = Ent, [O] = Open, [W] = Wall
* X O O
* O W X
* X O O
*/
it('leftBranchDiagsClear', () => {
add1x2Weight(bot.pathfinder.movements.entityIntersections, 12, 2, 12)
add1x2Weight(bot.pathfinder.movements.entityIntersections, 10, 2, 13)
add1x2Weight(bot.pathfinder.movements.entityIntersections, 10, 2, 11)
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
// Look at first and second nodes incase diagonal movements are used
const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'success')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 3, `Generated path length wrong (${path.length} === 3)`)
assert.ok(rightBranch === true, `Generated path did not follow Right Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}]`)
})
})
/**
* Tests if bot will try to path where they cannot build due to an entity and whether it will
* try to break a block that would potentially cause an entity to fall.
* The test course is a 2x2x4 pit where the start is at the bottom and the end is at the top
* [O] = Open, [W] = Wall, [S] = Start, [E] = End
* W W W E
* W O O W
* W S O W
* W W W W
*/
describe('Construction Path Avoidance', () => {
const Item = require('prismarine-item')(Version)
const scaffoldItemId = mcData.itemsByName.dirt.id
const groundYPos = 2
const lidYPos = 5
const forwardPos = { x: 11, z: 6 }
const leftPos = { x: 10, z: 6 }
const rightPos = { x: 11, z: 7 }
const backPos = { x: 10, z: 7 }
const targetBlock = new Vec3(forwardPos.x + 1.5, lidYPos + 1.0, forwardPos.z - 0.5) // a gold block away from the spawn position. One block diagonal from forward
const startPos = new Vec3(backPos.x + 0.5, groundYPos, backPos.z + 0.5) // Start point for test
const firstLeftNode = new Vec3(leftPos.x + 0.5, groundYPos, leftPos.z + 0.5)
const firstRightNode = new Vec3(rightPos.x + 0.5, groundYPos, rightPos.z + 0.5)
const firstForwardNode = new Vec3(forwardPos.x + 0.5, groundYPos, forwardPos.z + 0.5)
const firstBackNode = startPos.clone().plus(new Vec3(-0.5, 1, -0.5)) // Jump up isn't going to half block and targets one block higher
const blockersToPlace = [forwardPos, leftPos, rightPos, backPos]
const goal = new goals.GoalGetToBlock(targetBlock.x, targetBlock.y, targetBlock.z)
/** @type { import('minecraft-protocol').Client } */
let serverClient
/** @type { number } */
let hotbarSlot
before(() => {
serverClient = Object.values(server.clients)[0]
hotbarSlot = bot.inventory.firstEmptyHotbarSlot()
})
beforeEach((done) => {
bot.pathfinder.movements.clearCollisionIndex()
bot.inventory.slots[hotbarSlot] = new Item(scaffoldItemId, 64)
setTimeout(done, 100)
})
afterEach(async () => {
blockersToPlace.forEach(hPos => {
const blockPos = { x: hPos.x, y: lidYPos, z: hPos.z }
serverClient.write('block_change', { location: blockPos, type: mcData.blocksByName.air.id })
chunk.setBlockType(new Vec3(blockPos.x, blockPos.y, blockPos.z), mcData.blocksByName.air.id)
})
serverClient.write('map_chunk', generateChunkPacket(chunk))
await once(bot, 'chunkColumnLoad')
})
/**
* By default, algorithm will favor the Backward Path
* [X] = Ent Below, [+] = Ent Above a Block, [O] = Open, [W] = Wall
* W W W W
* W O O W
* W O O W
* W W W W
*/
it('defaultPath', () => {
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
// Look at first and second nodes incase diagonal movements are used
const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))
const forwardBranch = (path[0].equals(firstForwardNode) || path[1].equals(firstForwardNode))
const backwardBranch = (path[0].equals(firstBackNode) || path[1].equals(firstBackNode))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'success')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 6, `Generated path length wrong (${path.length} === 6)`)
assert.ok(backwardBranch === true, `Generated path did not follow Backward Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}, Forward Branch: ${forwardBranch}, Backward Branch: ${backwardBranch}]`)
})
/**
* Ensure bot finds a path when it cannot break left blocker with ent on top
* [X] = Ent Below, [+] = Ent Above a Block, [O] = Open, [W] = Wall
* W W W W
* W O O W
* W + O W
* W W W W
*/
it('backPathObstructed', async () => {
const blockPos = { x: backPos.x, y: lidYPos, z: backPos.z }
serverClient.write('block_change', { location: blockPos, type: mcData.blocksByName.dirt.id })
chunk.setBlockType(new Vec3(blockPos.x, blockPos.y, blockPos.z), mcData.blocksByName.dirt.id)
add1x2Weight(bot.pathfinder.movements.entityIntersections, blockPos.x, blockPos.y + 1, blockPos.z)
serverClient.write('map_chunk', generateChunkPacket(chunk))
await once(bot, 'chunkColumnLoad')
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
// Look at first and second nodes incase diagonal movements are used
const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))
const forwardBranch = (path[0].equals(firstForwardNode) || path[1].equals(firstForwardNode))
const backwardBranch = (path[0].equals(firstBackNode) || path[1].equals(firstBackNode))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'success')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 6, `Generated path length wrong (${path.length} === 6)`)
assert.ok(rightBranch === true, `Generated path did not follow Right Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}, Forward Branch: ${forwardBranch}, Backward Branch: ${backwardBranch}]`)
})
/**
* If there are blocks capping the pit with an entity above each block, ensure bot cannot path since
* there are no blocks that can be broken without potentially dropping an entity. Bot is expected
* to follow forward branch for as far as possible
* [X] = Ent Below, [+] = Ent Above a Block, [O] = Open, [W] = Wall
* W W W W
* W + + W
* W + + W
* W W W W
*/
it('noPathsBreakingObstructed', async () => {
blockersToPlace.forEach(hPos => {
const blockPos = { x: hPos.x, y: lidYPos, z: hPos.z }
serverClient.write('block_change', { location: blockPos, type: mcData.blocksByName.dirt.id })
chunk.setBlockType(new Vec3(blockPos.x, blockPos.y, blockPos.z), mcData.blocksByName.dirt.id)
add1x2Weight(bot.pathfinder.movements.entityIntersections, blockPos.x, blockPos.y + 1, blockPos.z)
})
serverClient.write('map_chunk', generateChunkPacket(chunk))
await once(bot, 'chunkColumnLoad')
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
// Look at first and second nodes incase diagonal movements are used
const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))
const forwardBranch = (path[0].equals(firstForwardNode) || path[1].equals(firstForwardNode))
const backwardBranch = (path[0].equals(firstBackNode) || path[1].equals(firstBackNode))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'noPath')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 2, `Generated path length wrong (${path.length} === 2)`)
assert.ok(forwardBranch === true, `Generated path did not attempt to follow Forward Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}, Forward Branch: ${forwardBranch}, Backward Branch: ${backwardBranch}]`)
})
/**
* If there are blocks capping the pit with an entity above each block, ensure bot can path
* if allowed in the movements configuration
* [X] = Ent Below, [+] = Ent Above a Block, [O] = Open, [W] = Wall
* W W W W
* W + + W
* W + + W
* W W W W
*/
it('canPathWithBreakingObstructed', async () => {
blockersToPlace.forEach(hPos => {
const blockPos = { x: hPos.x, y: lidYPos, z: hPos.z }
serverClient.write('block_change', { location: blockPos, type: mcData.blocksByName.dirt.id })
chunk.setBlockType(new Vec3(blockPos.x, blockPos.y, blockPos.z), mcData.blocksByName.dirt.id)
add1x2Weight(bot.pathfinder.movements.entityIntersections, blockPos.x, blockPos.y + 1, blockPos.z)
})
serverClient.write('map_chunk', generateChunkPacket(chunk))
await once(bot, 'chunkColumnLoad')
bot.pathfinder.movements.dontMineUnderFallingBlock = false
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
bot.pathfinder.movements.dontMineUnderFallingBlock = true
// Look at first and second nodes incase diagonal movements are used
const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))
const forwardBranch = (path[0].equals(firstForwardNode) || path[1].equals(firstForwardNode))
const backwardBranch = (path[0].equals(firstBackNode) || path[1].equals(firstBackNode))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'success')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 6, `Generated path length wrong (${path.length} === 6)`)
assert.ok(forwardBranch === true, `Generated path did not follow Forward Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}, Forward Branch: ${forwardBranch}, Backward Branch: ${backwardBranch}]`)
})
/**
* If there are entities filling the entire build area, ensure bot cannot path since
* entities will prevent any block placement. Bot is expected to follow forward branch
* for as far as possible
* [X] = Ent Below, [+] = Ent Above a Block, [O] = Open, [W] = Wall
* W W W W
* W X X W
* W X X W
* W W W W
*/
it('noPathsBuildingObstructed', () => {
blockersToPlace.forEach(hPos => {
add1x2Weight(bot.pathfinder.movements.entityIntersections, hPos.x, groundYPos, hPos.z)
})
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
const leftBranch = path[0].equals(firstLeftNode)
const rightBranch = path[0].equals(firstRightNode)
const forwardBranch = path[0].equals(firstForwardNode)
const backwardBranch = path[0].equals(firstBackNode)
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'noPath')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 1, `Generated path length wrong (${path.length} === 1)`)
assert.ok(forwardBranch === true, `Generated path did not attempt to follow Forward Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}, Forward Branch: ${forwardBranch}, Backward Branch: ${backwardBranch}]`)
})
/**
* If there are entities filling the entire build area except for one space, ensure bot finds a path
* [X] = Ent Below, [+] = Ent Above a Block, [O] = Open, [W] = Wall
* W W W W
* W O X W
* W X X W
* W W W W
*/
it('singlePathUnobstructed', () => {
blockersToPlace.forEach(hPos => {
if ((hPos.x !== leftPos.x) || (hPos.z !== leftPos.z)) {
add1x2Weight(bot.pathfinder.movements.entityIntersections, hPos.x, groundYPos, hPos.z)
}
})
const generator = bot.pathfinder.getPathFromTo(bot.pathfinder.movements, startPos, goal, patherOptions)
const { value: { result } } = generator.next()
const path = result.path
// Look at first and second nodes incase diagonal movements are used
const leftBranch = (path[0].equals(firstLeftNode) || path[1].equals(firstLeftNode))
const rightBranch = (path[0].equals(firstRightNode) || path[1].equals(firstRightNode))
const forwardBranch = (path[0].equals(firstForwardNode) || path[1].equals(firstForwardNode))
const backwardBranch = (path[0].equals(firstBackNode) || path[1].equals(firstBackNode))
// All depends on the actually path that gets generated. If target block is moved some were else these values have to change.
assert.strictEqual(result.status, 'success')
assert.ok(result.time < maxPathTime, `Generated path took too long (${result.time} < ${maxPathTime})`)
assert.ok(path.length === 6, `Generated path length wrong (${path.length} === 6)`)
assert.ok(leftBranch === true, `Generated path did not follow Left Branch [Left Branch: ${leftBranch}, Right Branch: ${rightBranch}, Forward Branch: ${forwardBranch}, Backward Branch: ${backwardBranch}]`)
})
})
})