@nxg-org/mineflayer-physics-util
Version:
Provides functionality for more accurate entity and projectile tracking.
1,042 lines (1,041 loc) • 76.7 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.BotcraftPhysics = void 0;
const mineflayer_util_plugin_1 = require("@nxg-org/mineflayer-util-plugin");
const vec3_1 = require("vec3");
const physicsUtils_1 = require("../../util/physicsUtils");
const math = __importStar(require("../info/math"));
const attributes = __importStar(require("../info/attributes"));
const states_1 = require("../states");
function extractAttribute(ctx, genericName) {
const data = ctx.data.attributesByName[genericName];
if (data == null)
return null;
if (ctx.supportFeature("attributesPrefixedByMinecraft")) {
return `minecraft:${data.resource}`;
}
else {
return data.resource;
}
}
/**
* Looking at this code, it's too specified towards players.
*
* I will eventually split this code into PlayerState and bot.entityState, where bot.entityState contains fewer controls.
*/
class BotcraftPhysics {
constructor(mcData) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
this.data = mcData;
const blocksByName = mcData.blocksByName;
this.supportFeature = (0, physicsUtils_1.makeSupportFeature)(mcData);
this.movementSpeedAttribute = extractAttribute(this, "movementSpeed");
this.movementEfficiencyAttribute = extractAttribute(this, "movementEfficiency");
this.jumpStrengthAttribute = extractAttribute(this, "jumpStrength");
this.waterMovementEfficiencyAttribute = extractAttribute(this, "waterMovementEfficiency");
this.stepHeightAttribute = extractAttribute(this, "stepHeight");
this.blockSlipperiness = {};
this.slimeBlockId = blocksByName.slime_block ? blocksByName.slime_block.id : blocksByName.slime.id;
this.blockSlipperiness[this.slimeBlockId] = 0.8;
this.blockSlipperiness[blocksByName.ice.id] = 0.98;
this.blockSlipperiness[blocksByName.packed_ice.id] = 0.98;
// 1.9+
if (blocksByName.frosted_ice)
this.blockSlipperiness[blocksByName.frosted_ice.id] = 0.98;
// 1.13+
if (blocksByName.blue_ice)
this.blockSlipperiness[blocksByName.blue_ice.id] = 0.989;
this.bedId = (_d = (_b = (_a = blocksByName.bed) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : (_c = blocksByName.white_bed) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : -1;
this.soulsandId = blocksByName.soul_sand.id;
this.scaffoldId = (_f = (_e = blocksByName.scaffolding) === null || _e === void 0 ? void 0 : _e.id) !== null && _f !== void 0 ? _f : -1; // 1.14+
this.berryBushId = (_h = (_g = blocksByName.sweet_berry_bush) === null || _g === void 0 ? void 0 : _g.id) !== null && _h !== void 0 ? _h : -1; // 1.14+
this.powderSnowId = (_k = (_j = blocksByName.powder_snow) === null || _j === void 0 ? void 0 : _j.id) !== null && _k !== void 0 ? _k : -1;
this.honeyblockId = blocksByName.honey_block ? blocksByName.honey_block.id : -1; // 1.15+
this.webId = blocksByName.cobweb ? blocksByName.cobweb.id : blocksByName.web.id;
this.waterId = blocksByName.water.id;
this.lavaId = blocksByName.lava.id;
this.ladderId = blocksByName.ladder.id;
this.vineId = blocksByName.vine.id;
this.waterLike = new Set();
if (blocksByName.seagrass)
this.waterLike.add(blocksByName.seagrass.id); // 1.13+
if (blocksByName.tall_seagrass)
this.waterLike.add(blocksByName.tall_seagrass.id); // 1.13+
if (blocksByName.kelp)
this.waterLike.add(blocksByName.kelp.id); // 1.13+
if (blocksByName.kelp_plant)
this.waterLike.add(blocksByName.kelp_plant.id); // 1.13+
this.bubblecolumnId = blocksByName.bubble_column ? blocksByName.bubble_column.id : -1; // 1.13+
if (blocksByName.bubble_column)
this.waterLike.add(this.bubblecolumnId);
this.statusEffectNames = {}; // mmm, speed.
this.enchantmentNames = {}; //mmm, double speed.
let ind = 0;
const tmp = (0, physicsUtils_1.getStatusEffectNamesForVersion)(this.supportFeature);
for (const key in tmp) {
this.statusEffectNames[ind] = tmp[key];
ind++;
}
Object.freeze(this.statusEffectNames);
ind = 0;
const tmp1 = (0, physicsUtils_1.getEnchantmentNamesForVersion)(this.supportFeature);
for (const key in tmp1) {
this.enchantmentNames[ind] = tmp1[key];
}
Object.freeze(this.enchantmentNames);
}
getEntityBB(entity, pos) {
const w = entity.getHalfWidth();
return new mineflayer_util_plugin_1.AABB(-w, 0, -w, w, entity.height, w).translate(pos.x, pos.y, pos.z);
}
setPositionToBB(entity, bb, pos) {
const halfWidth = entity.getHalfWidth();
pos.x = bb.minX + halfWidth;
pos.y = bb.minY;
pos.z = bb.minZ + halfWidth;
}
getSurroundingBBs(queryBB, world, underlying = true) {
const surroundingBBs = [];
const cursor = new vec3_1.Vec3(0, 0, 0);
for (cursor.y = Math.floor(queryBB.minY) - Number(underlying); 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 != null) {
const blockPos = block.position;
for (const shape of block.shapes) {
const blockBB = new mineflayer_util_plugin_1.AABB(shape[0], shape[1], shape[2], shape[3], shape[4], shape[5]);
blockBB.translate(blockPos.x, blockPos.y, blockPos.z);
surroundingBBs.push(blockBB);
}
}
}
}
}
return surroundingBBs;
}
getWaterInBBs(bb, world) {
const bbs = [];
const cursor = new vec3_1.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 && (block.type === this.waterId || this.waterLike.has(block.type) || block.getProperties().waterlogged)) {
const waterLevel = cursor.y + 1 - this.getLiquidHeightPcent(block);
if (Math.ceil(bb.maxY) >= waterLevel) {
bbs.push(new mineflayer_util_plugin_1.AABB(cursor.x, cursor.y, cursor.z, cursor.x + 1, cursor.y + 1, cursor.z + 1));
}
}
}
}
}
return bbs;
}
getEffectLevel(wantedEffect, effects) {
const effectDescriptor = this.data.effectsByName[this.statusEffectNames[wantedEffect]];
if (!effectDescriptor) {
return 0;
}
const effectInfo = effects[effectDescriptor.id];
if (!effectInfo) {
return 0;
}
return effectInfo.amplifier + 1;
}
getEnchantmentLevel(wantedEnchantment, enchantments) {
const enchantmentName = this.enchantmentNames[wantedEnchantment];
const enchantmentDescriptor = this.data.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;
}
verGreaterThan(ver) {
return this.data.version[">"](ver);
}
verLessThan(ver) {
return this.data.version["<"](ver);
}
worldIsFree(world, bb, ignoreLiquid) {
const bbs = ignoreLiquid ? this.getSurroundingBBs(bb, world, false) : [...this.getSurroundingBBs(bb, world, false), ...this.getWaterInBBs(bb, world)];
// now we have to actually check for collisions.
for (const blockBB of bbs) {
if (blockBB.intersects(bb)) {
const blockAt = world.getBlock(blockBB.minPoint());
// console.log('world not free due to block: ', blockAt.name, blockAt.position)
return false;
}
}
return true;
}
/**
* 1:1 copy of the original physicsTick function from botcraft
* https://github.com/adepierre/Botcraft/blob/6c572071b0237c27a85211a246ce10565ef4d80f/botcraft/src/Game/Physics/PhysicsManager.cpp#L277
*
*
* @param ctx
* @param world
*/
physicsTick(ctx, world) {
// Check for rocket boosting if currently in elytra flying mode
const entity = ctx.state;
// if (ctx.state.elytraFlying) {
// // TODO: entity check for fireworks
// // TODO: check if firework is attached to player
// if (false) {
// // player->speed += player->front_vector * 0.1 + (player->front_vector * 1.5 - player->speed) * 0.5;
// }
// }
// for now, only check if this is a player.
const playerFlag = ctx.entityType.type === "player";
this.fluidPhysics(ctx, world, true);
this.fluidPhysics(ctx, world, false);
// updateSwimming moved into AiStep.
// separation into a new function
// originally: https://github.com/adepierre/Botcraft/blob/6c572071b0237c27a85211a246ce10565ef4d80f/botcraft/src/Game/Physics/PhysicsManager.cpp#L325
if (playerFlag) {
this.localPlayerAIStep(ctx, world);
}
}
/**
* Assume later than 1.13.2 before calling this function.
* @param player
* @param world
*/
updatePoses(ctx, world) {
const player = ctx.state;
const swimPose = (0, states_1.getCollider)(states_1.PlayerPoses.SWIMMING, player.pos).expand(-1e-7, -1e-7, -1e-7);
if (this.worldIsFree(world, swimPose, false)) {
// update poses
let currentPose;
// player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying)
if (player.fallFlying) {
currentPose = states_1.PlayerPoses.FALL_FLYING;
}
// (player->GetSleepingPosIdImpl()
// this is based on metadata which I currently do not have access to.
else if (player.pose === states_1.PlayerPoses.SLEEPING) {
currentPose = states_1.PlayerPoses.SLEEPING;
}
else if (this.isSwimmingAndNotFlying(ctx, world)) {
currentPose = states_1.PlayerPoses.SWIMMING;
}
// player->GetDataLivingEntityFlagsImpl() & 0x04
// no clue.
else if (false) {
currentPose = states_1.PlayerPoses.SPIN_ATTACK;
}
else if (player.control.sneak && !player.flying) {
currentPose = states_1.PlayerPoses.SNEAKING;
}
else {
currentPose = states_1.PlayerPoses.STANDING;
}
const poseBB = (0, states_1.getCollider)(currentPose, player.pos).expand(-1e-7, -1e-7, -1e-7);
if (player.gameMode === "spectator" || this.worldIsFree(world, poseBB, false)) {
player.pose = currentPose;
}
else {
const crouchBB = (0, states_1.getCollider)(states_1.PlayerPoses.SNEAKING, player.pos).expand(-1e-7, -1e-7, -1e-7);
if (this.worldIsFree(world, crouchBB, false)) {
player.pose = states_1.PlayerPoses.SNEAKING;
}
else {
player.pose = states_1.PlayerPoses.SWIMMING;
}
}
}
}
fluidPhysics(ctx, world, water) {
const player = ctx.state;
const aabb = (0, states_1.getCollider)(player.pose, player.pos).expand(-1e-3, -1e-3, -1e-3); // -0.001
if (water) {
player.isInWater = false;
player.isUnderWater = false;
}
else {
player.isInLava = false;
player.isUnderLava = false;
}
const minAABB = aabb.minPoint();
const maxAABB = aabb.maxPoint();
const eyeHeight = player.eyeHeight + player.pos.y;
const waterCond = (block) => block.type === this.waterId || this.waterLike.has(block.type) || block.getProperties().waterlogged;
const lavaCond = (block) => block.type === this.lavaId;
const push = new vec3_1.Vec3(0, 0, 0);
const blockPos = new vec3_1.Vec3(0, 0, 0);
let fluidRelativeHeight = 0.0;
let numPush = 0;
for (blockPos.x = Math.floor(minAABB.x); blockPos.x <= Math.floor(maxAABB.x); ++blockPos.x) {
for (blockPos.y = Math.floor(minAABB.y); blockPos.y <= Math.floor(maxAABB.y); ++blockPos.y) {
for (blockPos.z = Math.floor(minAABB.z); blockPos.z <= Math.floor(maxAABB.z); ++blockPos.z) {
const block = world.getBlock(blockPos);
if (block == null)
continue;
const waterRes = waterCond(block);
const lavaRes = lavaCond(block);
if ((waterRes && !water) || (lavaRes && water) || (!waterRes && !lavaRes)) {
continue;
}
let fluidHeight = 0.0;
const blockAbv = world.getBlock(blockPos.offset(0, 1, 0));
if ((blockAbv != null && waterCond(blockAbv) && waterRes) || (lavaCond(blockAbv) && lavaRes)) {
fluidHeight = 1.0;
}
else {
fluidHeight = this.getLiquidHeightPcent(block);
}
if (fluidHeight + blockPos.y < minAABB.y) {
continue;
}
if (water) {
player.isInWater = true;
if (fluidHeight + blockPos.y > eyeHeight) {
player.isUnderWater = true;
}
}
else {
player.isInLava = true;
if (fluidHeight + blockPos.y > eyeHeight) {
player.isUnderLava = true;
}
}
fluidRelativeHeight = Math.max(fluidHeight + blockPos.y - minAABB.y, fluidRelativeHeight);
if (player.flying)
continue;
const currentPush = this.getFlow(block, world);
if (fluidRelativeHeight < 0.4) {
currentPush.scale(fluidRelativeHeight);
}
push.add(currentPush);
numPush++;
}
}
}
if (push.norm() > 0.0) {
if (numPush > 0) {
push.scale(1.0 / numPush);
}
if (water) {
push.scale(0.014);
}
else {
const worldInUltraWarm = false; // TODO: implement this (bot world relevance)
push.scale(worldInUltraWarm ? 0.007 : 0.0023333333333333335);
}
}
const pushNorm = push.norm();
if (Math.abs(player.vel.x) < ctx.worldSettings.negligeableVelocity &&
Math.abs(player.vel.z) < ctx.worldSettings.negligeableVelocity &&
pushNorm < 0.0045000000000000005) {
// normalize and scale
push.normalize().scale(0.0045000000000000005);
}
player.vel.add(push);
}
getFlow(block, world) {
const curlevel = this.getRenderedDepth(block);
const flow = new vec3_1.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 = this.getRenderedDepth(adjBlock);
if (adjLevel < 0) {
if (adjBlock && adjBlock.boundingBox !== "empty") {
const adjLevel = this.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();
}
getLiquidHeightPcent(block) {
return 1 - (this.getRenderedDepth(block) + 1) / 9;
}
getRenderedDepth(block) {
if (!block)
return -1;
if (this.waterLike.has(block.type))
return 0;
if (block.getProperties().waterlogged)
return 0;
if (block.type !== this.waterId)
return -1;
const meta = block.metadata;
return meta >= 8 ? 0 : meta;
}
updateSwimming(player, world) {
if (player.flying) {
player.swimming = false;
}
else if (player.swimming) {
player.swimming = player.sprinting && player.isInWater;
}
else {
const block = world.getBlock(player.pos);
player.swimming =
player.sprinting &&
player.isUnderWater &&
block != null &&
!!(block.type === this.waterId || this.waterLike.has(block.type) || block.getProperties().waterlogged);
}
}
isMovingSlowly(ctx, world) {
const player = ctx.state;
if (this.verGreaterThan("1.13.2")) {
const visuallyCrawling = (player.pose === states_1.PlayerPoses.SWIMMING || (!player.fallFlying && player.pose === states_1.PlayerPoses.FALL_FLYING)) &&
!player.isInWater;
return player.crouching || visuallyCrawling;
}
return player.crouching;
}
localPlayerAIStep(ctx, world) {
const player = ctx.state;
const tickStartedOnGround = player.onGround;
const heading = (0, states_1.convInpToAxes)(player);
player.heading = heading;
// moved into AiStep since it's tied to player behavior. Strictly, is Player::updateSwimming.
this.updateSwimming(player, world);
this.inputsToCrouch(ctx, heading, world);
this.inputsToSprint(ctx, heading, world);
this.inputsToFly(ctx, heading, world);
// If sneaking in water, add downward speed
if (player.isInWater && player.control.sneak && !player.flying) {
player.vel.y -= 0.03999999910593033;
}
if (player.flying) {
player.vel.y +=
(-1 * player.control.sneak + player.control.jump) * player.flySpeed * 3.0;
}
// player::AiStep
{
player.flyJumpTriggerTime = Math.max(0, player.flyJumpTriggerTime - 1);
}
// livingEntity::AiStep
{
player.jumpTicks = Math.max(0, player.jumpTicks - 1);
if (Math.abs(player.vel.x) < ctx.worldSettings.negligeableVelocity) {
player.vel.x = 0;
}
if (Math.abs(player.vel.y) < ctx.worldSettings.negligeableVelocity) {
player.vel.y = 0;
}
if (Math.abs(player.vel.z) < ctx.worldSettings.negligeableVelocity) {
player.vel.z = 0;
}
this.inputsToJump(player, world, ctx.worldSettings);
// TODO: properly implement heading handler. forward-axis is forward, left-axis = strafe. weird naming.
heading.forward *= 0.98;
heading.strafe *= 0.98;
// player->inputs.forward_axis *= 0.98f;
// player->inputs.left_axis *= 0.98f;
// Compensate water downward speed depending on looking direction (?)
if (this.isSwimmingAndNotFlying(ctx, world)) {
const mSinPitch = player.pitch;
let condition = mSinPitch < 0.0 || player.control.jump;
if (!condition) {
// check above block
const bl1 = world.getBlock(new vec3_1.Vec3(player.pos.x, player.pos.y + 1.0 - 0.1, player.pos.z));
condition = bl1 != null && (this.waterId === bl1.type || this.waterLike.has(bl1.type));
}
if (condition) {
// console.log('changing vel by', (mSinPitch - player.vel.y) * (mSinPitch < -0.2 ? 0.085 : 0.06));
player.vel.y += (mSinPitch - player.vel.y) * (mSinPitch < -0.2 ? 0.085 : 0.06);
}
}
// Refresh pose before movement so the first fall-flying tick uses the
// correct collider instead of the standing bounding box.
if (this.verGreaterThan("1.13.2")) {
this.updatePoses(ctx, world);
}
const velY = player.vel.y;
this.movePlayer(ctx, world); // TODO: should be in player-specific logic??
if (player.flying) {
player.vel.y = 0.6 * velY;
/* player->SetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying, false); */ player.fallFlying = false;
}
player.onClimbable = this.isInClimbable(player, world);
} // !livingplayer::AiStep
if (player.onGround && player.flying && player.gameMode !== "spectator") {
player.flying = false;
// onUpdateAbilities();
}
// Gen: pretty sure mobs can't spawn or exist outside world borders. this should be fine in here.
player.pos.x = math.clamp(-2.9999999e7, player.pos.x, 2.9999999e7);
player.pos.z = math.clamp(-2.9999999e7, player.pos.z, 2.9999999e7);
if (this.verGreaterThan("1.13.2")) {
this.updatePoses(ctx, world);
}
// Preserve the current inputs for the next tick after all previous-state
// consumers in this tick have already read the prior values.
player.lastOnGround = player.onGround;
player.prevHeading.forward = heading.forward;
player.prevHeading.strafe = heading.strafe;
player.prevControl.jump = player.control.jump;
player.prevControl.sneak = player.control.sneak;
}
inputsToCrouch(ctx, heading, world) {
var _a, _b, _c;
const player = ctx.state;
if (this.verGreaterThan("1.13.2")) {
const sneakBb = (0, states_1.getCollider)(states_1.PlayerPoses.SNEAKING, player.pos);
const standBb = (0, states_1.getCollider)(states_1.PlayerPoses.STANDING, player.pos);
sneakBb.expand(-1e-7, -1e-7, -1e-7);
standBb.expand(-1e-7, -1e-7, -1e-7);
player.crouching =
!this.isSwimmingAndNotFlying(ctx, world) &&
this.worldIsFree(world, sneakBb, false) &&
(player.prevControl.sneak || !this.worldIsFree(world, standBb, false));
}
else {
player.crouching = !this.isSwimmingAndNotFlying(ctx, world) && player.prevControl.sneak;
}
// Determine if moving slowly
const isMovingSlowly = this.isMovingSlowly(ctx, world);
// Handle post-1.21.3 sprinting conditions
if (this.verGreaterThan("1.21.3")) {
// TODO: just use stored blindness effect.
const hasBlindness = this.getEffectLevel(physicsUtils_1.CheapEffects.BLINDNESS, player.effects) > 0;
// Stop sprinting when crouching fix in 1.21.4+
if (hasBlindness || isMovingSlowly) {
this.setSprinting(ctx, false);
}
}
// Apply slow down to player inputs when moving slowly
if (isMovingSlowly) {
let sneakCoefficient;
if (this.verLessThan("1.19")) {
sneakCoefficient = 0.3;
}
else if (this.verLessThan("1.21")) {
sneakCoefficient = 0.3 + player.swiftSneak * 0.15;
sneakCoefficient = Math.min(Math.max(0.0, sneakCoefficient), 1.0);
}
else {
sneakCoefficient = (_c = (_b = (_a = player.attributes) === null || _a === void 0 ? void 0 : _a.sneakingSpeed) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : 0.3; // TODO: this shouldn't be happening!
}
heading.forward *= sneakCoefficient;
heading.strafe *= sneakCoefficient;
}
}
isSwimmingAndNotFlying(ctx, world) {
const entity = ctx.state;
// return !player->flying &&
// player->game_mode != GameType::Spectator &&
// player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::Swimming);
if (entity instanceof states_1.PlayerState) {
return !entity.flying && entity.gameMode !== "spectator" && entity.swimming;
}
else {
return false; // TODO: proper handling of non-player mobs.
}
}
inputsToSprint(ctx, heading, world) {
const player = ctx.state;
// console.log('is sprinting', player.sprinting, 'can sprint', this.canStartSprinting(ctx, heading), player.sprinting)
if (this.canStartSprinting(ctx, heading) &&
(player.control.sprint || (player.sprintTriggerTime > 0 && heading.forward >= (player.isInWater ? 1e-5 : 0.8)))) {
this.setSprinting(ctx, true);
}
// console.log('should stop', this.shouldStopRunSprinting(ctx, heading), 'minor collision', player.isCollidedHorizontallyMinor)
// Stop sprinting if necessary
if (player.sprinting) {
if (!player.control.sprint) {
this.setSprinting(ctx, false);
}
if (this.isSwimming(ctx)) {
if (this.shouldStopSwimSprinting(ctx, heading)) {
this.setSprinting(ctx, false);
}
}
else if (this.shouldStopRunSprinting(ctx, heading)) {
this.setSprinting(ctx, false);
}
}
}
canStartSprinting(ctx, heading) {
const player = ctx.state;
return !player.sprinting &&
heading.forward >= (player.isInWater ? 1e-5 : 0.8) &&
this.hasEnoughFoodToSprint(ctx) &&
!player.isUsingItem &&
!player.blindness &&
// (!player.isPassenger || this.vehicleCanSprint(ctx)) &&
(!player.fallFlying || player.isUnderWater) &&
(!player.crouching || player.isUnderWater) &&
(!player.isInWater || player.isUnderWater);
}
hasEnoughFoodToSprint(ctx) {
const player = ctx.state;
return /* player.isPassenger ||*/ player.food > 6 || player.mayFly;
}
vehicleCanSprint(ctx) {
return false;
// const player = ctx.state as PlayerState;
// return player.vehicle &&
// player.vehicle.canSprint &&
// player.vehicle.isLocalInstanceAuthoritative;
}
isSwimming(ctx) {
const player = ctx.state;
return player.isInWater && player.isUnderWater;
}
handleFallFlyingCollisions(player, previousHorizontalSpeed) {
if (!player.isCollidedHorizontally)
return;
const currentHorizontalSpeed = Math.sqrt(player.vel.x * player.vel.x + player.vel.z * player.vel.z);
const deltaSpeed = previousHorizontalSpeed - currentHorizontalSpeed;
const collisionDamage = deltaSpeed * 10.0 - 3.0;
if (collisionDamage > 0.0) {
// Vanilla applies fly-into-wall damage and sound here.
// BotcraftPhysics currently does not model those side effects.
}
}
applyFireworkBoost(player) {
if (player.fireworkRocketDuration <= 0)
return;
if (!player.fallFlying) {
player.fireworkRocketDuration = 0;
return;
}
const { lookDir } = (0, physicsUtils_1.getLookingVector)(player);
player.vel.x += lookDir.x * 0.1 + (lookDir.x * 1.5 - player.vel.x) * 0.5;
player.vel.y += lookDir.y * 0.1 + (lookDir.y * 1.5 - player.vel.y) * 0.5;
player.vel.z += lookDir.z * 0.1 + (lookDir.z * 1.5 - player.vel.z) * 0.5;
--player.fireworkRocketDuration;
}
shouldStopRunSprinting(ctx, heading) {
const player = ctx.state;
return player.blindness > 0 ||
// (player.isPassenger && !this.vehicleCanSprint(ctx)) ||
heading.forward < 1e-5 ||
!this.hasEnoughFoodToSprint(ctx) ||
(player.isCollidedHorizontally && !player.isCollidedHorizontallyMinor) ||
(player.isInWater && !player.isUnderWater);
}
shouldStopSwimSprinting(ctx, heading) {
const player = ctx.state;
return player.blindness > 0 ||
// (player.isPassenger && !this.vehicleCanSprint(ctx)) ||
!player.isInWater ||
(heading.forward <= 1e-5 && !player.onGround && !player.control.sneak) ||
!this.hasEnoughFoodToSprint(ctx);
}
/**
* TODO: almost certainly unfinished.
* @param player
* @param value
*/
setSprinting(ctx, value) {
const player = ctx.state;
let attr = player.attributes[this.movementSpeedAttribute];
if (attr != null)
attributes.deleteAttributeModifier(attr, ctx.worldSettings.sprintingUUID);
attr = attributes.createAttributeValue(ctx.worldSettings.playerSpeed);
if (value) {
attributes.addAttributeModifier(attr, {
uuid: ctx.worldSettings.sprintingUUID,
amount: ctx.worldSettings.sprintSpeed,
operation: 2,
});
}
// potential deviation from vanilla.
// if (value != player.sprinting) {
// player.sprintTriggerTime = ctx.worldSettings.sprintTimeTriggerCooldown;
// }
player.attributes[this.movementSpeedAttribute] = attr;
player.sprinting = value;
}
inputsToFly(ctx, heading, world) {
const player = ctx.state;
let flyChanged = false;
if (player.mayFly) {
// Auto trigger if in spectator mode
if (player.gameMode === "spectator") {
player.flying = true;
flyChanged = true;
// onUpdateAbilities();
}
else {
// If double jump in creative, swap flying mode
if (player.prevControl.jump && player.control.jump) {
if (player.flyJumpTriggerTime === 0) {
player.flyJumpTriggerTime = ctx.worldSettings.flyJumpTriggerCooldown;
}
else if (this.isSwimmingAndNotFlying(ctx, world)) {
player.flying = !player.flying;
flyChanged = true;
// onUpdateAbilities();
player.flyJumpTriggerTime = 0;
}
}
}
}
const hasLevitationEffect = player.levitation > 0;
if (player.control.jump &&
!flyChanged &&
!player.prevControl.jump &&
!player.flying &&
!player.onClimbable &&
!player.onGround &&
!player.isInWater &&
/* !player->GetDataSharedFlagsIdImpl(EntitySharedFlagsId::FallFlying) && */ !player.fallFlying &&
!hasLevitationEffect) {
const hasElytra = player.validElytraEquipped;
if (hasElytra) {
player.fallFlying = true;
// https://github.com/adepierre/Botcraft/blob/6c572071b0237c27a85211a246ce10565ef4d80f/botcraft/src/Game/Physics/PhysicsManager.cpp#L792
// something about setting actions?
}
}
}
inputsToJump(entity, world, worldSettings) {
// TODO: implement non-player entity jumping.
var _a, _b;
if (entity instanceof states_1.PlayerState) {
const player = entity;
if (!player.control.jump) {
player.jumpTicks = 0;
return;
}
if (player.flying) {
return;
}
if (player.isInWater || player.isInLava) {
player.vel.y += 0.03999999910593033; // magic number
}
else if (player.onGround && player.jumpTicks === 0) {
const blockJumpFactor = this.getBlockJumpFactor(player, world, worldSettings);
const jumpBoost = Math.fround(0.1 * player.jumpBoost); // in mineflayer, level 1 is 1, not 0.
if (this.verLessThan("1.20.5")) {
const jumpPower = Math.fround(Math.fround(0.42) * Math.fround(blockJumpFactor) + jumpBoost);
player.vel.y = Math.max(jumpPower, player.vel.y);
if (player.sprinting) {
const yawRad = Math.PI - player.yaw; // should already be in yaw. MINEFLAYER SPECIFC CHANGE, MATH.PI -
// potential inconsistency here. This may not be accurate.
const offsetX = Math.fround(Math.sin(yawRad)) * 0.2;
const offsetZ = Math.fround(Math.cos(yawRad)) * 0.2;
player.vel.x -= offsetX;
player.vel.z += offsetZ;
}
}
else {
const value = Math.fround((_b = (_a = entity.attributes[this.jumpStrengthAttribute]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : Math.fround(0.42));
const jumpPower = Math.fround(value * Math.fround(blockJumpFactor) + jumpBoost);
if (jumpPower > 1e-5) {
player.vel.y = Math.max(jumpPower, player.vel.y);
if (player.sprinting) {
const yawRad = Math.PI - player.yaw; // should already be in yaw. MINEFLAYER SPECIFC CHANGE, MATH.PI -
player.vel.x -= Math.sin(yawRad) * 0.2;
player.vel.z += Math.cos(yawRad) * 0.2;
}
}
}
player.jumpTicks = worldSettings.autojumpCooldown;
}
}
}
getBlockJumpFactor(entity, world, worldSettings) {
const feetBlock = world.getBlock(new vec3_1.Vec3(entity.pos.x, entity.pos.y, entity.pos.z));
const feetJumpFactor = this.getKnownJumpFactor(feetBlock === null || feetBlock === void 0 ? void 0 : feetBlock.type, worldSettings);
if (feetJumpFactor !== 1.0)
return feetJumpFactor;
const supportBlock = world.getBlock(this.getBlockBelowAffectingMovement(entity, world));
return this.getKnownJumpFactor(supportBlock === null || supportBlock === void 0 ? void 0 : supportBlock.type, worldSettings);
}
getKnownJumpFactor(blockType, worldSettings) {
if (blockType == null)
return 1.0;
if (blockType === this.honeyblockId)
return worldSettings.honeyblockJumpSpeed;
return 1.0;
}
getOffGroundSpeed(player) {
if (player.flying) {
return player.sprinting ? player.flySpeed * 2.0 : player.flySpeed;
}
return player.sprinting ? Math.fround(0.025999999) : Math.fround(0.02);
}
getBlockBelowAffectingMovement(entity, world) {
if (entity.supportingBlockPos != null) {
return entity.supportingBlockPos; //.offset(0, -0.500001, 0);
}
else {
return entity.pos.offset(0, -0.500001, 0);
}
}
/**
* Taken from original physics impl.
* @param entity
* @returns
*/
getMovementSpeedAttribute(entity) {
const isSprinting = entity.state instanceof states_1.PlayerState && entity.state.sprinting;
let attribute;
// In 1.21+, this should map to 'minecraft:movement_speed'
if (entity.state.attributes && entity.state.attributes[this.movementSpeedAttribute]) {
attribute = entity.state.attributes[this.movementSpeedAttribute];
}
else {
// Create fallback if server hasn't sent it
attribute = attributes.createAttributeValue(Math.fround(entity.worldSettings.playerSpeed));
}
// --- SPRINTING ---
attribute = attributes.deleteAttributeModifier(attribute, entity.worldSettings.sprintingUUID);
if (isSprinting) {
attribute = attributes.addAttributeModifier(attribute, {
uuid: entity.worldSettings.sprintingUUID,
// Math.fround(0.3) perfectly resolves to 0.30000001192092896
amount: Math.fround(0.3),
operation: 2,
});
}
// --- SPEED EFFECT ---
const SPEED_UUID = "91AEAA56-376B-4498-935B-2F7F68070635";
attribute = attributes.deleteAttributeModifier(attribute, SPEED_UUID);
const speedLevel = this.getEffectLevel(physicsUtils_1.CheapEffects.SPEED, entity.state.effects);
if (speedLevel > 0) {
attribute = attributes.addAttributeModifier(attribute, {
uuid: SPEED_UUID,
// Truncate the 0.2 first, then multiply, then truncate again (how Java float math works)
amount: Math.fround(Math.fround(0.2) * speedLevel),
operation: 2,
});
}
// --- SLOWNESS EFFECT ---
const SLOWNESS_UUID = "7107DE5E-7CE8-4030-940E-514C1F160890";
attribute = attributes.deleteAttributeModifier(attribute, SLOWNESS_UUID);
const slownessLevel = this.getEffectLevel(physicsUtils_1.CheapEffects.SLOWNESS, entity.state.effects);
if (slownessLevel > 0) {
attribute = attributes.addAttributeModifier(attribute, {
uuid: SLOWNESS_UUID,
amount: Math.fround(Math.fround(-0.15) * slownessLevel),
operation: 2,
});
}
// Calculate the final speed and cast the entire result to a 32-bit float
const attributeSpeed = Math.fround(attributes.getAttributeValue(attribute));
return attributeSpeed;
}
/**
* Assume EPhysicsCtx is wrapping a PlayerState.
* @param ctx
* @param world
*/
movePlayer(ctx, world) {
var _a, _b, _c;
const player = ctx.state;
{
// LivingEntity::travel
const goingDown = player.vel.y <= 0.0;
const hasSlowFalling = player.slowFalling > 0;
// named drag in botcraft. That seems incorrect. Not sure yet.
let gravity;
if (this.verLessThan("1.20.5")) {
gravity = goingDown && hasSlowFalling ? 0.01 : ctx.gravity;
}
else {
gravity = goingDown && hasSlowFalling ? Math.min(0.01, ctx.gravity) : ctx.gravity;
}
if (player.isInWater && !player.flying) {
const initY = player.pos.y;
let waterSlowDown = player.sprinting ? ctx.sprintWaterInertia : ctx.waterInertia;
let inputStrength = 0.02;
let depthStriderMult;
if (this.verLessThan("1.21")) {
const depthStrider = player.depthStrider;
depthStriderMult = Math.min(depthStrider, 3) / 3;
}
else {
depthStriderMult = (_b = (_a = player.attributes[this.waterMovementEfficiencyAttribute]) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : 0; // random value as default
}
if (!player.onGround) {
waterSlowDown += (0.54600006 - waterSlowDown) * depthStriderMult; // magic number
const movementSpeed = this.getMovementSpeedAttribute(ctx); // slight deviation, using utility method
inputStrength += Math.fround(movementSpeed - inputStrength) * depthStriderMult;
}
if (this.verGreaterThan("1.12.2")) {
if (player.dolphinsGrace > 0) {
waterSlowDown = 0.96; // magic number
}
}
this.applyInputs(inputStrength, player);
this.applyMovement(ctx, world);
if (player.isCollidedHorizontally && player.onClimbable) {
player.vel.y = 0.2;
}
player.vel.x *= waterSlowDown;
player.vel.y *= 0.800000011920929; // magic number, pretty sure this is wrong.
player.vel.z *= waterSlowDown;
if (!player.sprinting) {
// this logic does not look entirely correct. I believe this is an attempt at version-agnostic water gravity.
// originally, the neg. vel value here was hardcoded.
// if (goingDown &&
// Math.abs(player.vel.y - 0.005) >= ectx.worldSettings.negligeableVelocity &&
// Math.abs(player.vel.y - gravity / 16) < ectx.worldSettings.negligeableVelocity) {
// player.vel.y -= ectx.worldSettings.negligeableVelocity;
// } else {
// player.vel.y -= gravity / 16
// }
// because of this, I will implement my own version.
// if (goingDown) {
player.vel.y -= ctx.waterGravity;
// }
}
const bb = player.getBB().expand(-1e-7, -1e-7, -1e-7);
bb.translate(0, 0.6000000238418579 - player.pos.y + initY, 0);
bb.translateVec(player.vel);
if (player.isCollidedHorizontally && this.worldIsFree(world, bb, false)) {
player.vel.y = ctx.worldSettings.outOfLiquidImpulse;
}
}
else if (player.isInLava && !player.flying) {
const initY = player.pos.y;
this.applyInputs(0.02, player);
this.applyMovement(ctx, world);
player.vel.scale(0.5);
player.vel.y -= ctx.lavaGravity;
const bb = player.getBB().expand(-1e-7, -1e-7, -1e-7);
bb.translate(0, 0.6000000238418579 - player.pos.y + initY, 0); // Math.fround(0.60)
bb.translateVec(player.vel);
if (player.isCollidedHorizontally && this.worldIsFree(world, bb, false)) {
player.vel.y = ctx.worldSettings.outOfLiquidImpulse;
}
}
// elytra flying.
else if (player.fallFlying) {
const previousHorizontalSpeed = Math.sqrt(player.vel.x * player.vel.x + player.vel.z * player.vel.z);
// slight deviation
// sqrt(front_vector.x² + front_vector.z²) to follow vanilla code
const { pitch, sinPitch, cosPitch, lookDir } = (0, physicsUtils_1.getLookingVector)(player);
const cosPitchFromLength = Math.sqrt(lookDir.x * lookDir.x + lookDir.z * lookDir.z);
const cosPitchSqr = cosPitch * cosPitch;
const hVel = Math.sqrt(player.vel.x * player.vel.x + player.vel.z * player.vel.z);
player.vel.y += gravity * (-1 + 0.75 * cosPitchSqr);
if (player.vel.y < 0.0 && cosPitchFromLength > 0.0) {
const deltaSpeed = -player.vel.y * 0.1 * cosPitchSqr;
player.vel.x += (lookDir.x * deltaSpeed) / cosPitchFromLength;
player.vel.z += (lookDir.z * deltaSpeed) / cosPitchFromLength;
player.vel.y += deltaSpeed;
}
if (player.pitch > 0.0 && cosPitchFromLength > 0.0) {
const deltaSpeed = hVel * lookDir.y * 0.04;
player.vel.x += (-lookDir.x * deltaSpeed) / cosPitchFromLength;
player.vel.z += (-lookDir.z * deltaSpeed) / cosPitchFromLength;
player.vel.y += deltaSpeed * 3.2; // magic number
}
if (cosPitchFromLength > 0.0) {
player.vel.x += ((lookDir.x / cosPitchFromLength) * hVel - player.vel.x) * 0.1;
player.vel.z += ((lookDir.z / cosPitchFromLength) * hVel - player.vel.z) * 0.1;
}
player.vel.x *= Math.fround(0.99); // magic number, this DEFINITELY should be replaced by a drag value.
player.vel.z *= Math.fround(0.99); // magic number, this DEFINITELY should be replaced by a drag value.
player.vel.y *= Math.fround(0.98); // magic number, this DEFINITELY should be replaced by a drag value.
this.applyMovement(ctx, world);
this.handleFallFlyingCollisions(player, previousHorizontalSpeed);
}
else {
const blockBelow = world.getBlock(this.getBlockBelowAffectingMovement(player, world));
// deviation. using our stores slipperiness values.
const friction = blockBelow
? (_c = this.blockSlipperiness[blockBelow.type]) !== null && _c !== void 0 ? _c : ctx.worldSettings.defaultSlipperiness
: ctx.worldSettings.defaultSlipperiness;
// console.log(blockBelow.name, blockBelow.position, player.supportingBlockPos, friction)
const inertia = player.lastOnGround ? friction * ctx.airborneInertia : ctx.airborneInertia;
// deviation, adding additional logic for changing attribute values.
const movementSpeedAttr = this.getMovementSpeedAttribute(ctx);
let inputStrength;
if (player.lastOnGround) {
inputStrength = movementSpeedAttr * (0.21600002 / (friction * friction * friction));
}
else {
inputStrength = this.getOffGroundSpeed(player);
}
this.applyInputs(inputStrength, player);
if (player.onClimbable) {
// LivingEntity::handleOnClimbable
player.vel.x = math.clamp(-0.15000000596046448, player.vel.x, 0.15000000596046448); // Math.fround(0.15)
player.vel.z = math.clamp(-0.15000000596046448, player.vel.z, 0.15000000596046448); // Math.fround(0.15)
player.vel.y = Math.max(-0.15000000596046448, player.vel.y); // Math.fround(0.15)
const feetBlock = world.getBlock(new vec3_1.Vec3(player.pos.x, player.pos.y, player.pos.z));
if (feetBlock && (this.scaffoldId === feetBlock.type) !== player.control.sneak) {
player.vel.y = 0.0;
}
}
this.applyMovement(ctx, world);
// if colliding and in climbable, go up.
if ((player.isCollidedHorizontally || player.control.jump) && player.onClimbable) {
// TODO: or in powder with leather boots.
player.vel.y = 0.2;
}