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 }