LookAtMySuitBot/js/node_modules/prismarine-physics/index.js

837 lines
28 KiB
JavaScript
Raw Normal View History

2023-12-24 20:08:39 -05:00
const Vec3 = require('vec3').Vec3
const AABB = require('./lib/aabb')
const math = require('./lib/math')
const features = require('./lib/features')
const attribute = require('./lib/attribute')
function makeSupportFeature (mcData) {
return feature => features.some(({ name, versions }) => name === feature && versions.includes(mcData.version.majorVersion))
}
function Physics (mcData, world) {
const supportFeature = makeSupportFeature(mcData)
const blocksByName = mcData.blocksByName
// Block Slipperiness
// https://www.mcpk.wiki/w/index.php?title=Slipperiness
const blockSlipperiness = {}
const slimeBlockId = blocksByName.slime_block ? blocksByName.slime_block.id : blocksByName.slime.id
blockSlipperiness[slimeBlockId] = 0.8
blockSlipperiness[blocksByName.ice.id] = 0.98
blockSlipperiness[blocksByName.packed_ice.id] = 0.98
if (blocksByName.frosted_ice) { // 1.9+
blockSlipperiness[blocksByName.frosted_ice.id] = 0.98
}
if (blocksByName.blue_ice) { // 1.13+
blockSlipperiness[blocksByName.blue_ice.id] = 0.989
}
// Block ids
const soulsandId = blocksByName.soul_sand.id
const honeyblockId = blocksByName.honey_block ? blocksByName.honey_block.id : -1 // 1.15+
const webId = blocksByName.cobweb ? blocksByName.cobweb.id : blocksByName.web.id
const waterIds = [blocksByName.water.id, blocksByName.flowing_water ? blocksByName.flowing_water.id : -1]
const lavaIds = [blocksByName.lava.id, blocksByName.flowing_lava ? blocksByName.flowing_lava.id : -1]
const ladderId = blocksByName.ladder.id
const vineId = blocksByName.vine.id
const waterLike = new Set()
if (blocksByName.seagrass) waterLike.add(blocksByName.seagrass.id) // 1.13+
if (blocksByName.tall_seagrass) waterLike.add(blocksByName.tall_seagrass.id) // 1.13+
if (blocksByName.kelp) waterLike.add(blocksByName.kelp.id) // 1.13+
if (blocksByName.kelp_plant) waterLike.add(blocksByName.kelp_plant.id) // 1.13+
const bubblecolumnId = blocksByName.bubble_column ? blocksByName.bubble_column.id : -1 // 1.13+
if (blocksByName.bubble_column) waterLike.add(bubblecolumnId)
const physics = {
gravity: 0.08, // blocks/tick^2 https://minecraft.gamepedia.com/Entity#Motion_of_entities
airdrag: Math.fround(1 - 0.02), // actually (1 - drag)
yawSpeed: 3.0,
pitchSpeed: 3.0,
playerSpeed: 0.1,
sprintSpeed: 0.3,
sneakSpeed: 0.3,
stepHeight: 0.6, // how much height can the bot step on without jump
negligeableVelocity: 0.003, // actually 0.005 for 1.8, but seems fine
soulsandSpeed: 0.4,
honeyblockSpeed: 0.4,
honeyblockJumpSpeed: 0.4,
ladderMaxSpeed: 0.15,
ladderClimbSpeed: 0.2,
playerHalfWidth: 0.3,
playerHeight: 1.8,
waterInertia: 0.8,
lavaInertia: 0.5,
liquidAcceleration: 0.02,
airborneInertia: 0.91,
airborneAcceleration: 0.02,
defaultSlipperiness: 0.6,
outOfLiquidImpulse: 0.3,
autojumpCooldown: 10, // ticks (0.5s)
bubbleColumnSurfaceDrag: {
down: 0.03,
maxDown: -0.9,
up: 0.1,
maxUp: 1.8
},
bubbleColumnDrag: {
down: 0.03,
maxDown: -0.3,
up: 0.06,
maxUp: 0.7
},
slowFalling: 0.125,
movementSpeedAttribute: mcData.attributesByName.movementSpeed.resource,
sprintingUUID: '662a6b8d-da3e-4c1c-8813-96ea6097278d' // SPEED_MODIFIER_SPRINTING_UUID is from LivingEntity.java
}
if (supportFeature('independentLiquidGravity')) {
physics.waterGravity = 0.02
physics.lavaGravity = 0.02
} else if (supportFeature('proportionalLiquidGravity')) {
physics.waterGravity = physics.gravity / 16
physics.lavaGravity = physics.gravity / 4
} else {
throw new Error('No liquid gravity settings, have you made sure the liquid gravity features are up to date?')
}
function getPlayerBB (pos) {
const w = physics.playerHalfWidth
return new AABB(-w, 0, -w, w, physics.playerHeight, w).offset(pos.x, pos.y, pos.z)
}
function setPositionToBB (bb, pos) {
pos.x = bb.minX + physics.playerHalfWidth
pos.y = bb.minY
pos.z = bb.minZ + physics.playerHalfWidth
}
function getSurroundingBBs (world, queryBB) {
const surroundingBBs = []
const cursor = new Vec3(0, 0, 0)
for (cursor.y = Math.floor(queryBB.minY) - 1; cursor.y <= Math.floor(queryBB.maxY); cursor.y++) {
for (cursor.z = Math.floor(queryBB.minZ); cursor.z <= Math.floor(queryBB.maxZ); cursor.z++) {
for (cursor.x = Math.floor(queryBB.minX); cursor.x <= Math.floor(queryBB.maxX); cursor.x++) {
const block = world.getBlock(cursor)
if (block) {
const blockPos = block.position
for (const shape of block.shapes) {
const blockBB = new AABB(shape[0], shape[1], shape[2], shape[3], shape[4], shape[5])
blockBB.offset(blockPos.x, blockPos.y, blockPos.z)
surroundingBBs.push(blockBB)
}
}
}
}
}
return surroundingBBs
}
physics.adjustPositionHeight = (pos) => {
const playerBB = getPlayerBB(pos)
const queryBB = playerBB.clone().extend(0, -1, 0)
const surroundingBBs = getSurroundingBBs(world, queryBB)
let dy = -1
for (const blockBB of surroundingBBs) {
dy = blockBB.computeOffsetY(playerBB, dy)
}
pos.y += dy
}
function moveEntity (entity, world, dx, dy, dz) {
const vel = entity.vel
const pos = entity.pos
if (entity.isInWeb) {
dx *= 0.25
dy *= 0.05
dz *= 0.25
vel.x = 0
vel.y = 0
vel.z = 0
entity.isInWeb = false
}
let oldVelX = dx
const oldVelY = dy
let oldVelZ = dz
if (entity.control.sneak && entity.onGround) {
const step = 0.05
// In the 3 loops bellow, y offset should be -1, but that doesnt reproduce vanilla behavior.
for (; dx !== 0 && getSurroundingBBs(world, getPlayerBB(pos).offset(dx, 0, 0)).length === 0; oldVelX = dx) {
if (dx < step && dx >= -step) dx = 0
else if (dx > 0) dx -= step
else dx += step
}
for (; dz !== 0 && getSurroundingBBs(world, getPlayerBB(pos).offset(0, 0, dz)).length === 0; oldVelZ = dz) {
if (dz < step && dz >= -step) dz = 0
else if (dz > 0) dz -= step
else dz += step
}
while (dx !== 0 && dz !== 0 && getSurroundingBBs(world, getPlayerBB(pos).offset(dx, 0, dz)).length === 0) {
if (dx < step && dx >= -step) dx = 0
else if (dx > 0) dx -= step
else dx += step
if (dz < step && dz >= -step) dz = 0
else if (dz > 0) dz -= step
else dz += step
oldVelX = dx
oldVelZ = dz
}
}
let playerBB = getPlayerBB(pos)
const queryBB = playerBB.clone().extend(dx, dy, dz)
const surroundingBBs = getSurroundingBBs(world, queryBB)
const oldBB = playerBB.clone()
for (const blockBB of surroundingBBs) {
dy = blockBB.computeOffsetY(playerBB, dy)
}
playerBB.offset(0, dy, 0)
for (const blockBB of surroundingBBs) {
dx = blockBB.computeOffsetX(playerBB, dx)
}
playerBB.offset(dx, 0, 0)
for (const blockBB of surroundingBBs) {
dz = blockBB.computeOffsetZ(playerBB, dz)
}
playerBB.offset(0, 0, dz)
// Step on block if height < stepHeight
if (physics.stepHeight > 0 &&
(entity.onGround || (dy !== oldVelY && oldVelY < 0)) &&
(dx !== oldVelX || dz !== oldVelZ)) {
const oldVelXCol = dx
const oldVelYCol = dy
const oldVelZCol = dz
const oldBBCol = playerBB.clone()
dy = physics.stepHeight
const queryBB = oldBB.clone().extend(oldVelX, dy, oldVelZ)
const surroundingBBs = getSurroundingBBs(world, queryBB)
const BB1 = oldBB.clone()
const BB2 = oldBB.clone()
const BB_XZ = BB1.clone().extend(dx, 0, dz)
let dy1 = dy
let dy2 = dy
for (const blockBB of surroundingBBs) {
dy1 = blockBB.computeOffsetY(BB_XZ, dy1)
dy2 = blockBB.computeOffsetY(BB2, dy2)
}
BB1.offset(0, dy1, 0)
BB2.offset(0, dy2, 0)
let dx1 = oldVelX
let dx2 = oldVelX
for (const blockBB of surroundingBBs) {
dx1 = blockBB.computeOffsetX(BB1, dx1)
dx2 = blockBB.computeOffsetX(BB2, dx2)
}
BB1.offset(dx1, 0, 0)
BB2.offset(dx2, 0, 0)
let dz1 = oldVelZ
let dz2 = oldVelZ
for (const blockBB of surroundingBBs) {
dz1 = blockBB.computeOffsetZ(BB1, dz1)
dz2 = blockBB.computeOffsetZ(BB2, dz2)
}
BB1.offset(0, 0, dz1)
BB2.offset(0, 0, dz2)
const norm1 = dx1 * dx1 + dz1 * dz1
const norm2 = dx2 * dx2 + dz2 * dz2
if (norm1 > norm2) {
dx = dx1
dy = -dy1
dz = dz1
playerBB = BB1
} else {
dx = dx2
dy = -dy2
dz = dz2
playerBB = BB2
}
for (const blockBB of surroundingBBs) {
dy = blockBB.computeOffsetY(playerBB, dy)
}
playerBB.offset(0, dy, 0)
if (oldVelXCol * oldVelXCol + oldVelZCol * oldVelZCol >= dx * dx + dz * dz) {
dx = oldVelXCol
dy = oldVelYCol
dz = oldVelZCol
playerBB = oldBBCol
}
}
// Update flags
setPositionToBB(playerBB, pos)
entity.isCollidedHorizontally = dx !== oldVelX || dz !== oldVelZ
entity.isCollidedVertically = dy !== oldVelY
entity.onGround = entity.isCollidedVertically && oldVelY < 0
const blockAtFeet = world.getBlock(pos.offset(0, -0.2, 0))
if (dx !== oldVelX) vel.x = 0
if (dz !== oldVelZ) vel.z = 0
if (dy !== oldVelY) {
if (blockAtFeet && blockAtFeet.type === slimeBlockId && !entity.control.sneak) {
vel.y = -vel.y
} else {
vel.y = 0
}
}
// Finally, apply block collisions (web, soulsand...)
playerBB.contract(0.001, 0.001, 0.001)
const cursor = new Vec3(0, 0, 0)
for (cursor.y = Math.floor(playerBB.minY); cursor.y <= Math.floor(playerBB.maxY); cursor.y++) {
for (cursor.z = Math.floor(playerBB.minZ); cursor.z <= Math.floor(playerBB.maxZ); cursor.z++) {
for (cursor.x = Math.floor(playerBB.minX); cursor.x <= Math.floor(playerBB.maxX); cursor.x++) {
const block = world.getBlock(cursor)
if (block) {
if (supportFeature('velocityBlocksOnCollision')) {
if (block.type === soulsandId) {
vel.x *= physics.soulsandSpeed
vel.z *= physics.soulsandSpeed
} else if (block.type === honeyblockId) {
vel.x *= physics.honeyblockSpeed
vel.z *= physics.honeyblockSpeed
}
}
if (block.type === webId) {
entity.isInWeb = true
} else if (block.type === bubblecolumnId) {
const down = !block.metadata
const aboveBlock = world.getBlock(cursor.offset(0, 1, 0))
const bubbleDrag = (aboveBlock && aboveBlock.type === 0 /* air */) ? physics.bubbleColumnSurfaceDrag : physics.bubbleColumnDrag
if (down) {
vel.y = Math.max(bubbleDrag.maxDown, vel.y - bubbleDrag.down)
} else {
vel.y = Math.min(bubbleDrag.maxUp, vel.y + bubbleDrag.up)
}
}
}
}
}
}
if (supportFeature('velocityBlocksOnTop')) {
const blockBelow = world.getBlock(entity.pos.floored().offset(0, -0.5, 0))
if (blockBelow) {
if (blockBelow.type === soulsandId) {
vel.x *= physics.soulsandSpeed
vel.z *= physics.soulsandSpeed
} else if (blockBelow.type === honeyblockId) {
vel.x *= physics.honeyblockSpeed
vel.z *= physics.honeyblockSpeed
}
}
}
}
function getLookingVector (entity) {
// given a yaw pitch, we need the looking vector
// yaw is right handed rotation about y (up) starting from -z (north)
// pitch is -90 looking down, 90 looking up, 0 looking at horizon
// lets get its coordinate system.
// let x' = -z (north)
// let y' = -x (west)
// let z' = y (up)
// the non normalized looking vector in x', y', z' space is
// x' is cos(yaw)
// y' is sin(yaw)
// z' is tan(pitch)
// substituting back in x, y, z, we get the looking vector in the normal x, y, z space
// -z = cos(yaw) => z = -cos(yaw)
// -x = sin(yaw) => x = -sin(yaw)
// y = tan(pitch)
// normalizing the vectors, we divide each by |sqrt(x*x + y*y + z*z)|
// x*x + z*z = sin^2 + cos^2 = 1
// so |sqrt(xx+yy+zz)| = |sqrt(1+tan^2(pitch))|
// = |sqrt(1+sin^2(pitch)/cos^2(pitch))|
// = |sqrt((cos^2+sin^2)/cos^2(pitch))|
// = |sqrt(1/cos^2(pitch))|
// = |+/- 1/cos(pitch)|
// = 1/cos(pitch) since pitch in [-90, 90]
// the looking vector is therefore
// x = -sin(yaw) * cos(pitch)
// y = tan(pitch) * cos(pitch) = sin(pitch)
// z = -cos(yaw) * cos(pitch)
const yaw = entity.yaw
const pitch = entity.pitch
const sinYaw = Math.sin(yaw)
const cosYaw = Math.cos(yaw)
const sinPitch = Math.sin(pitch)
const cosPitch = Math.cos(pitch)
const lookX = -sinYaw * cosPitch
const lookY = sinPitch
const lookZ = -cosYaw * cosPitch
const lookDir = new Vec3(lookX, lookY, lookZ)
return {
yaw,
pitch,
sinYaw,
cosYaw,
sinPitch,
cosPitch,
lookX,
lookY,
lookZ,
lookDir
}
}
function applyHeading (entity, strafe, forward, multiplier) {
let speed = Math.sqrt(strafe * strafe + forward * forward)
if (speed < 0.01) return new Vec3(0, 0, 0)
speed = multiplier / Math.max(speed, 1)
strafe *= speed
forward *= speed
const yaw = Math.PI - entity.yaw
const sin = Math.sin(yaw)
const cos = Math.cos(yaw)
const vel = entity.vel
vel.x -= strafe * cos + forward * sin
vel.z += forward * cos - strafe * sin
}
function isOnLadder (world, pos) {
const block = world.getBlock(pos)
return (block && (block.type === ladderId || block.type === vineId))
}
function doesNotCollide (world, pos) {
const pBB = getPlayerBB(pos)
return !getSurroundingBBs(world, pBB).some(x => pBB.intersects(x)) && getWaterInBB(world, pBB).length === 0
}
function moveEntityWithHeading (entity, world, strafe, forward) {
const vel = entity.vel
const pos = entity.pos
const gravityMultiplier = (vel.y <= 0 && entity.slowFalling > 0) ? physics.slowFalling : 1
if (entity.isInWater || entity.isInLava) {
// Water / Lava movement
const lastY = pos.y
let acceleration = physics.liquidAcceleration
const inertia = entity.isInWater ? physics.waterInertia : physics.lavaInertia
let horizontalInertia = inertia
if (entity.isInWater) {
let strider = Math.min(entity.depthStrider, 3)
if (!entity.onGround) {
strider *= 0.5
}
if (strider > 0) {
horizontalInertia += (0.546 - horizontalInertia) * strider / 3
acceleration += (0.7 - acceleration) * strider / 3
}
if (entity.dolphinsGrace > 0) horizontalInertia = 0.96
}
applyHeading(entity, strafe, forward, acceleration)
moveEntity(entity, world, vel.x, vel.y, vel.z)
vel.y *= inertia
vel.y -= (entity.isInWater ? physics.waterGravity : physics.lavaGravity) * gravityMultiplier
vel.x *= horizontalInertia
vel.z *= horizontalInertia
if (entity.isCollidedHorizontally && doesNotCollide(world, pos.offset(vel.x, vel.y + 0.6 - pos.y + lastY, vel.z))) {
vel.y = physics.outOfLiquidImpulse // jump out of liquid
}
} else if (entity.elytraFlying) {
const {
pitch,
sinPitch,
cosPitch,
lookDir
} = getLookingVector(entity)
const horizontalSpeed = Math.sqrt(vel.x * vel.x + vel.z * vel.z)
const cosPitchSquared = cosPitch * cosPitch
vel.y += physics.gravity * gravityMultiplier * (-1.0 + cosPitchSquared * 0.75)
// cosPitch is in [0, 1], so cosPitch > 0.0 is just to protect against
// divide by zero errors
if (vel.y < 0.0 && cosPitch > 0.0) {
const movingDownSpeedModifier = vel.y * (-0.1) * cosPitchSquared
vel.x += lookDir.x * movingDownSpeedModifier / cosPitch
vel.y += movingDownSpeedModifier
vel.z += lookDir.z * movingDownSpeedModifier / cosPitch
}
if (pitch < 0.0 && cosPitch > 0.0) {
const lookDownSpeedModifier = horizontalSpeed * (-sinPitch) * 0.04
vel.x += -lookDir.x * lookDownSpeedModifier / cosPitch
vel.y += lookDownSpeedModifier * 3.2
vel.z += -lookDir.z * lookDownSpeedModifier / cosPitch
}
if (cosPitch > 0.0) {
vel.x += (lookDir.x / cosPitch * horizontalSpeed - vel.x) * 0.1
vel.z += (lookDir.z / cosPitch * horizontalSpeed - vel.z) * 0.1
}
vel.x *= 0.99
vel.y *= 0.98
vel.z *= 0.99
moveEntity(entity, world, vel.x, vel.y, vel.z)
if (entity.onGround) {
entity.elytraFlying = false
}
} else {
// Normal movement
let acceleration = 0.0
let inertia = 0.0
const blockUnder = world.getBlock(pos.offset(0, -1, 0))
if (entity.onGround && blockUnder) {
let playerSpeedAttribute
if (entity.attributes && entity.attributes[physics.movementSpeedAttribute]) {
// Use server-side player attributes
playerSpeedAttribute = entity.attributes[physics.movementSpeedAttribute]
} else {
// Create an attribute if the player does not have it
playerSpeedAttribute = attribute.createAttributeValue(physics.playerSpeed)
}
// Client-side sprinting (don't rely on server-side sprinting)
// setSprinting in LivingEntity.java
playerSpeedAttribute = attribute.deleteAttributeModifier(playerSpeedAttribute, physics.sprintingUUID) // always delete sprinting (if it exists)
if (entity.control.sprint) {
if (!attribute.checkAttributeModifier(playerSpeedAttribute, physics.sprintingUUID)) {
playerSpeedAttribute = attribute.addAttributeModifier(playerSpeedAttribute, {
uuid: physics.sprintingUUID,
amount: physics.sprintSpeed,
operation: 2
})
}
}
// Calculate what the speed is (0.1 if no modification)
const attributeSpeed = attribute.getAttributeValue(playerSpeedAttribute)
inertia = (blockSlipperiness[blockUnder.type] || physics.defaultSlipperiness) * 0.91
acceleration = attributeSpeed * (0.1627714 / (inertia * inertia * inertia))
if (acceleration < 0) acceleration = 0 // acceleration should not be negative
} else {
acceleration = physics.airborneAcceleration
inertia = physics.airborneInertia
if (entity.control.sprint) {
const airSprintFactor = physics.airborneAcceleration * 0.3
acceleration += airSprintFactor
}
}
applyHeading(entity, strafe, forward, acceleration)
if (isOnLadder(world, pos)) {
vel.x = math.clamp(-physics.ladderMaxSpeed, vel.x, physics.ladderMaxSpeed)
vel.z = math.clamp(-physics.ladderMaxSpeed, vel.z, physics.ladderMaxSpeed)
vel.y = Math.max(vel.y, entity.control.sneak ? 0 : -physics.ladderMaxSpeed)
}
moveEntity(entity, world, vel.x, vel.y, vel.z)
if (isOnLadder(world, pos) && (entity.isCollidedHorizontally ||
(supportFeature('climbUsingJump') && entity.control.jump))) {
vel.y = physics.ladderClimbSpeed // climb ladder
}
// Apply friction and gravity
if (entity.levitation > 0) {
vel.y += (0.05 * entity.levitation - vel.y) * 0.2
} else {
vel.y -= physics.gravity * gravityMultiplier
}
vel.y *= physics.airdrag
vel.x *= inertia
vel.z *= inertia
}
}
function isMaterialInBB (world, queryBB, types) {
const cursor = new Vec3(0, 0, 0)
for (cursor.y = Math.floor(queryBB.minY); cursor.y <= Math.floor(queryBB.maxY); cursor.y++) {
for (cursor.z = Math.floor(queryBB.minZ); cursor.z <= Math.floor(queryBB.maxZ); cursor.z++) {
for (cursor.x = Math.floor(queryBB.minX); cursor.x <= Math.floor(queryBB.maxX); cursor.x++) {
const block = world.getBlock(cursor)
if (block && types.includes(block.type)) return true
}
}
}
return false
}
function getLiquidHeightPcent (block) {
return (getRenderedDepth(block) + 1) / 9
}
function getRenderedDepth (block) {
if (!block) return -1
if (waterLike.has(block.type)) return 0
if (block.getProperties().waterlogged) return 0
if (!waterIds.includes(block.type)) return -1
const meta = block.metadata
return meta >= 8 ? 0 : meta
}
function getFlow (world, block) {
const curlevel = getRenderedDepth(block)
const flow = new Vec3(0, 0, 0)
for (const [dx, dz] of [[0, 1], [-1, 0], [0, -1], [1, 0]]) {
const adjBlock = world.getBlock(block.position.offset(dx, 0, dz))
const adjLevel = getRenderedDepth(adjBlock)
if (adjLevel < 0) {
if (adjBlock && adjBlock.boundingBox !== 'empty') {
const adjLevel = getRenderedDepth(world.getBlock(block.position.offset(dx, -1, dz)))
if (adjLevel >= 0) {
const f = adjLevel - (curlevel - 8)
flow.x += dx * f
flow.z += dz * f
}
}
} else {
const f = adjLevel - curlevel
flow.x += dx * f
flow.z += dz * f
}
}
if (block.metadata >= 8) {
for (const [dx, dz] of [[0, 1], [-1, 0], [0, -1], [1, 0]]) {
const adjBlock = world.getBlock(block.position.offset(dx, 0, dz))
const adjUpBlock = world.getBlock(block.position.offset(dx, 1, dz))
if ((adjBlock && adjBlock.boundingBox !== 'empty') || (adjUpBlock && adjUpBlock.boundingBox !== 'empty')) {
flow.normalize().translate(0, -6, 0)
}
}
}
return flow.normalize()
}
function getWaterInBB (world, bb) {
const waterBlocks = []
const cursor = new Vec3(0, 0, 0)
for (cursor.y = Math.floor(bb.minY); cursor.y <= Math.floor(bb.maxY); cursor.y++) {
for (cursor.z = Math.floor(bb.minZ); cursor.z <= Math.floor(bb.maxZ); cursor.z++) {
for (cursor.x = Math.floor(bb.minX); cursor.x <= Math.floor(bb.maxX); cursor.x++) {
const block = world.getBlock(cursor)
if (block && (waterIds.includes(block.type) || waterLike.has(block.type) || block.getProperties().waterlogged)) {
const waterLevel = cursor.y + 1 - getLiquidHeightPcent(block)
if (Math.ceil(bb.maxY) >= waterLevel) waterBlocks.push(block)
}
}
}
}
return waterBlocks
}
function isInWaterApplyCurrent (world, bb, vel) {
const acceleration = new Vec3(0, 0, 0)
const waterBlocks = getWaterInBB(world, bb)
const isInWater = waterBlocks.length > 0
for (const block of waterBlocks) {
const flow = getFlow(world, block)
acceleration.add(flow)
}
const len = acceleration.norm()
if (len > 0) {
vel.x += acceleration.x / len * 0.014
vel.y += acceleration.y / len * 0.014
vel.z += acceleration.z / len * 0.014
}
return isInWater
}
physics.simulatePlayer = (entity, world) => {
const vel = entity.vel
const pos = entity.pos
const waterBB = getPlayerBB(pos).contract(0.001, 0.401, 0.001)
const lavaBB = getPlayerBB(pos).contract(0.1, 0.4, 0.1)
entity.isInWater = isInWaterApplyCurrent(world, waterBB, vel)
entity.isInLava = isMaterialInBB(world, lavaBB, lavaIds)
// Reset velocity component if it falls under the threshold
if (Math.abs(vel.x) < physics.negligeableVelocity) vel.x = 0
if (Math.abs(vel.y) < physics.negligeableVelocity) vel.y = 0
if (Math.abs(vel.z) < physics.negligeableVelocity) vel.z = 0
// Handle inputs
if (entity.control.jump || entity.jumpQueued) {
if (entity.jumpTicks > 0) entity.jumpTicks--
if (entity.isInWater || entity.isInLava) {
vel.y += 0.04
} else if (entity.onGround && entity.jumpTicks === 0) {
const blockBelow = world.getBlock(entity.pos.floored().offset(0, -0.5, 0))
vel.y = Math.fround(0.42) * ((blockBelow && blockBelow.type === honeyblockId) ? physics.honeyblockJumpSpeed : 1)
if (entity.jumpBoost > 0) {
vel.y += 0.1 * entity.jumpBoost
}
if (entity.control.sprint) {
const yaw = Math.PI - entity.yaw
vel.x -= Math.sin(yaw) * 0.2
vel.z += Math.cos(yaw) * 0.2
}
entity.jumpTicks = physics.autojumpCooldown
}
} else {
entity.jumpTicks = 0 // reset autojump cooldown
}
entity.jumpQueued = false
let strafe = (entity.control.right - entity.control.left) * 0.98
let forward = (entity.control.forward - entity.control.back) * 0.98
if (entity.control.sneak) {
strafe *= physics.sneakSpeed
forward *= physics.sneakSpeed
}
entity.elytraFlying = entity.elytraFlying && entity.elytraEquipped && !entity.onGround && !entity.levitation
if (entity.fireworkRocketDuration > 0) {
if (!entity.elytraFlying) {
entity.fireworkRocketDuration = 0
} else {
const { lookDir } = getLookingVector(entity)
vel.x += lookDir.x * 0.1 + (lookDir.x * 1.5 - vel.x) * 0.5
vel.y += lookDir.y * 0.1 + (lookDir.y * 1.5 - vel.y) * 0.5
vel.z += lookDir.z * 0.1 + (lookDir.z * 1.5 - vel.z) * 0.5
--entity.fireworkRocketDuration
}
}
moveEntityWithHeading(entity, world, strafe, forward)
return entity
}
return physics
}
function getEffectLevel (mcData, effectName, effects) {
const effectDescriptor = mcData.effectsByName[effectName]
if (!effectDescriptor) {
return 0
}
const effectInfo = effects[effectDescriptor.id]
if (!effectInfo) {
return 0
}
return effectInfo.amplifier + 1
}
function getEnchantmentLevel (mcData, enchantmentName, enchantments) {
const enchantmentDescriptor = mcData.enchantmentsByName[enchantmentName]
if (!enchantmentDescriptor) {
return 0
}
for (const enchInfo of enchantments) {
if (typeof enchInfo.id === 'string') {
if (enchInfo.id.includes(enchantmentName)) {
return enchInfo.lvl
}
} else if (enchInfo.id === enchantmentDescriptor.id) {
return enchInfo.lvl
}
}
return 0
}
class PlayerState {
constructor (bot, control) {
const mcData = require('minecraft-data')(bot.version)
const nbt = require('prismarine-nbt')
// Input / Outputs
this.pos = bot.entity.position.clone()
this.vel = bot.entity.velocity.clone()
this.onGround = bot.entity.onGround
this.isInWater = bot.entity.isInWater
this.isInLava = bot.entity.isInLava
this.isInWeb = bot.entity.isInWeb
this.isCollidedHorizontally = bot.entity.isCollidedHorizontally
this.isCollidedVertically = bot.entity.isCollidedVertically
this.elytraFlying = bot.entity.elytraFlying
this.jumpTicks = bot.jumpTicks
this.jumpQueued = bot.jumpQueued
this.fireworkRocketDuration = bot.fireworkRocketDuration
// Input only (not modified)
this.attributes = bot.entity.attributes
this.yaw = bot.entity.yaw
this.pitch = bot.entity.pitch
this.control = control
// effects
const effects = bot.entity.effects
this.jumpBoost = getEffectLevel(mcData, 'JumpBoost', effects)
this.speed = getEffectLevel(mcData, 'Speed', effects)
this.slowness = getEffectLevel(mcData, 'Slowness', effects)
this.dolphinsGrace = getEffectLevel(mcData, 'DolphinsGrace', effects)
this.slowFalling = getEffectLevel(mcData, 'SlowFalling', effects)
this.levitation = getEffectLevel(mcData, 'Levitation', effects)
// armour enchantments
const boots = bot.inventory.slots[8]
if (boots && boots.nbt) {
const simplifiedNbt = nbt.simplify(boots.nbt)
const enchantments = simplifiedNbt.Enchantments ?? simplifiedNbt.ench ?? []
this.depthStrider = getEnchantmentLevel(mcData, 'depth_strider', enchantments)
} else {
this.depthStrider = 0
}
// extra elytra requirements
const item = bot.inventory.slots[6]
this.elytraEquipped = item != null && item.name === 'elytra'
}
apply (bot) {
bot.entity.position = this.pos
bot.entity.velocity = this.vel
bot.entity.onGround = this.onGround
bot.entity.isInWater = this.isInWater
bot.entity.isInLava = this.isInLava
bot.entity.isInWeb = this.isInWeb
bot.entity.isCollidedHorizontally = this.isCollidedHorizontally
bot.entity.isCollidedVertically = this.isCollidedVertically
bot.entity.elytraFlying = this.elytraFlying
bot.jumpTicks = this.jumpTicks
bot.jumpQueued = this.jumpQueued
bot.fireworkRocketDuration = this.fireworkRocketDuration
}
}
module.exports = { Physics, PlayerState }