663 lines
24 KiB
JavaScript
663 lines
24 KiB
JavaScript
const { Vec3 } = require('vec3')
|
|
const nbt = require('prismarine-nbt')
|
|
const Move = require('./move')
|
|
|
|
const cardinalDirections = [
|
|
{ x: -1, z: 0 }, // West
|
|
{ x: 1, z: 0 }, // East
|
|
{ x: 0, z: -1 }, // North
|
|
{ x: 0, z: 1 } // South
|
|
]
|
|
const diagonalDirections = [
|
|
{ x: -1, z: -1 },
|
|
{ x: -1, z: 1 },
|
|
{ x: 1, z: -1 },
|
|
{ x: 1, z: 1 }
|
|
]
|
|
|
|
class Movements {
|
|
constructor (bot) {
|
|
const registry = bot.registry
|
|
this.bot = bot
|
|
|
|
this.canDig = true
|
|
this.digCost = 1
|
|
this.placeCost = 1
|
|
this.liquidCost = 1
|
|
this.entityCost = 1
|
|
|
|
this.dontCreateFlow = true
|
|
this.dontMineUnderFallingBlock = true
|
|
this.allow1by1towers = true
|
|
this.allowFreeMotion = false
|
|
this.allowParkour = true
|
|
this.allowSprinting = true
|
|
this.allowEntityDetection = true
|
|
|
|
this.entitiesToAvoid = new Set()
|
|
this.passableEntities = new Set(require('./passableEntities.json'))
|
|
this.interactableBlocks = new Set(require('./interactable.json'))
|
|
|
|
this.blocksCantBreak = new Set()
|
|
this.blocksCantBreak.add(registry.blocksByName.chest.id)
|
|
|
|
registry.blocksArray.forEach(block => {
|
|
if (block.diggable) return
|
|
this.blocksCantBreak.add(block.id)
|
|
})
|
|
|
|
this.blocksToAvoid = new Set()
|
|
this.blocksToAvoid.add(registry.blocksByName.fire.id)
|
|
if (registry.blocksByName.cobweb) this.blocksToAvoid.add(registry.blocksByName.cobweb.id)
|
|
if (registry.blocksByName.web) this.blocksToAvoid.add(registry.blocksByName.web.id)
|
|
this.blocksToAvoid.add(registry.blocksByName.lava.id)
|
|
|
|
this.liquids = new Set()
|
|
this.liquids.add(registry.blocksByName.water.id)
|
|
this.liquids.add(registry.blocksByName.lava.id)
|
|
|
|
this.gravityBlocks = new Set()
|
|
this.gravityBlocks.add(registry.blocksByName.sand.id)
|
|
this.gravityBlocks.add(registry.blocksByName.gravel.id)
|
|
|
|
this.climbables = new Set()
|
|
this.climbables.add(registry.blocksByName.ladder.id)
|
|
// this.climbables.add(registry.blocksByName.vine.id)
|
|
this.emptyBlocks = new Set()
|
|
|
|
this.replaceables = new Set()
|
|
this.replaceables.add(registry.blocksByName.air.id)
|
|
if (registry.blocksByName.cave_air) this.replaceables.add(registry.blocksByName.cave_air.id)
|
|
if (registry.blocksByName.void_air) this.replaceables.add(registry.blocksByName.void_air.id)
|
|
this.replaceables.add(registry.blocksByName.water.id)
|
|
this.replaceables.add(registry.blocksByName.lava.id)
|
|
|
|
this.scafoldingBlocks = []
|
|
this.scafoldingBlocks.push(registry.itemsByName.dirt.id)
|
|
this.scafoldingBlocks.push(registry.itemsByName.cobblestone.id)
|
|
|
|
const Block = require('prismarine-block')(bot.registry)
|
|
this.fences = new Set()
|
|
this.carpets = new Set()
|
|
this.openable = new Set()
|
|
registry.blocksArray.map(x => Block.fromStateId(x.minStateId, 0)).forEach(block => {
|
|
if (block.shapes.length > 0) {
|
|
// Fences or any block taller than 1, they will be considered as non-physical to avoid
|
|
// trying to walk on them
|
|
if (block.shapes[0][4] > 1) this.fences.add(block.type)
|
|
// Carpets or any blocks smaller than 0.1, they will be considered as safe to walk in
|
|
if (block.shapes[0][4] < 0.1) this.carpets.add(block.type)
|
|
} else if (block.shapes.length === 0) {
|
|
this.emptyBlocks.add(block.type)
|
|
}
|
|
})
|
|
registry.blocksArray.forEach(block => {
|
|
if (this.interactableBlocks.has(block.name) && block.name.toLowerCase().includes('gate') && !block.name.toLowerCase().includes('iron')) {
|
|
// console.info(block)
|
|
this.openable.add(block.id)
|
|
}
|
|
})
|
|
|
|
this.canOpenDoors = false // Causes issues. Probably due to none paper servers.
|
|
|
|
this.exclusionAreasStep = []
|
|
this.exclusionAreasBreak = []
|
|
this.exclusionAreasPlace = []
|
|
|
|
this.maxDropDown = 4
|
|
this.infiniteLiquidDropdownDistance = true
|
|
|
|
this.entityIntersections = {}
|
|
}
|
|
|
|
exclusionPlace (block) {
|
|
if (this.exclusionAreasPlace.length === 0) return 0
|
|
let weight = 0
|
|
for (const a of this.exclusionAreasPlace) {
|
|
weight += a(block)
|
|
}
|
|
return weight
|
|
}
|
|
|
|
exclusionStep (block) {
|
|
if (this.exclusionAreasStep.length === 0) return 0
|
|
let weight = 0
|
|
for (const a of this.exclusionAreasStep) {
|
|
weight += a(block)
|
|
}
|
|
return weight
|
|
}
|
|
|
|
exclusionBreak (block) {
|
|
if (this.exclusionAreasBreak.length === 0) return 0
|
|
let weight = 0
|
|
for (const a of this.exclusionAreasBreak) {
|
|
weight += a(block)
|
|
}
|
|
return weight
|
|
}
|
|
|
|
countScaffoldingItems () {
|
|
let count = 0
|
|
const items = this.bot.inventory.items()
|
|
for (const id of this.scafoldingBlocks) {
|
|
for (const j in items) {
|
|
const item = items[j]
|
|
if (item.type === id) count += item.count
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
getScaffoldingItem () {
|
|
const items = this.bot.inventory.items()
|
|
for (const id of this.scafoldingBlocks) {
|
|
for (const j in items) {
|
|
const item = items[j]
|
|
if (item.type === id) return item
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
clearCollisionIndex () {
|
|
this.entityIntersections = {}
|
|
}
|
|
|
|
/**
|
|
* Finds blocks intersected by entity bounding boxes
|
|
* and sets the number of ents intersecting in a dict.
|
|
* Ignores entities that do not affect block placement
|
|
*/
|
|
updateCollisionIndex () {
|
|
for (const ent of Object.values(this.bot.entities)) {
|
|
if (ent === this.bot.entity) { continue }
|
|
|
|
const avoidedEnt = this.entitiesToAvoid.has(ent.name)
|
|
if (avoidedEnt || !this.passableEntities.has(ent.name)) {
|
|
const entSquareRadius = ent.width / 2.0
|
|
const minY = Math.floor(ent.position.y)
|
|
const maxY = Math.ceil(ent.position.y + ent.height)
|
|
const minX = Math.floor(ent.position.x - entSquareRadius)
|
|
const maxX = Math.ceil(ent.position.x + entSquareRadius)
|
|
const minZ = Math.floor(ent.position.z - entSquareRadius)
|
|
const maxZ = Math.ceil(ent.position.z + entSquareRadius)
|
|
|
|
const cost = avoidedEnt ? 100 : 1
|
|
|
|
for (let y = minY; y < maxY; y++) {
|
|
for (let x = minX; x < maxX; x++) {
|
|
for (let z = minZ; z < maxZ; z++) {
|
|
this.entityIntersections[`${x},${y},${z}`] = this.entityIntersections[`${x},${y},${z}`] ?? 0
|
|
this.entityIntersections[`${x},${y},${z}`] += cost // More ents = more weight
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets number of entities who's bounding box intersects the node + offset
|
|
* @param {import('vec3').Vec3} pos node position
|
|
* @param {number} dx X axis offset
|
|
* @param {number} dy Y axis offset
|
|
* @param {number} dz Z axis offset
|
|
* @returns {number} Number of entities intersecting block
|
|
*/
|
|
getNumEntitiesAt (pos, dx, dy, dz) {
|
|
if (this.allowEntityDetection === false) return 0
|
|
if (!pos) return 0
|
|
const y = pos.y + dy
|
|
const x = pos.x + dx
|
|
const z = pos.z + dz
|
|
|
|
return this.entityIntersections[`${x},${y},${z}`] ?? 0
|
|
}
|
|
|
|
getBlock (pos, dx, dy, dz) {
|
|
const b = pos ? this.bot.blockAt(new Vec3(pos.x + dx, pos.y + dy, pos.z + dz), false) : null
|
|
if (!b) {
|
|
return {
|
|
replaceable: false,
|
|
canFall: false,
|
|
safe: false,
|
|
physical: false,
|
|
liquid: false,
|
|
climbable: false,
|
|
height: dy,
|
|
openable: false
|
|
}
|
|
}
|
|
b.climbable = this.climbables.has(b.type)
|
|
b.safe = (b.boundingBox === 'empty' || b.climbable || this.carpets.has(b.type)) && !this.blocksToAvoid.has(b.type)
|
|
b.physical = b.boundingBox === 'block' && !this.fences.has(b.type)
|
|
b.replaceable = this.replaceables.has(b.type) && !b.physical
|
|
b.liquid = this.liquids.has(b.type)
|
|
b.height = pos.y + dy
|
|
b.canFall = this.gravityBlocks.has(b.type)
|
|
b.openable = this.openable.has(b.type)
|
|
|
|
for (const shape of b.shapes) {
|
|
b.height = Math.max(b.height, pos.y + dy + shape[4])
|
|
}
|
|
return b
|
|
}
|
|
|
|
/**
|
|
* Takes into account if the block is within a break exclusion area.
|
|
* @param {import('prismarine-block').Block} block
|
|
* @returns
|
|
*/
|
|
safeToBreak (block) {
|
|
if (!this.canDig) {
|
|
return false
|
|
}
|
|
|
|
if (this.dontCreateFlow) {
|
|
// false if next to liquid
|
|
if (this.getBlock(block.position, 0, 1, 0).liquid) return false
|
|
if (this.getBlock(block.position, -1, 0, 0).liquid) return false
|
|
if (this.getBlock(block.position, 1, 0, 0).liquid) return false
|
|
if (this.getBlock(block.position, 0, 0, -1).liquid) return false
|
|
if (this.getBlock(block.position, 0, 0, 1).liquid) return false
|
|
}
|
|
|
|
if (this.dontMineUnderFallingBlock) {
|
|
// TODO: Determine if there are other blocks holding the entity up
|
|
if (this.getBlock(block.position, 0, 1, 0).canFall || (this.getNumEntitiesAt(block.position, 0, 1, 0) > 0)) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return block.type && !this.blocksCantBreak.has(block.type) && this.exclusionBreak(block) < 100
|
|
}
|
|
|
|
/**
|
|
* Takes into account if the block is within the stepExclusionAreas. And returns 100 if a block to be broken is within break exclusion areas.
|
|
* @param {import('prismarine-block').Block} block block
|
|
* @param {[]} toBreak
|
|
* @returns {number}
|
|
*/
|
|
safeOrBreak (block, toBreak) {
|
|
let cost = 0
|
|
cost += this.exclusionStep(block) // Is excluded so can't move or break
|
|
cost += this.getNumEntitiesAt(block.position, 0, 0, 0) * this.entityCost
|
|
if (block.safe) return cost
|
|
if (!this.safeToBreak(block)) return 100 // Can't break, so can't move
|
|
toBreak.push(block.position)
|
|
|
|
if (block.physical) cost += this.getNumEntitiesAt(block.position, 0, 1, 0) * this.entityCost // Add entity cost if there is an entity above (a breakable block) that will fall
|
|
|
|
const tool = this.bot.pathfinder.bestHarvestTool(block)
|
|
const enchants = (tool && tool.nbt) ? nbt.simplify(tool.nbt).Enchantments : []
|
|
const effects = this.bot.entity.effects
|
|
const digTime = block.digTime(tool ? tool.type : null, false, false, false, enchants, effects)
|
|
const laborCost = (1 + 3 * digTime / 1000) * this.digCost
|
|
cost += laborCost
|
|
return cost
|
|
}
|
|
|
|
getMoveJumpUp (node, dir, neighbors) {
|
|
const blockA = this.getBlock(node, 0, 2, 0)
|
|
const blockH = this.getBlock(node, dir.x, 2, dir.z)
|
|
const blockB = this.getBlock(node, dir.x, 1, dir.z)
|
|
const blockC = this.getBlock(node, dir.x, 0, dir.z)
|
|
|
|
let cost = 2 // move cost (move+jump)
|
|
const toBreak = []
|
|
const toPlace = []
|
|
|
|
if (blockA.physical && (this.getNumEntitiesAt(blockA.position, 0, 1, 0) > 0)) return // Blocks A, B and H are above C, D and the player's space, we need to make sure there are no entities that will fall down onto our building space if we break them
|
|
if (blockH.physical && (this.getNumEntitiesAt(blockH.position, 0, 1, 0) > 0)) return
|
|
if (blockB.physical && !blockH.physical && !blockC.physical && (this.getNumEntitiesAt(blockB.position, 0, 1, 0) > 0)) return // It is fine if an ent falls on B so long as we don't need to replace block C
|
|
|
|
if (!blockC.physical) {
|
|
if (node.remainingBlocks === 0) return // not enough blocks to place
|
|
|
|
if (this.getNumEntitiesAt(blockC.position, 0, 0, 0) > 0) return // Check for any entities in the way of a block placement
|
|
|
|
const blockD = this.getBlock(node, dir.x, -1, dir.z)
|
|
if (!blockD.physical) {
|
|
if (node.remainingBlocks === 1) return // not enough blocks to place
|
|
|
|
if (this.getNumEntitiesAt(blockD.position, 0, 0, 0) > 0) return // Check for any entities in the way of a block placement
|
|
|
|
if (!blockD.replaceable) {
|
|
if (!this.safeToBreak(blockD)) return
|
|
cost += this.exclusionBreak(blockD)
|
|
toBreak.push(blockD.position)
|
|
}
|
|
cost += this.exclusionPlace(blockD)
|
|
toPlace.push({ x: node.x, y: node.y - 1, z: node.z, dx: dir.x, dy: 0, dz: dir.z, returnPos: new Vec3(node.x, node.y, node.z) })
|
|
cost += this.placeCost // additional cost for placing a block
|
|
}
|
|
|
|
if (!blockC.replaceable) {
|
|
if (!this.safeToBreak(blockC)) return
|
|
cost += this.exclusionBreak(blockC)
|
|
toBreak.push(blockC.position)
|
|
}
|
|
cost += this.exclusionPlace(blockC)
|
|
toPlace.push({ x: node.x + dir.x, y: node.y - 1, z: node.z + dir.z, dx: 0, dy: 1, dz: 0 })
|
|
cost += this.placeCost // additional cost for placing a block
|
|
|
|
blockC.height += 1
|
|
}
|
|
|
|
const block0 = this.getBlock(node, 0, -1, 0)
|
|
if (blockC.height - block0.height > 1.2) return // Too high to jump
|
|
|
|
cost += this.safeOrBreak(blockA, toBreak)
|
|
if (cost > 100) return
|
|
cost += this.safeOrBreak(blockH, toBreak)
|
|
if (cost > 100) return
|
|
cost += this.safeOrBreak(blockB, toBreak)
|
|
if (cost > 100) return
|
|
|
|
neighbors.push(new Move(blockB.position.x, blockB.position.y, blockB.position.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace))
|
|
}
|
|
|
|
getMoveForward (node, dir, neighbors) {
|
|
const blockB = this.getBlock(node, dir.x, 1, dir.z)
|
|
const blockC = this.getBlock(node, dir.x, 0, dir.z)
|
|
const blockD = this.getBlock(node, dir.x, -1, dir.z)
|
|
|
|
let cost = 1 // move cost
|
|
cost += this.exclusionStep(blockC)
|
|
|
|
const toBreak = []
|
|
const toPlace = []
|
|
|
|
if (!blockD.physical && !blockC.liquid) {
|
|
if (node.remainingBlocks === 0) return // not enough blocks to place
|
|
|
|
if (this.getNumEntitiesAt(blockD.position, 0, 0, 0) > 0) return // D intersects an entity hitbox
|
|
|
|
if (!blockD.replaceable) {
|
|
if (!this.safeToBreak(blockD)) return
|
|
cost += this.exclusionBreak(blockD)
|
|
toBreak.push(blockD.position)
|
|
}
|
|
cost += this.exclusionPlace(blockC)
|
|
toPlace.push({ x: node.x, y: node.y - 1, z: node.z, dx: dir.x, dy: 0, dz: dir.z })
|
|
cost += this.placeCost // additional cost for placing a block
|
|
}
|
|
|
|
cost += this.safeOrBreak(blockB, toBreak)
|
|
if (cost > 100) return
|
|
|
|
// Open fence gates
|
|
if (this.canOpenDoors && blockC.openable && blockC.shapes && blockC.shapes.length !== 0) {
|
|
toPlace.push({ x: node.x + dir.x, y: node.y, z: node.z + dir.z, dx: 0, dy: 0, dz: 0, useOne: true }) // Indicate that a block should be used on this block not placed
|
|
} else {
|
|
cost += this.safeOrBreak(blockC, toBreak)
|
|
if (cost > 100) return
|
|
}
|
|
|
|
if (this.getBlock(node, 0, 0, 0).liquid) cost += this.liquidCost
|
|
|
|
neighbors.push(new Move(blockC.position.x, blockC.position.y, blockC.position.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace))
|
|
}
|
|
|
|
getMoveDiagonal (node, dir, neighbors) {
|
|
let cost = Math.SQRT2 // move cost
|
|
const toBreak = []
|
|
|
|
const blockC = this.getBlock(node, dir.x, 0, dir.z) // Landing block or standing on block when jumping up by 1
|
|
const y = blockC.physical ? 1 : 0
|
|
|
|
const block0 = this.getBlock(node, 0, -1, 0)
|
|
|
|
let cost1 = 0
|
|
const toBreak1 = []
|
|
const blockB1 = this.getBlock(node, 0, y + 1, dir.z)
|
|
const blockC1 = this.getBlock(node, 0, y, dir.z)
|
|
const blockD1 = this.getBlock(node, 0, y - 1, dir.z)
|
|
cost1 += this.safeOrBreak(blockB1, toBreak1)
|
|
cost1 += this.safeOrBreak(blockC1, toBreak1)
|
|
if (blockD1.height - block0.height > 1.2) cost1 += this.safeOrBreak(blockD1, toBreak1)
|
|
|
|
let cost2 = 0
|
|
const toBreak2 = []
|
|
const blockB2 = this.getBlock(node, dir.x, y + 1, 0)
|
|
const blockC2 = this.getBlock(node, dir.x, y, 0)
|
|
const blockD2 = this.getBlock(node, dir.x, y - 1, 0)
|
|
cost2 += this.safeOrBreak(blockB2, toBreak2)
|
|
cost2 += this.safeOrBreak(blockC2, toBreak2)
|
|
if (blockD2.height - block0.height > 1.2) cost2 += this.safeOrBreak(blockD2, toBreak2)
|
|
|
|
if (cost1 < cost2) {
|
|
cost += cost1
|
|
toBreak.push(...toBreak1)
|
|
} else {
|
|
cost += cost2
|
|
toBreak.push(...toBreak2)
|
|
}
|
|
if (cost > 100) return
|
|
|
|
cost += this.safeOrBreak(this.getBlock(node, dir.x, y, dir.z), toBreak)
|
|
if (cost > 100) return
|
|
cost += this.safeOrBreak(this.getBlock(node, dir.x, y + 1, dir.z), toBreak)
|
|
if (cost > 100) return
|
|
|
|
if (this.getBlock(node, 0, 0, 0).liquid) cost += this.liquidCost
|
|
|
|
const blockD = this.getBlock(node, dir.x, -1, dir.z)
|
|
if (y === 1) { // Case jump up by 1
|
|
if (blockC.height - block0.height > 1.2) return // Too high to jump
|
|
cost += this.safeOrBreak(this.getBlock(node, 0, 2, 0), toBreak)
|
|
if (cost > 100) return
|
|
cost += 1
|
|
neighbors.push(new Move(blockC.position.x, blockC.position.y + 1, blockC.position.z, node.remainingBlocks, cost, toBreak))
|
|
} else if (blockD.physical || blockC.liquid) {
|
|
neighbors.push(new Move(blockC.position.x, blockC.position.y, blockC.position.z, node.remainingBlocks, cost, toBreak))
|
|
} else if (this.getBlock(node, dir.x, -2, dir.z).physical || blockD.liquid) {
|
|
if (!blockD.safe) return // don't self-immolate
|
|
cost += this.getNumEntitiesAt(blockC.position, 0, -1, 0) * this.entityCost
|
|
neighbors.push(new Move(blockC.position.x, blockC.position.y - 1, blockC.position.z, node.remainingBlocks, cost, toBreak))
|
|
}
|
|
}
|
|
|
|
getLandingBlock (node, dir) {
|
|
let blockLand = this.getBlock(node, dir.x, -2, dir.z)
|
|
while (blockLand.position && blockLand.position.y > this.bot.game.minY) {
|
|
if (blockLand.liquid && blockLand.safe) return blockLand
|
|
if (blockLand.physical) {
|
|
if (node.y - blockLand.position.y <= this.maxDropDown) return this.getBlock(blockLand.position, 0, 1, 0)
|
|
return null
|
|
}
|
|
if (!blockLand.safe) return null
|
|
blockLand = this.getBlock(blockLand.position, 0, -1, 0)
|
|
}
|
|
return null
|
|
}
|
|
|
|
getMoveDropDown (node, dir, neighbors) {
|
|
const blockB = this.getBlock(node, dir.x, 1, dir.z)
|
|
const blockC = this.getBlock(node, dir.x, 0, dir.z)
|
|
const blockD = this.getBlock(node, dir.x, -1, dir.z)
|
|
|
|
let cost = 1 // move cost
|
|
const toBreak = []
|
|
const toPlace = []
|
|
|
|
const blockLand = this.getLandingBlock(node, dir)
|
|
if (!blockLand) return
|
|
if (!this.infiniteLiquidDropdownDistance && ((node.y - blockLand.position.y) > this.maxDropDown)) return // Don't drop down into water
|
|
|
|
cost += this.safeOrBreak(blockB, toBreak)
|
|
if (cost > 100) return
|
|
cost += this.safeOrBreak(blockC, toBreak)
|
|
if (cost > 100) return
|
|
cost += this.safeOrBreak(blockD, toBreak)
|
|
if (cost > 100) return
|
|
|
|
if (blockC.liquid) return // dont go underwater
|
|
|
|
cost += this.getNumEntitiesAt(blockLand.position, 0, 0, 0) * this.entityCost // add cost for entities
|
|
|
|
neighbors.push(new Move(blockLand.position.x, blockLand.position.y, blockLand.position.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace))
|
|
}
|
|
|
|
getMoveDown (node, neighbors) {
|
|
const block0 = this.getBlock(node, 0, -1, 0)
|
|
|
|
let cost = 1 // move cost
|
|
const toBreak = []
|
|
const toPlace = []
|
|
|
|
const blockLand = this.getLandingBlock(node, { x: 0, z: 0 })
|
|
if (!blockLand) return
|
|
|
|
cost += this.safeOrBreak(block0, toBreak)
|
|
if (cost > 100) return
|
|
|
|
if (this.getBlock(node, 0, 0, 0).liquid) return // dont go underwater
|
|
|
|
cost += this.getNumEntitiesAt(blockLand.position, 0, 0, 0) * this.entityCost // add cost for entities
|
|
|
|
neighbors.push(new Move(blockLand.position.x, blockLand.position.y, blockLand.position.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace))
|
|
}
|
|
|
|
getMoveUp (node, neighbors) {
|
|
const block1 = this.getBlock(node, 0, 0, 0)
|
|
if (block1.liquid) return
|
|
if (this.getNumEntitiesAt(node, 0, 0, 0) > 0) return // an entity (besides the player) is blocking the building area
|
|
|
|
const block2 = this.getBlock(node, 0, 2, 0)
|
|
|
|
let cost = 1 // move cost
|
|
const toBreak = []
|
|
const toPlace = []
|
|
cost += this.safeOrBreak(block2, toBreak)
|
|
if (cost > 100) return
|
|
|
|
if (!block1.climbable) {
|
|
if (!this.allow1by1towers || node.remainingBlocks === 0) return // not enough blocks to place
|
|
|
|
if (!block1.replaceable) {
|
|
if (!this.safeToBreak(block1)) return
|
|
toBreak.push(block1.position)
|
|
}
|
|
|
|
const block0 = this.getBlock(node, 0, -1, 0)
|
|
if (block0.physical && block0.height - node.y < -0.2) return // cannot jump-place from a half block
|
|
|
|
cost += this.exclusionPlace(block1)
|
|
toPlace.push({ x: node.x, y: node.y - 1, z: node.z, dx: 0, dy: 1, dz: 0, jump: true })
|
|
cost += this.placeCost // additional cost for placing a block
|
|
}
|
|
|
|
if (cost > 100) return
|
|
|
|
neighbors.push(new Move(node.x, node.y + 1, node.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace))
|
|
}
|
|
|
|
// Jump up, down or forward over a 1 block gap
|
|
getMoveParkourForward (node, dir, neighbors) {
|
|
const block0 = this.getBlock(node, 0, -1, 0)
|
|
const block1 = this.getBlock(node, dir.x, -1, dir.z)
|
|
if ((block1.physical && block1.height >= block0.height) ||
|
|
!this.getBlock(node, dir.x, 0, dir.z).safe ||
|
|
!this.getBlock(node, dir.x, 1, dir.z).safe) return
|
|
if (this.getBlock(node, 0, 0, 0).liquid) return // cant jump from water
|
|
|
|
let cost = 1
|
|
|
|
// Leaving entities at the ceiling level (along path) out for now because there are few cases where that will be important
|
|
cost += this.getNumEntitiesAt(node, dir.x, 0, dir.z) * this.entityCost
|
|
|
|
// If we have a block on the ceiling, we cannot jump but we can still fall
|
|
let ceilingClear = this.getBlock(node, 0, 2, 0).safe && this.getBlock(node, dir.x, 2, dir.z).safe
|
|
|
|
// Similarly for the down path
|
|
let floorCleared = !this.getBlock(node, dir.x, -2, dir.z).physical
|
|
|
|
const maxD = this.allowSprinting ? 4 : 2
|
|
|
|
for (let d = 2; d <= maxD; d++) {
|
|
const dx = dir.x * d
|
|
const dz = dir.z * d
|
|
const blockA = this.getBlock(node, dx, 2, dz)
|
|
const blockB = this.getBlock(node, dx, 1, dz)
|
|
const blockC = this.getBlock(node, dx, 0, dz)
|
|
const blockD = this.getBlock(node, dx, -1, dz)
|
|
|
|
if (blockC.safe) cost += this.getNumEntitiesAt(blockC.position, 0, 0, 0) * this.entityCost
|
|
|
|
if (ceilingClear && blockB.safe && blockC.safe && blockD.physical) {
|
|
cost += this.exclusionStep(blockB)
|
|
// Forward
|
|
neighbors.push(new Move(blockC.position.x, blockC.position.y, blockC.position.z, node.remainingBlocks, cost, [], [], true))
|
|
break
|
|
} else if (ceilingClear && blockB.safe && blockC.physical) {
|
|
// Up
|
|
if (blockA.safe && d !== 4) { // 4 Blocks forward 1 block up is very difficult and fails often
|
|
cost += this.exclusionStep(blockA)
|
|
if (blockC.height - block0.height > 1.2) break // Too high to jump
|
|
cost += this.getNumEntitiesAt(blockB.position, 0, 0, 0) * this.entityCost
|
|
neighbors.push(new Move(blockB.position.x, blockB.position.y, blockB.position.z, node.remainingBlocks, cost, [], [], true))
|
|
break
|
|
}
|
|
} else if ((ceilingClear || d === 2) && blockB.safe && blockC.safe && blockD.safe && floorCleared) {
|
|
// Down
|
|
const blockE = this.getBlock(node, dx, -2, dz)
|
|
if (blockE.physical) {
|
|
cost += this.exclusionStep(blockD)
|
|
cost += this.getNumEntitiesAt(blockD.position, 0, 0, 0) * this.entityCost
|
|
neighbors.push(new Move(blockD.position.x, blockD.position.y, blockD.position.z, node.remainingBlocks, cost, [], [], true))
|
|
}
|
|
floorCleared = floorCleared && !blockE.physical
|
|
} else if (!blockB.safe || !blockC.safe) {
|
|
break
|
|
}
|
|
|
|
ceilingClear = ceilingClear && blockA.safe
|
|
}
|
|
}
|
|
|
|
// for each cardinal direction:
|
|
// "." is head. "+" is feet and current location.
|
|
// "#" is initial floor which is always solid. "a"-"u" are blocks to check
|
|
//
|
|
// --0123-- horizontalOffset
|
|
// |
|
|
// +2 aho
|
|
// +1 .bip
|
|
// 0 +cjq
|
|
// -1 #dkr
|
|
// -2 els
|
|
// -3 fmt
|
|
// -4 gn
|
|
// |
|
|
// dy
|
|
|
|
getNeighbors (node) {
|
|
const neighbors = []
|
|
|
|
// Simple moves in 4 cardinal points
|
|
for (const i in cardinalDirections) {
|
|
const dir = cardinalDirections[i]
|
|
this.getMoveForward(node, dir, neighbors)
|
|
this.getMoveJumpUp(node, dir, neighbors)
|
|
this.getMoveDropDown(node, dir, neighbors)
|
|
if (this.allowParkour) {
|
|
this.getMoveParkourForward(node, dir, neighbors)
|
|
}
|
|
}
|
|
|
|
// Diagonals
|
|
for (const i in diagonalDirections) {
|
|
const dir = diagonalDirections[i]
|
|
this.getMoveDiagonal(node, dir, neighbors)
|
|
}
|
|
|
|
this.getMoveDown(node, neighbors)
|
|
this.getMoveUp(node, neighbors)
|
|
|
|
return neighbors
|
|
}
|
|
}
|
|
|
|
module.exports = Movements
|