-
Notifications
You must be signed in to change notification settings - Fork 58
Obey the flight the server grants in the abilities packet #142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -543,6 +543,9 @@ function Physics (mcData, world) { | |
| let acceleration = 0.0 | ||
| let inertia = 0.0 | ||
| const blockUnder = world.getBlock(pos.offset(0, -1, 0)) | ||
| // Player.travel wraps the move while flying and puts back the vertical velocity the tick | ||
| // started with, damped, so it is read before anything below touches it. | ||
| const flightEntryVelY = vel.y | ||
| if (entity.onGround && blockUnder) { | ||
| let playerSpeedAttribute | ||
| if (entity.attributes && entity.attributes[physics.movementSpeedAttribute]) { | ||
|
|
@@ -569,6 +572,11 @@ function Physics (mcData, world) { | |
| 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 if (entity.flying) { | ||
| // Player.getFlyingSpeed: creative flight accelerates at the abilities' flying speed, | ||
| // doubled while sprinting, in place of the 0.02 / 0.026 of a falling player. | ||
| acceleration = entity.control.sprint ? entity.flyingSpeed * 2 : entity.flyingSpeed | ||
| inertia = physics.airborneInertia | ||
| } else { | ||
| acceleration = physics.airborneAcceleration | ||
| inertia = physics.airborneInertia | ||
|
|
@@ -581,26 +589,30 @@ function Physics (mcData, world) { | |
|
|
||
| applyHeading(entity, strafe, forward, acceleration) | ||
|
|
||
| if (isOnLadder(world, pos)) { | ||
| if (!entity.flying && 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 || | ||
| if (!entity.flying && 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 | ||
| if (entity.flying) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Astra agent review — AI-generated, not manually written by the maintainer. The flight handling here is reached only by the normal-movement branch; a flying player in water or lava still takes the earlier fluid branch, including fluid acceleration and gravity. With this head's Skills used: prismarine-behavior-test-review helped verify the environmental branch with real blocks; prismarine-architecture-review helped trace the flight flag across shared movement paths. |
||
| vel.y = flightEntryVelY * 0.6 | ||
| } else { | ||
| vel.y -= physics.gravity * gravityMultiplier | ||
| 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.y *= physics.airdrag | ||
| vel.x *= inertia | ||
| vel.z *= inertia | ||
| } | ||
|
|
@@ -820,6 +832,9 @@ class PlayerState { | |
| this.fireworkRocketDuration = bot.fireworkRocketDuration | ||
|
|
||
| // Input only (not modified) | ||
| // The server owns these: it grants flight in the abilities packet and the client obeys. | ||
| this.flying = bot.entity.flying ?? false | ||
| this.flyingSpeed = bot.entity.flyingSpeed ?? 0.05 | ||
| this.attributes = bot.entity.attributes | ||
| this.yaw = bot.entity.yaw | ||
| this.pitch = bot.entity.pitch | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| /* eslint-env mocha */ | ||
|
|
||
| const { Physics, PlayerState } = require('prismarine-physics') | ||
| const { Vec3 } = require('vec3') | ||
| const expect = require('expect') | ||
|
|
||
| const version = '1.13.2' | ||
| const mcData = require('minecraft-data')(version) | ||
| const Block = require('prismarine-block')(version) | ||
|
|
||
| const fakeWorld = { | ||
| getBlock: (pos) => { | ||
| const type = (pos.y < 60) ? mcData.blocksByName.stone.id : mcData.blocksByName.air.id | ||
| const b = new Block(type, 0, 0) | ||
| b.position = pos | ||
| return b | ||
| } | ||
| } | ||
|
|
||
| function fakePlayer (pos, { flying = false, flyingSpeed = 0.05 } = {}) { | ||
| return { | ||
| entity: { | ||
| position: pos, | ||
| velocity: new Vec3(0, 0, 0), | ||
| onGround: false, | ||
| isInWater: false, | ||
| isInLava: false, | ||
| isInWeb: false, | ||
| isCollidedHorizontally: false, | ||
| isCollidedVertically: false, | ||
| elytraFlying: false, | ||
| flying, | ||
| flyingSpeed, | ||
| yaw: Math.PI * 3 / 2, // east (+x) | ||
| pitch: 0, | ||
| effects: {} | ||
| }, | ||
| jumpTicks: 0, | ||
| jumpQueued: false, | ||
| fireworkRocketDuration: 0, | ||
| version, | ||
| inventory: { slots: [] } | ||
| } | ||
| } | ||
|
|
||
| const idle = () => ({ forward: false, back: false, left: false, right: false, jump: false, sprint: false, sneak: false }) | ||
|
|
||
| function run (player, controls, ticks) { | ||
| const physics = Physics(mcData, fakeWorld) | ||
| const state = new PlayerState(player, controls) | ||
| for (let i = 0; i < ticks; i++) physics.simulatePlayer(state, fakeWorld).apply(player) | ||
| return player.entity | ||
| } | ||
|
|
||
| describe('creative flight', () => { | ||
| it('holds its altitude instead of falling', () => { | ||
| const entity = run(fakePlayer(new Vec3(0.5, 80, 0.5), { flying: true }), idle(), 40) | ||
| expect(entity.position.y).toEqual(80) | ||
| expect(entity.velocity.y).toEqual(0) | ||
| }) | ||
|
|
||
| it('still falls when the server has not granted flight', () => { | ||
| const entity = run(fakePlayer(new Vec3(0.5, 80, 0.5), { flying: false }), idle(), 40) | ||
| expect(entity.position.y).toBeLessThan(80) | ||
| }) | ||
|
|
||
| it('damps the velocity it entered the tick with rather than adding gravity', () => { | ||
| const player = fakePlayer(new Vec3(0.5, 80, 0.5), { flying: true }) | ||
| player.entity.velocity.y = 1 | ||
| // Player.travel keeps y * 0.6 per tick, so the climb decays instead of turning into a fall. | ||
| const entity = run(player, idle(), 1) | ||
| expect(entity.velocity.y).toBeCloseTo(0.6, 10) | ||
| expect(entity.position.y).toBeCloseTo(81, 10) | ||
| }) | ||
|
|
||
| it('accelerates at the abilities speed, not the 0.02 of a falling player', () => { | ||
| const controls = { ...idle(), forward: true } | ||
| const flying = run(fakePlayer(new Vec3(0.5, 80, 0.5), { flying: true }), controls, 1) | ||
| const falling = run(fakePlayer(new Vec3(0.5, 80, 0.5), { flying: false }), controls, 1) | ||
| expect(flying.velocity.x).toBeGreaterThan(falling.velocity.x) | ||
| // 0.05 against the airborne 0.02 | ||
| expect(flying.velocity.x / falling.velocity.x).toBeCloseTo(2.5, 6) | ||
| }) | ||
|
|
||
| it('doubles that speed while sprinting', () => { | ||
| const walk = run(fakePlayer(new Vec3(0.5, 80, 0.5), { flying: true }), { ...idle(), forward: true }, 1) | ||
| const sprint = run(fakePlayer(new Vec3(0.5, 80, 0.5), { flying: true }), { ...idle(), forward: true, sprint: true }, 1) | ||
| expect(sprint.velocity.x / walk.velocity.x).toBeCloseTo(2, 6) | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Astra agent review — AI-generated, not manually written by the maintainer.
The new flying mode preserves hover but never applies vertical flight input.
simulatePlayeronly adds upward velocity for water/lava or an on-ground jump, and sneak only scales horizontal input. Using this PR'sfakePlayer/world fixture withflying: trueand initialvy = 0, holding either jump or sneak for 40 ticks leavesy = 80andvy = 0throughout. Once the abilities fields are wired up, the bot can therefore neither ascend nor descend from a hover through its normal controls. Please apply the flying jump/sneak input before movement and damping, using the granted flying speed, and add ascent/descent cases alongside the idle-hover tests.