Skip to content

Commit 9f1a5a0

Browse files
committed
vanilla behavior change
1 parent 2b72576 commit 9f1a5a0

File tree

2 files changed

+260
-53
lines changed

2 files changed

+260
-53
lines changed

lib/javamath.js

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
const assert = require("node:assert");
2+
3+
class JavaFloat {
4+
constructor(value) {
5+
this.value = Math.fround(value.valueOf());
6+
}
7+
valueOf() {
8+
return this.value;
9+
}
10+
add(other) {
11+
assert(other instanceof JavaFloat, 'Operand must be a JavaFloat');
12+
return new JavaFloat(this.value + other.value);
13+
}
14+
subtract(other) {
15+
assert(other instanceof JavaFloat, 'Operand must be a JavaFloat');
16+
return new JavaFloat(this.value - other.value);
17+
}
18+
multiply(other) {
19+
assert(other instanceof JavaFloat, 'Operand must be a JavaFloat');
20+
return new JavaFloat(this.value * other.value);
21+
}
22+
divide(other) {
23+
assert(other instanceof JavaFloat, 'Operand must be a JavaFloat');
24+
return new JavaFloat(this.value / other.value);
25+
}
26+
abs() {
27+
return new JavaFloat(Math.abs(this.value));
28+
}
29+
round() {
30+
return new JavaFloat(Math.round(this.value));
31+
}
32+
clamp(min, max) {
33+
assert(min instanceof JavaFloat, 'Min operand must be a JavaFloat');
34+
assert(max instanceof JavaFloat, 'Max operand must be a JavaFloat');
35+
return new JavaFloat(Math.min(Math.max(this.value, min.value), max.value));
36+
}
37+
}
38+
39+
class JavaDouble {
40+
constructor(value) {
41+
this.value = value.valueOf()
42+
}
43+
valueOf() {
44+
return this.value;
45+
}
46+
add(other) {
47+
assert(other instanceof JavaDouble, 'Operand must be a JavaDouble');
48+
return new JavaDouble(this.value + other.value);
49+
}
50+
subtract(other) {
51+
assert(other instanceof JavaDouble, 'Operand must be a JavaDouble');
52+
return new JavaDouble(this.value - other.value);
53+
}
54+
multiply(other) {
55+
assert(other instanceof JavaDouble, 'Operand must be a JavaDouble');
56+
return new JavaDouble(this.value * other.value);
57+
}
58+
divide(other) {
59+
assert(other instanceof JavaDouble, 'Operand must be a JavaDouble');
60+
return new JavaDouble(this.value / other.value);
61+
}
62+
abs() {
63+
return new JavaDouble(Math.abs(this.value));
64+
}
65+
clamp(min, max) {
66+
assert(min instanceof JavaDouble, 'Min operand must be a JavaDouble');
67+
assert(max instanceof JavaDouble, 'Max operand must be a JavaDouble');
68+
return new JavaDouble(Math.min(Math.max(this.value, min.value), max.value));
69+
}
70+
round() {
71+
return new JavaDouble(Math.round(this.value));
72+
}
73+
}
74+
75+
class JavaInt {
76+
constructor(value) {
77+
this.value = value.valueOf() | 0;
78+
}
79+
valueOf() {
80+
return this.value;
81+
}
82+
add(other) {
83+
return new JavaInt(this.value + other.value);
84+
}
85+
subtract(other) {
86+
return new JavaInt(this.value - other.value);
87+
}
88+
multiply(other) {
89+
return new JavaInt(this.value * other.value);
90+
}
91+
divide(other) {
92+
return new JavaInt((this.value / other.value) | 0);
93+
}
94+
abs() {
95+
return new JavaInt(Math.abs(this.value));
96+
}
97+
}
98+
99+
const SIN_TABLE = new Array(65536);
100+
101+
for (let i = 0; i < 65536; i++) {
102+
103+
SIN_TABLE[i] = new JavaFloat(Math.sin(
104+
new JavaDouble(i)
105+
.multiply(new JavaDouble(Math.PI))
106+
.multiply(new JavaDouble(2.0))
107+
.divide(new JavaDouble(65536.0))
108+
.valueOf()
109+
))
110+
}
111+
112+
function sin32(x) {
113+
assert(x instanceof JavaFloat, 'Operand must be a JavaFloat');
114+
return SIN_TABLE[new JavaInt(x
115+
.multiply(new JavaFloat(10430.378)
116+
).valueOf()).valueOf() & 65535];
117+
}
118+
119+
function cos32(x) {
120+
assert(x instanceof JavaFloat, 'Operand must be a JavaFloat');
121+
return SIN_TABLE[(new JavaInt(
122+
x.multiply(new JavaFloat(10430.378))
123+
.add(new JavaFloat(16384.0))).valueOf()
124+
) & 65535];
125+
}
126+
127+
class Vec3Double {
128+
constructor(x, y, z) {
129+
this.x = new JavaDouble(x);
130+
this.y = new JavaDouble(y);
131+
this.z = new JavaDouble(z);
132+
}
133+
134+
offset(dx, dy, dz) {
135+
return new Vec3Double(
136+
this.x.add(new JavaDouble(dx)),
137+
this.y.add(new JavaDouble(dy)),
138+
this.z.add(new JavaDouble(dz))
139+
)
140+
}
141+
142+
// mutable add
143+
add(other) {
144+
assert(other instanceof Vec3Double, 'Operand must be a Vec3Double');
145+
this.x = this.x.add(other.x);
146+
this.y = this.y.add(other.y);
147+
this.z = this.z.add(other.z);
148+
return this;
149+
}
150+
151+
// mutable subtract
152+
subtract(other) {
153+
assert(other instanceof Vec3Double, 'Operand must be a Vec3Double');
154+
this.x = this.x.subtract(other.x);
155+
this.y = this.y.subtract(other.y);
156+
this.z = this.z.subtract(other.z);
157+
return this;
158+
}
159+
160+
floored() {
161+
return new Vec3Double(
162+
Math.floor(this.x.valueOf()),
163+
Math.floor(this.y.valueOf()),
164+
Math.floor(this.z.valueOf())
165+
)
166+
}
167+
168+
length() {
169+
return new JavaDouble(Math.sqrt(
170+
this.x.valueOf() * this.x.valueOf() +
171+
this.y.valueOf() * this.y.valueOf() +
172+
this.z.valueOf() * this.z.valueOf()
173+
))
174+
}
175+
176+
// normalize own values
177+
normalize() {
178+
const len = this.length().valueOf();
179+
if (len === 0) {
180+
return this;
181+
}
182+
this.x = new JavaDouble(this.x.valueOf() / len);
183+
this.y = new JavaDouble(this.y.valueOf() / len);
184+
this.z = new JavaDouble(this.z.valueOf() / len);
185+
return this;
186+
}
187+
188+
// translate own values
189+
translate(dx, dy, dz) {
190+
this.x = this.x.add(new JavaDouble(dx));
191+
this.y = this.y.add(new JavaDouble(dy));
192+
this.z = this.z.add(new JavaDouble(dz));
193+
return this;
194+
}
195+
}
196+
197+
module.exports = {
198+
sin32,
199+
cos32,
200+
JavaFloat,
201+
JavaDouble,
202+
JavaInt,
203+
Vec3Double
204+
}

lib/plugins/physics.js

Lines changed: 56 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
11
const {Vec3} = require('vec3')
22
const assert = require('assert')
3-
const math = require('../math')
43
const conv = require('../conversions')
54
const {performance} = require('perf_hooks')
65
const {createDoneTask, createTask} = require('../promise_utils')
76

87
const {Physics, PlayerState} = require('prismarine-physics')
8+
const {JavaFloat} = require("../javamath");
99

10-
// optional: toggling f32 for less precision but more java-like behavior (still not very accurate)
11-
// const f32 = Math.fround
12-
const f32 = (x) => x
13-
14-
// default is 0.5F
15-
const rawSensitivity = f32(0.5)
10+
const mouseSensitivity = 100.0
11+
// default is 0.5F (corresponds to 100%), so we just translate according to that
12+
// https://github.com/Marcelektro/MCP-919/blob/1717f75902c6184a1ed1bfcd7880404aab4da503/src/minecraft/net/minecraft/client/settings/GameSettings.java#L65
13+
const rawSensitivity = new JavaFloat(mouseSensitivity).divide(new JavaFloat(200.0))
1614
// https://github.com/Marcelektro/MCP-919/blob/1717f75902c6184a1ed1bfcd7880404aab4da503/src/minecraft/net/minecraft/client/renderer/EntityRenderer.java#L1097
17-
const f = rawSensitivity * f32(0.6) + f32(0.2)
18-
const calculatedSensitivity = f * f * f * f32(8.0)
19-
// 0.15D
20-
// https://github.com/Marcelektro/MCP-919/blob/1717f75902c6184a1ed1bfcd7880404aab4da503/src/minecraft/net/minecraft/entity/Entity.java#L389
21-
const sensitivityConstant = 0.15 * calculatedSensitivity
22-
23-
module.exports = inject
15+
const f = rawSensitivity.multiply(new JavaFloat(0.6)).add(new JavaFloat(0.2))
16+
const roundingGCD = new JavaFloat(0.15).multiply(new JavaFloat(8.0)).multiply(f).multiply(f).multiply(f)
2417

2518
const PHYSICS_INTERVAL_MS = 50
2619
const PHYSICS_TIMESTEP = PHYSICS_INTERVAL_MS / 1000 // 0.05
@@ -84,8 +77,8 @@ function inject(bot, {physicsEnabled, maxCatchupTicks}) {
8477
y: 0,
8578
z: 0,
8679
// notchian
87-
yaw: 0,
88-
pitch: 0,
80+
yaw: new JavaFloat(0),
81+
pitch: new JavaFloat(0),
8982
onGround: false,
9083
ticker: 20,
9184
flags: {onGround: false, hasHorizontalCollision: false},
@@ -126,9 +119,9 @@ function inject(bot, {physicsEnabled, maxCatchupTicks}) {
126119
if (bot.blockAt(bot.entity.position) == null) return // check if chunk is unloaded
127120
bot.emit('physicsTickBegin')
128121
if (bot.physicsEnabled && shouldUsePhysics) {
129-
if (typeof bot.entity.yawDegrees !== 'number') {
130-
bot.entity.yawDegrees = 0
131-
bot.entity.pitchDegrees = 0
122+
if (typeof bot.entity.yawDegrees === 'undefined') {
123+
bot.entity.yawDegrees = new JavaFloat(0)
124+
bot.entity.pitchDegrees = new JavaFloat(0)
132125
}
133126
physics.simulatePlayer(new PlayerState(bot, controlState, lastSent), world).apply(bot)
134127
updatePosition(now)
@@ -182,9 +175,9 @@ function inject(bot, {physicsEnabled, maxCatchupTicks}) {
182175
}
183176

184177
function deltaYawDegrees(yaw1, yaw2) {
185-
let dYaw = (yaw1 - yaw2) % 360
186-
if (dYaw < -180) dYaw += 360
187-
else if (dYaw > 180) dYaw -= 360
178+
let dYaw = new JavaFloat((yaw1.subtract(yaw2)) % new JavaFloat(360))
179+
if (dYaw < new JavaFloat(-180).valueOf()) dYaw = dYaw.add(new JavaFloat(360))
180+
else if (dYaw.valueOf() > new JavaFloat(180).valueOf()) dYaw = dYaw.subtract(new JavaFloat(360))
188181

189182
return dYaw
190183
}
@@ -239,7 +232,8 @@ function inject(bot, {physicsEnabled, maxCatchupTicks}) {
239232
})
240233
}
241234

242-
const yaw = lastSent.yaw + deltaYawDegrees(bot.entity.yawDegrees, lastSent.yaw)
235+
// todo: fix potential for aimmodulo360
236+
const yaw = bot.entity.yawDegrees
243237
const pitch = bot.entity.pitchDegrees
244238

245239
const position = bot.entity.position
@@ -362,35 +356,42 @@ function inject(bot, {physicsEnabled, maxCatchupTicks}) {
362356

363357
// https://github.com/Marcelektro/MCP-919/blob/1717f75902c6184a1ed1bfcd7880404aab4da503/src/minecraft/net/minecraft/entity/Entity.java#L389
364358
function setAngleDegrees(newYaw, newPitch, autoround = true) {
365-
const initialYaw = bot.entity.yawDegrees
366-
const initialPitch = bot.entity.pitchDegrees
367-
368359
if (autoround) {
369-
bot.entity.yawDegrees = f32(bot.entity.yawDegrees + f32(Math.round((newYaw - initialYaw) / sensitivityConstant) * sensitivityConstant))
370-
bot.entity.pitchDegrees = f32(bot.entity.pitchDegrees + f32(Math.round((newPitch - initialPitch) / sensitivityConstant) * sensitivityConstant))
360+
const dYaw = deltaYawDegrees(new JavaFloat(newYaw), bot.entity.yawDegrees)
361+
const normYaw = bot.entity.yawDegrees.add(dYaw)
362+
bot.entity.yawDegrees = normYaw
363+
.divide(roundingGCD)
364+
.round()
365+
.multiply(roundingGCD)
366+
bot.entity.pitchDegrees = new JavaFloat(newPitch)
367+
.divide(roundingGCD)
368+
.round()
369+
.multiply(roundingGCD)
371370
} else {
372-
bot.entity.yawDegrees = f32(newYaw)
373-
bot.entity.pitchDegrees = f32(newPitch)
374-
}
371+
bot.entity.yawDegrees = new JavaFloat(newYaw)
372+
bot.entity.pitchDegrees = new JavaFloat(newPitch)
375373

376-
bot.entity.pitchDegrees = math.clamp(f32(-90.0), bot.entity.pitchDegrees, f32(90.0))
374+
assert(newYaw.valueOf() === bot.entity.yawDegrees.valueOf(), `Yaw value changed during assignment: ${newYaw.valueOf()} -> ${bot.entity.yawDegrees.valueOf()}`)
375+
assert(newPitch.valueOf() === bot.entity.pitchDegrees.valueOf(), `Pitch value changed during assignment: ${newPitch.valueOf()} -> ${bot.entity.pitchDegrees.valueOf()}`)
376+
}
377+
bot.entity.pitchDegrees = bot.entity.pitchDegrees.clamp(new JavaFloat(-90.0), new JavaFloat(90.0))
377378

378379
bot.entity.yaw = conv.fromNotchianYaw(bot.entity.yawDegrees)
379380
bot.entity.pitch = conv.fromNotchianPitch(bot.entity.pitchDegrees)
380381
}
381382

382-
bot.on('physicsTick', () => {
383-
if (!lookingTask.done) {
384-
if (Math.abs(deltaYawDegrees(bot.entity.yawDegrees, targetYaw)) < 0.1 && Math.abs(bot.entity.pitchDegrees - targetPitch) < 0.1) {
385-
lookingTask.finish()
386-
} else {
387-
// look toward it
388-
const yawChange = math.clamp(-physics.yawSpeed, deltaYawDegrees(targetYaw, bot.entity.yawDegrees), physics.yawSpeed)
389-
const pitchChange = math.clamp(-physics.pitchSpeed, targetPitch - bot.entity.pitchDegrees, physics.pitchSpeed)
390-
setAngleDegrees(bot.entity.yawDegrees + yawChange, bot.entity.pitchDegrees + pitchChange)
391-
}
392-
}
393-
})
383+
// bot.on('physicsTick', () => {
384+
// if (!lookingTask.done) {
385+
// if (Math.abs(deltaYawDegrees(bot.entity.yawDegrees, targetYaw)) < 0.1 && Math.abs(bot.entity.pitchDegrees.subtract(targetPitch)) < 0.1) {
386+
// lookingTask.finish()
387+
// } else {
388+
// // look toward it
389+
// const yawChange = math.clamp(-physics.yawSpeed, deltaYawDegrees(targetYaw, bot.entity.yawDegrees.valueOf()), physics.yawSpeed)
390+
// const pitchChange = math.clamp(-physics.pitchSpeed, targetPitch - bot.entity.pitchDegrees.valueOf(), physics.pitchSpeed)
391+
// setAngleDegrees(bot.entity.yawDegrees.valueOf() + yawChange, bot.entity.pitchDegrees.valueOf() + pitchChange)
392+
// }
393+
// }
394+
// })
394395

395396
bot._client.on('explosion', explosion => {
396397
// TODO: emit an explosion event with more info
@@ -464,14 +465,14 @@ function inject(bot, {physicsEnabled, maxCatchupTicks}) {
464465
const newY = (flagY ? position.y : 0) + packet.y
465466
const newZ = (flagZ ? position.z : 0) + packet.z
466467

467-
if (typeof bot.entity.yawDegrees !== 'number') {
468-
bot.entity.yawDegrees = 0
469-
bot.entity.pitchDegrees = 0
468+
if (typeof bot.entity.yawDegrees === 'undefined') {
469+
bot.entity.yawDegrees = new JavaFloat(0.0)
470+
bot.entity.pitchDegrees = new JavaFloat(0.0)
470471
}
471472

472473
// these are float resolution
473-
const newYaw = (flagYaw ? bot.entity.yawDegrees : 0) + packet.yaw
474-
const newPitch = (flagPitch ? bot.entity.pitchDegrees : 0) + packet.pitch
474+
const newYaw = (flagYaw ? bot.entity.yawDegrees : new JavaFloat(0)).add(new JavaFloat(packet.yaw))
475+
const newPitch = (flagPitch ? bot.entity.pitchDegrees : new JavaFloat(0)).add(new JavaFloat(packet.pitch))
475476

476477
velocity.set(
477478
flagX ? velocity.x : 0,
@@ -522,15 +523,15 @@ function inject(bot, {physicsEnabled, maxCatchupTicks}) {
522523
}
523524

524525
bot.on('respawn', () => {
525-
bot.entity.yawDegrees = 0
526-
bot.entity.pitchDegrees = 0
526+
bot.entity.yawDegrees = new JavaFloat(0)
527+
bot.entity.pitchDegrees = new JavaFloat(0)
527528
shouldUsePhysics = false
528529
forceResetControls()
529530
})
530531

531532
bot.on('login', () => {
532-
bot.entity.yawDegrees = 0
533-
bot.entity.pitchDegrees = 0
533+
bot.entity.yawDegrees = new JavaFloat(0)
534+
bot.entity.pitchDegrees = new JavaFloat(0)
534535
shouldUsePhysics = false
535536
forceResetControls()
536537
lastSent = {
@@ -557,3 +558,5 @@ function inject(bot, {physicsEnabled, maxCatchupTicks}) {
557558
})
558559
bot.on('end', cleanup)
559560
}
561+
562+
module.exports = inject

0 commit comments

Comments
 (0)