UNPKG

@nxg-org/mineflayer-physics-util

Version:

Provides functionality for more accurate entity and projectile tracking.

355 lines (289 loc) 11.4 kB
import { describe, it, beforeEach } from "mocha"; import expect from "expect"; import { Vec3 } from "vec3"; import md from "minecraft-data"; import block, { Block as PBlock } from "prismarine-block"; import { applyMdToNewEntity } from "../src/util/physicsUtils"; import { EPhysicsCtx, PhysicsWorldSettings } from "../src/physics/settings"; import { ControlStateHandler } from "../src/physics/player"; import { BotcraftPhysics, EntityPhysics, IPhysics } from "../src/physics/engines"; import { initSetup } from "../src/index"; import { PlayerState } from "../src/physics/states"; import { Bot, ControlState } from "mineflayer"; import { AABB } from "@nxg-org/mineflayer-util-plugin"; const version = "1.12.2"; const mcData = md(version); const Engine = BotcraftPhysics; const Block = block(version) as typeof PBlock; const groundLevel = 67; const floatingOffset = 100 - groundLevel; const control: { [key: string]: boolean } = {}; class FakeWorld { overrideBlocks: { [key: string]: PBlock } = {}; setOverrideBlock(pos: Vec3, type: number) { pos = pos.floored(); const block = new Block(type, 0, 0); block.position = pos; this.overrideBlocks[`${pos.x},${pos.y},${pos.z}`] = block; } clearOverrides() { this.overrideBlocks = {}; } getBlock(pos: Vec3) { pos = pos.floored(); const key = `${pos.x},${pos.y},${pos.z}`; if (this.overrideBlocks[key]) { return this.overrideBlocks[key]; } const type = pos.y < groundLevel ? mcData.blocksByName.stone.id : mcData.blocksByName.air.id; const b = new Block(type, 0, 0); b.position = pos; return b; } } function createFakePlayer(pos: Vec3, tmpGroundLevel: number = groundLevel) { const onGround = pos.y === tmpGroundLevel return { entity: { position: pos, velocity: new Vec3(0, onGround ? -0.08 : 0, 0), onGround: onGround, isInWater: false, isInLava: false, isInWeb: false, isCollidedHorizontally: false, isCollidedVertically: false, yaw: 0, effects: [], attributes: {} }, jumpTicks: 0, jumpQueued: false, version: version, inventory: { slots: [] }, equipment: [], food: 20, game: { gameMode: "survival" }, registry: mcData, setControlState: (name: ControlState, value: boolean) => { control[name] = value; }, getControlState: (name: ControlState) => { return control?.[name] ?? false; }, getEquipmentDestSlot: () => {}, }; } initSetup(mcData); const playerType = mcData.entitiesByName["player"]; describe("Physics Simulation Tests", () => { let fakePlayer: ReturnType<typeof createFakePlayer> | any; let physics: IPhysics; let playerCtx: EPhysicsCtx; let playerState: PlayerState; const fakeWorld = new FakeWorld(); const setupEntity = (yOffset: number) => { fakePlayer = createFakePlayer(new Vec3(0, groundLevel + yOffset, 0), groundLevel); fakePlayer.entity = applyMdToNewEntity(EPhysicsCtx, playerType, fakePlayer.entity); physics = new Engine(mcData); playerCtx = EPhysicsCtx.FROM_BOT(physics, fakePlayer); playerState = playerCtx.state as PlayerState; playerState.control = ControlStateHandler.DEFAULT(); }; afterEach(() => { fakeWorld.clearOverrides(); }); it("should maintain position when gravity is zero", () => { setupEntity(floatingOffset); fakePlayer.entity.velocity = new Vec3(0, 0, 0); playerState.vel.y = 0; playerCtx.gravity = 0; for (let i = 0; i < 2; i++) { // playerState.update(fakePlayer); physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); } expect(fakePlayer.entity.position).toEqual(new Vec3(0, groundLevel + floatingOffset, 0)); }); it("should move forward correctly given proper gravity", () => { setupEntity(0); playerState.control.forward = true; playerState.control.sprint = true; for (let i = 0; i < 10; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); // console.log(fakePlayer.entity.position, playerState.pos, playerState.vel, playerState.age) } if (playerState.control.forward) { expect(fakePlayer.entity.position).toEqual(new Vec3(0, groundLevel, -2.4694812397932626)); } else { expect(fakePlayer.entity.position).toEqual(new Vec3(0, groundLevel, 0)); } }); it("sprint-jumping", () => { setupEntity(0); playerState.control.forward = true; playerState.control.sprint = true; for (let i = 0; i < 4; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); // console.log(playerState.sprinting, playerState.onGround, playerState.pos) } playerState.control.jump = true; for (let i = 0; i < 12; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); // console.log(playerState.sprinting, playerState.onGround, playerState.pos) } expect(fakePlayer.entity.position.y).toEqual(groundLevel); expect(fakePlayer.entity.position.z).toEqual(-4.085029471928113); }); it("walk_fallspeed", () => { setupEntity(floatingOffset); playerState.control.forward = true; while (!fakePlayer.entity.onGround && playerState.age < 100) { physics.simulate(playerCtx, fakeWorld); // console.log(fakePlayer.entity.position, playerState.pos, playerState.vel, playerState.age) playerState.apply(fakePlayer); } expect(fakePlayer.entity.position.z).toEqual(-5.082680598494437); expect(fakePlayer.entity.position.y).toEqual(groundLevel); }); it ("sprint_fallspeed", () => { setupEntity(floatingOffset); playerState.control.forward = true; playerState.control.sprint = true; // console.log(playerState.vel) while (!fakePlayer.entity.onGround && playerState.age < 100) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); } // console.log(fakePlayer.entity.position, landingPos, playerState.pos, playerState.control) expect(fakePlayer.entity.position.z).toEqual(-6.607484778042766); expect(fakePlayer.entity.position.y).toEqual(groundLevel); }) it("should restore position after gravity toggle", () => { setupEntity(floatingOffset); const orgGravity = playerCtx.gravity; fakePlayer.entity.velocity = new Vec3(0, 0, 0); playerState.vel.y = 0; playerCtx.gravity = 0; for (let i = 0; i < 5; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); } expect(fakePlayer.entity.position).toEqual(new Vec3(0, groundLevel + floatingOffset, 0)); playerCtx.gravity = orgGravity; while (!fakePlayer.entity.onGround && playerState.age < 100) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); } expect(fakePlayer.entity.position.y).toEqual(groundLevel); // Verify movement in Z direction }); it("should jump and fall correctly", () => { setupEntity(0); playerState.control.jump = true; for (let i = 0; i < 3; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); } expect(fakePlayer.entity.position.y).toEqual(groundLevel + 1.001335979112147); for (let i = 0; i < 9; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); } expect(fakePlayer.entity.position.y).toEqual(groundLevel); }); it("hCol--z", () => { setupEntity(0); const blockPos = new Vec3(0, groundLevel + 1, -2); fakeWorld.setOverrideBlock(blockPos, mcData.blocksByName.dirt.id); playerState.look(0, 0); playerState.control.forward = true; for (let i = 0; i < 10; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); console.log(fakePlayer.entity.position, playerState.isCollidedHorizontally); } expect(playerState.pos.z).toEqual(-0.7); expect(playerState.isCollidedHorizontally).toEqual(true); }); it("hCol-z", () => { setupEntity(0); const blockPos = new Vec3(0, groundLevel + 1, 1); fakeWorld.setOverrideBlock(blockPos, mcData.blocksByName.dirt.id); playerState.look(-359.9999 * (Math.PI / 360), 0); playerState.control.forward = true; for (let i = 0; i < 10; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); console.log(fakePlayer.entity.position, playerState.isCollidedHorizontally); } expect(playerState.pos.z).toEqual(0.7); expect(playerState.isCollidedHorizontally).toEqual(true); }); it("hCol--x", () => { setupEntity(0); const blockPos = new Vec3(-2, groundLevel + 1, 0); fakeWorld.setOverrideBlock(blockPos, mcData.blocksByName.dirt.id); playerState.look(180 * (Math.PI / 360), 0) playerState.control.forward = true; for (let i = 0; i < 10; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); console.log(fakePlayer.entity.position, playerState.isCollidedHorizontally); } expect(playerState.pos.x).toEqual(-0.7); expect(playerState.isCollidedHorizontally).toEqual(true); }); it("hCol-x", () => { setupEntity(0); const blockPos = new Vec3(1, groundLevel + 1, 0); fakeWorld.setOverrideBlock(blockPos, mcData.blocksByName.dirt.id); playerState.look(-180 * (Math.PI / 360), 0); playerState.control.forward = true; for (let i = 0; i < 10; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); console.log(fakePlayer.entity.position, playerState.isCollidedHorizontally); } expect(playerState.pos.x).toEqual(0.7); expect(playerState.isCollidedHorizontally).toEqual(true); }); it("jumpIntoBlock", () => { setupEntity(0); const bl1 = new Vec3(0, groundLevel + 1, 1); fakeWorld.setOverrideBlock(bl1, mcData.blocksByName.dirt.id); fakePlayer.entity.position = new Vec3(0.5, groundLevel, 0.7); // right up against a block playerState.pos = fakePlayer.entity.position.clone(); playerState.look(-359.9999 * (Math.PI / 360), 0); playerState.control.jump = true; playerState.control.forward = true; playerState.control.sprint = true; for (let i = 0; i < 12; i++) { physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); // console.log(fakePlayer.entity.position, playerState.isCollidedHorizontally); } expect(playerState.pos.z).toEqual(0.7); expect(playerState.isCollidedHorizontally).toEqual(true); }); it("walkUpStairs", () => { setupEntity(0); const bl1 = new Vec3(0, groundLevel, -1); fakeWorld.setOverrideBlock(bl1, mcData.blocksByName.stone_stairs.id); const shapes = fakeWorld.getBlock(bl1).shapes; const bbs = shapes.map((shape) => AABB.fromShape(shape, bl1)); fakePlayer.entity.position = new Vec3(-0.3, groundLevel, -0.5); // right up against a block playerState.pos = fakePlayer.entity.position.clone(); playerState.look(-180 * (Math.PI / 360), 0); playerState.control.forward = true; playerState.control.sprint = true; for (let i = 0; i < 4; i++) { // console.log(fakePlayer.entity.position, playerState.pos, playerState.isCollidedHorizontally); physics.simulate(playerCtx, fakeWorld); playerState.apply(fakePlayer); } expect(playerState.pos.y).toEqual(groundLevel + 1); }); });