@miner-org/mineflayer-physics-reworked 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { PhysicsEngine } from "./src/engine";
2
+
3
+ interface ControlState {
4
+ forward: boolean;
5
+ back: boolean;
6
+ left: boolean;
7
+ right: boolean;
8
+ jump: boolean;
9
+ sneak: boolean;
10
+ }
11
+
12
+ declare module "@miner-org/mineflayer-physics-reworked" {
13
+ export function inject(bot: Bot): void;
14
+ }
15
+
16
+ declare module "mineflayer" {
17
+ interface Bot {
18
+ ashPhysics: PhysicsEngine;
19
+ ashPhysicsEnabled: boolean;
20
+ ashControlState: ControlState;
21
+ ashGetControlState(): ControlState;
22
+ ashClearControlStates(): void;
23
+ ashSetControlState(
24
+ control: "forward" | "back" | "left" | "right" | "jump" | "sneak",
25
+ value: boolean,
26
+ ): void;
27
+ }
28
+ }
package/index.js ADDED
@@ -0,0 +1,501 @@
1
+ const { Vec3 } = require("vec3");
2
+ const assert = require("assert");
3
+ const math = require("mineflayer/lib/math");
4
+ const conv = require("mineflayer/lib/conversions");
5
+ const { performance } = require("perf_hooks");
6
+ const { createDoneTask, createTask } = require("mineflayer/lib/promise_utils");
7
+
8
+ const { PhysicsEngine, PlayerState } = require("./src/engine.js");
9
+
10
+ module.exports = inject;
11
+
12
+ const PI = Math.PI;
13
+ const PI_2 = Math.PI * 2;
14
+ const PHYSICS_INTERVAL_MS = 50;
15
+ const PHYSICS_TIMESTEP = PHYSICS_INTERVAL_MS / 1000; // 0.05
16
+
17
+ /**
18
+ * @param {import('mineflayer').Bot} bot
19
+ */
20
+ function inject(bot) {
21
+ // Disable mineflayer's built-in physics
22
+ bot.physicsEnabled = false;
23
+ bot._client.on("position", () => {
24
+ bot.physicsEnabled = false;
25
+ });
26
+
27
+ const world = {
28
+ getBlock: (pos) => bot.blockAt(pos, false),
29
+ };
30
+
31
+ const physics = new PhysicsEngine(bot.registry, world);
32
+
33
+ const positionUpdateSentEveryTick = bot.supportFeature(
34
+ "positionUpdateSentEveryTick",
35
+ );
36
+
37
+ // Jump state
38
+ bot.jumpQueued = false;
39
+ bot.jumpTicks = 0;
40
+
41
+ // Control state
42
+ const controlState = {
43
+ forward: false,
44
+ back: false,
45
+ left: false,
46
+ right: false,
47
+ jump: false,
48
+ sprint: false,
49
+ sneak: false,
50
+ };
51
+
52
+ // Look state
53
+ let lastSentYaw = null;
54
+ let lastSentPitch = null;
55
+
56
+ // Physics timing
57
+ let doPhysicsTimer = null;
58
+
59
+ // Physics enabled flags
60
+ let shouldUsePhysics = false;
61
+ bot.ashPhysicsEnabled = true;
62
+ let deadTicks = 21;
63
+
64
+ // Last sent packet data
65
+ const lastSent = {
66
+ x: 0,
67
+ y: 0,
68
+ z: 0,
69
+ yaw: 0,
70
+ pitch: 0,
71
+ onGround: false,
72
+ time: 0,
73
+ flags: { onGround: false, hasHorizontalCollision: false },
74
+ };
75
+
76
+ function doPhysics() {
77
+ const now = performance.now();
78
+ tickPhysics(now);
79
+ }
80
+
81
+ function tickPhysics(now) {
82
+ // Skip if chunk is unloaded
83
+ if (bot.blockAt(bot.entity.position) == null) return;
84
+
85
+ if (bot.ashPhysicsEnabled && shouldUsePhysics) {
86
+ const state = new PlayerState(bot, controlState);
87
+
88
+ physics.simulatePlayer(state);
89
+ state.apply(bot);
90
+
91
+ bot.emit("physicsTick");
92
+ bot.emit("physicTick"); // Deprecated
93
+ }
94
+
95
+ if (shouldUsePhysics) {
96
+ updatePosition(now);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Update position and send packets to server
102
+ */
103
+ function updatePosition(now) {
104
+ if (isEntityRemoved()) return;
105
+
106
+ const dYaw = deltaYaw(bot.entity.yaw, lastSentYaw);
107
+ const dPitch = bot.entity.pitch - (lastSentPitch || 0);
108
+
109
+ const maxDeltaYaw = PHYSICS_TIMESTEP * physics.constants.yawSpeed;
110
+ const maxDeltaPitch = PHYSICS_TIMESTEP * physics.constants.pitchSpeed;
111
+ lastSentYaw += math.clamp(-maxDeltaYaw, dYaw, maxDeltaYaw);
112
+ lastSentPitch += math.clamp(-maxDeltaPitch, dPitch, maxDeltaPitch);
113
+
114
+ const yaw = Math.fround(conv.toNotchianYaw(lastSentYaw));
115
+ const pitch = Math.fround(conv.toNotchianPitch(lastSentPitch));
116
+ const position = bot.entity.position;
117
+ const onGround = bot.entity.onGround;
118
+
119
+ const positionUpdated =
120
+ lastSent.x !== position.x ||
121
+ lastSent.y !== position.y ||
122
+ lastSent.z !== position.z ||
123
+ Math.round((now - lastSent.time) / PHYSICS_INTERVAL_MS) *
124
+ PHYSICS_INTERVAL_MS >=
125
+ 1000;
126
+
127
+ const lookUpdated = lastSent.yaw !== yaw || lastSent.pitch !== pitch;
128
+
129
+ if (positionUpdated && lookUpdated) {
130
+ sendPacketPositionAndLook(position, yaw, pitch, onGround);
131
+ lastSent.time = now;
132
+ } else if (positionUpdated) {
133
+ sendPacketPosition(position, onGround);
134
+ lastSent.time = now;
135
+ } else if (lookUpdated) {
136
+ sendPacketLook(yaw, pitch, onGround);
137
+ } else if (positionUpdateSentEveryTick || onGround !== lastSent.onGround) {
138
+ bot._client.write("flying", {
139
+ onGround: bot.entity.onGround,
140
+ flags: {
141
+ onGround: bot.entity.onGround,
142
+ hasHorizontalCollision: undefined,
143
+ },
144
+ });
145
+ }
146
+
147
+ lastSent.onGround = bot.entity.onGround;
148
+ }
149
+
150
+ function sendPacketPosition(position, onGround) {
151
+ const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z);
152
+ lastSent.x = position.x;
153
+ lastSent.y = position.y;
154
+ lastSent.z = position.z;
155
+ lastSent.onGround = onGround;
156
+ lastSent.flags = { onGround, hasHorizontalCollision: undefined };
157
+ bot._client.write("position", lastSent);
158
+ bot.emit("move", oldPos);
159
+ }
160
+
161
+ function sendPacketLook(yaw, pitch, onGround) {
162
+ const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z);
163
+ lastSent.yaw = yaw;
164
+ lastSent.pitch = pitch;
165
+ lastSent.onGround = onGround;
166
+ lastSent.flags = { onGround, hasHorizontalCollision: undefined };
167
+ bot._client.write("look", lastSent);
168
+ bot.emit("move", oldPos);
169
+ }
170
+
171
+ function sendPacketPositionAndLook(position, yaw, pitch, onGround) {
172
+ const oldPos = new Vec3(lastSent.x, lastSent.y, lastSent.z);
173
+ lastSent.x = position.x;
174
+ lastSent.y = position.y;
175
+ lastSent.z = position.z;
176
+ lastSent.yaw = yaw;
177
+ lastSent.pitch = pitch;
178
+ lastSent.onGround = onGround;
179
+ lastSent.flags = { onGround, hasHorizontalCollision: undefined };
180
+ bot._client.write("position_look", lastSent);
181
+ bot.emit("move", oldPos);
182
+ }
183
+
184
+ function deltaYaw(yaw1, yaw2) {
185
+ let dYaw = (yaw1 - yaw2) % PI_2;
186
+ if (dYaw < -PI) dYaw += PI_2;
187
+ else if (dYaw > PI) dYaw -= PI_2;
188
+ return dYaw;
189
+ }
190
+
191
+ function isEntityRemoved() {
192
+ if (bot.isAlive === true) deadTicks = 0;
193
+ if (bot.isAlive === false && deadTicks <= 20) deadTicks++;
194
+ if (deadTicks >= 20) return true;
195
+ return false;
196
+ }
197
+
198
+ function getEffectLevel(mcData, effectName, effects) {
199
+ const effectDescriptor = mcData.effectsByName[effectName];
200
+ if (!effectDescriptor) return 0;
201
+ const effectInfo = effects[effectDescriptor.id];
202
+ if (!effectInfo) return 0;
203
+ return effectInfo.amplifier + 1;
204
+ }
205
+
206
+ function cleanup() {
207
+ clearInterval(doPhysicsTimer);
208
+ doPhysicsTimer = null;
209
+ }
210
+
211
+ // Expose physics engine to bot
212
+ bot.ashPhysics = physics;
213
+
214
+ bot.ashSetControlState = (control, state) => {
215
+ assert.ok(control in controlState, `invalid control: ${control}`);
216
+ assert.ok(typeof state === "boolean", `invalid state: ${state}`);
217
+ if (controlState[control] === state) return;
218
+
219
+ controlState[control] = state;
220
+
221
+ if (control === "jump" && state) {
222
+ bot.jumpQueued = true;
223
+ } else if (control === "sprint") {
224
+ bot._client.write("entity_action", {
225
+ entityId: bot.entity.id,
226
+ actionId: state ? 3 : 4,
227
+ jumpBoost: 0,
228
+ });
229
+ } else if (control === "sneak") {
230
+ bot._client.write("entity_action", {
231
+ entityId: bot.entity.id,
232
+ actionId: state ? 0 : 1,
233
+ jumpBoost: 0,
234
+ });
235
+ }
236
+ };
237
+
238
+ bot.ashGetControlState = (control) => {
239
+ assert.ok(control in controlState, `invalid control: ${control}`);
240
+ return controlState[control];
241
+ };
242
+
243
+ bot.ashClearControlStates = () => {
244
+ for (const control in controlState) {
245
+ bot.ashSetControlState(control, false);
246
+ }
247
+ };
248
+
249
+ bot.ashControlState = {};
250
+ for (const control of Object.keys(controlState)) {
251
+ Object.defineProperty(bot.ashControlState, control, {
252
+ get() {
253
+ return controlState[control];
254
+ },
255
+ set(state) {
256
+ bot.ashSetControlState(control, state);
257
+ return state;
258
+ },
259
+ });
260
+ }
261
+
262
+ let lookingTask = createDoneTask();
263
+
264
+ bot.on("move", () => {
265
+ if (
266
+ !lookingTask.done &&
267
+ Math.abs(deltaYaw(bot.entity.yaw, lastSentYaw)) < 0.001
268
+ ) {
269
+ lookingTask.finish();
270
+ }
271
+ });
272
+
273
+ bot.look = async (yaw, pitch, force) => {
274
+ if (!lookingTask.done) {
275
+ lookingTask.finish();
276
+ }
277
+ lookingTask = createTask();
278
+
279
+ const sensitivity = conv.fromNotchianPitch(0.15);
280
+ const yawChange =
281
+ Math.round((yaw - bot.entity.yaw) / sensitivity) * sensitivity;
282
+ const pitchChange =
283
+ Math.round((pitch - bot.entity.pitch) / sensitivity) * sensitivity;
284
+
285
+ if (yawChange === 0 && pitchChange === 0) {
286
+ return;
287
+ }
288
+
289
+ bot.entity.yaw += yawChange;
290
+ bot.entity.pitch += pitchChange;
291
+
292
+ if (force) {
293
+ lastSentYaw = yaw;
294
+ lastSentPitch = pitch;
295
+ return;
296
+ }
297
+
298
+ await lookingTask.promise;
299
+ };
300
+
301
+ bot.lookAt = async (point, force) => {
302
+ const delta = point.minus(
303
+ bot.entity.position.offset(0, bot.entity.eyeHeight, 0),
304
+ );
305
+ const yaw = Math.atan2(-delta.x, -delta.z);
306
+ const groundDistance = Math.sqrt(delta.x * delta.x + delta.z * delta.z);
307
+ const pitch = Math.atan2(delta.y, groundDistance);
308
+ await bot.look(yaw, pitch, force);
309
+ };
310
+
311
+ bot.elytraFly = async () => {
312
+ if (bot.entity.elytraFlying) {
313
+ throw new Error("Already elytra flying");
314
+ } else if (bot.entity.onGround) {
315
+ throw new Error("Unable to fly from ground");
316
+ } else if (bot.entity.isInWater) {
317
+ throw new Error("Unable to elytra fly while in water");
318
+ }
319
+
320
+ const mcData = require("minecraft-data")(bot.version);
321
+ if (getEffectLevel(mcData, "Levitation", bot.entity.effects) > 0) {
322
+ throw new Error("Unable to elytra fly with levitation effect");
323
+ }
324
+
325
+ const torsoSlot = bot.getEquipmentDestSlot("torso");
326
+ const item = bot.inventory.slots[torsoSlot];
327
+ if (item == null || item.name !== "elytra") {
328
+ throw new Error("Elytra must be equipped to start flying");
329
+ }
330
+
331
+ bot._client.write("entity_action", {
332
+ entityId: bot.entity.id,
333
+ actionId: 8,
334
+ jumpBoost: 0,
335
+ });
336
+ };
337
+
338
+ bot.waitForTicks = async function (ticks) {
339
+ if (ticks <= 0) return;
340
+
341
+ await new Promise((resolve) => {
342
+ const tickListener = () => {
343
+ ticks--;
344
+ if (ticks === 0) {
345
+ bot.removeListener("physicsTick", tickListener);
346
+ resolve();
347
+ }
348
+ };
349
+ bot.on("physicsTick", tickListener);
350
+ });
351
+ };
352
+
353
+ // Deprecated event warning
354
+ bot.on("newListener", (name) => {
355
+ if (name === "physicTick") {
356
+ console.warn(
357
+ "Mineflayer: You are using a deprecated event (physicTick)! Use physicsTick instead.",
358
+ );
359
+ }
360
+ });
361
+
362
+ // Track crawling/pose state from server-confirmed entity metadata
363
+ bot._client.on("entity_metadata", (packet) => {
364
+ if (!bot.entity || packet.entityId !== bot.entity.id) return;
365
+
366
+ const entity = bot.entity;
367
+
368
+ if (bot.supportFeature("mcDataHasEntityMetadata")) {
369
+ const metadataKeys =
370
+ bot.registry.entitiesByName[entity.name]?.metadataKeys;
371
+ if (!metadataKeys) return;
372
+
373
+ const metas = Object.fromEntries(
374
+ packet.metadata.map((e) => [metadataKeys[e.key], e.value]),
375
+ );
376
+
377
+ if (metas.pose != null) {
378
+ entity._pose = metas.pose;
379
+ entity.isElytra = metas.pose === 1;
380
+ entity.isSwimmingPose = metas.pose === 3;
381
+ // Crawling is swimming pose but not in water
382
+ entity.isCrawling = metas.pose === 3 && !entity.isInWater;
383
+
384
+ bot.emit("botPoseUpdate", {
385
+ pose: metas.pose,
386
+ crawling: entity.isCrawling,
387
+ swimming: entity.isSwimmingPose && entity.isInWater,
388
+ elytra: entity.isElytra,
389
+ });
390
+ }
391
+ }
392
+ });
393
+
394
+ // Entity velocity — handles being pushed by players, mobs, minecarts, etc.
395
+ //this shit does not work for mineflayer bots :(
396
+ bot._client.on("entity_velocity", (packet) => {
397
+ if (packet.entityId !== bot.entity.id) return;
398
+ if (!bot.ashPhysicsEnabled) return;
399
+
400
+ bot.entity.velocity.x += packet.velocityX / 8000;
401
+ bot.entity.velocity.y += packet.velocityY / 8000;
402
+ bot.entity.velocity.z += packet.velocityZ / 8000;
403
+ });
404
+
405
+ // Explosion knockbakc
406
+ bot._client.on("explosion", (explosion) => {
407
+ if (bot.ashPhysicsEnabled && bot.game.gameMode !== "creative") {
408
+ if (explosion.playerKnockback) {
409
+ // 1.21.3+
410
+ bot.entity.velocity.add(
411
+ explosion.playerMotionX,
412
+ explosion.playerMotionY,
413
+ explosion.playerMotionZ,
414
+ );
415
+ } else if ("playerMotionX" in explosion) {
416
+ // older versions
417
+ bot.entity.velocity.x += explosion.playerMotionX;
418
+ bot.entity.velocity.y += explosion.playerMotionY;
419
+ bot.entity.velocity.z += explosion.playerMotionZ;
420
+ }
421
+ }
422
+ });
423
+
424
+ // Handle player rotation packet (1.21.3+)
425
+ bot._client.on("player_rotation", (packet) => {
426
+ bot.entity.yaw = conv.fromNotchianYaw(packet.yaw);
427
+ bot.entity.pitch = conv.fromNotchianPitch(packet.pitch);
428
+ });
429
+
430
+ // Handle position updates from server
431
+ bot._client.on("position", (packet) => {
432
+ bot.entity.height = 1.8;
433
+
434
+ const vel = bot.entity.velocity;
435
+ const pos = bot.entity.position;
436
+ let newYaw, newPitch;
437
+
438
+ if (bot.registry.version[">="]("1.21.3")) {
439
+ const flags = packet.flags;
440
+ vel.set(flags.x ? vel.x : 0, flags.y ? vel.y : 0, flags.z ? vel.z : 0);
441
+ pos.set(
442
+ flags.x ? pos.x + packet.x : packet.x,
443
+ flags.y ? pos.y + packet.y : packet.y,
444
+ flags.z ? pos.z + packet.z : packet.z,
445
+ );
446
+ newYaw =
447
+ (flags.yaw ? conv.toNotchianYaw(bot.entity.yaw) : 0) + packet.yaw;
448
+ newPitch =
449
+ (flags.pitch ? conv.toNotchianPitch(bot.entity.pitch) : 0) +
450
+ packet.pitch;
451
+ } else {
452
+ vel.set(
453
+ packet.flags & 1 ? vel.x : 0,
454
+ packet.flags & 2 ? vel.y : 0,
455
+ packet.flags & 4 ? vel.z : 0,
456
+ );
457
+ pos.set(
458
+ packet.flags & 1 ? pos.x + packet.x : packet.x,
459
+ packet.flags & 2 ? pos.y + packet.y : packet.y,
460
+ packet.flags & 4 ? pos.z + packet.z : packet.z,
461
+ );
462
+ newYaw =
463
+ (packet.flags & 8 ? conv.toNotchianYaw(bot.entity.yaw) : 0) +
464
+ packet.yaw;
465
+ newPitch =
466
+ (packet.flags & 16 ? conv.toNotchianPitch(bot.entity.pitch) : 0) +
467
+ packet.pitch;
468
+ }
469
+
470
+ bot.entity.yaw = conv.fromNotchianYaw(newYaw);
471
+ bot.entity.pitch = conv.fromNotchianPitch(newPitch);
472
+ bot.entity.onGround = false;
473
+
474
+ sendPacketPositionAndLook(pos, newYaw, newPitch, bot.entity.onGround);
475
+
476
+ shouldUsePhysics = true;
477
+ bot.jumpTicks = 0;
478
+ lastSentYaw = bot.entity.yaw;
479
+ lastSentPitch = bot.entity.pitch;
480
+
481
+ bot.emit("forcedMove");
482
+ });
483
+
484
+ bot.on("mount", () => {
485
+ shouldUsePhysics = false;
486
+ });
487
+ bot.on("respawn", () => {
488
+ shouldUsePhysics = false;
489
+ });
490
+
491
+ bot.on("login", () => {
492
+ shouldUsePhysics = false;
493
+ lastSentYaw = bot.entity.yaw;
494
+ lastSentPitch = bot.entity.pitch;
495
+ if (doPhysicsTimer === null) {
496
+ doPhysicsTimer = setInterval(doPhysics, PHYSICS_INTERVAL_MS);
497
+ }
498
+ });
499
+
500
+ bot.on("end", cleanup);
501
+ }
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@miner-org/mineflayer-physics-reworked",
3
+ "version": "0.0.2",
4
+ "scripts": {
5
+ "npm-publish-patch": "npm version patch && npm publish --access public && git push origin main --tags",
6
+ "npm-publish-minor": "npm version minor && npm publish --access public && git push origin main --tags",
7
+ "npm-publish-major": "npm version major && npm publish --access public && git push origin main --tags"
8
+ },
9
+ "keywords": [
10
+ "mineflayer-physics"
11
+ ],
12
+ "author": "Ash",
13
+ "license": "ISC",
14
+ "description": "Mineflayer physics but with extra stuff like crawling support and scaffolding support",
15
+ "dependencies": {
16
+ "mineflayer": "^4.25.0"
17
+ }
18
+ }
package/src/aabb.js ADDED
@@ -0,0 +1,107 @@
1
+ class AABB {
2
+ constructor (x0, y0, z0, x1, y1, z1) {
3
+ this.minX = x0
4
+ this.minY = y0
5
+ this.minZ = z0
6
+ this.maxX = x1
7
+ this.maxY = y1
8
+ this.maxZ = z1
9
+ }
10
+
11
+ clone () {
12
+ return new AABB(this.minX, this.minY, this.minZ, this.maxX, this.maxY, this.maxZ)
13
+ }
14
+
15
+ floor () {
16
+ this.minX = Math.floor(this.minX)
17
+ this.minY = Math.floor(this.minY)
18
+ this.minZ = Math.floor(this.minZ)
19
+ this.maxX = Math.floor(this.maxX)
20
+ this.maxY = Math.floor(this.maxY)
21
+ this.maxZ = Math.floor(this.maxZ)
22
+ }
23
+
24
+ extend (dx, dy, dz) {
25
+ if (dx < 0) this.minX += dx
26
+ else this.maxX += dx
27
+
28
+ if (dy < 0) this.minY += dy
29
+ else this.maxY += dy
30
+
31
+ if (dz < 0) this.minZ += dz
32
+ else this.maxZ += dz
33
+
34
+ return this
35
+ }
36
+
37
+ contract (x, y, z) {
38
+ this.minX += x
39
+ this.minY += y
40
+ this.minZ += z
41
+ this.maxX -= x
42
+ this.maxY -= y
43
+ this.maxZ -= z
44
+ return this
45
+ }
46
+
47
+ expand (x, y, z) {
48
+ this.minX -= x
49
+ this.minY -= y
50
+ this.minZ -= z
51
+ this.maxX += x
52
+ this.maxY += y
53
+ this.maxZ += z
54
+ return this
55
+ }
56
+
57
+ offset (x, y, z) {
58
+ this.minX += x
59
+ this.minY += y
60
+ this.minZ += z
61
+ this.maxX += x
62
+ this.maxY += y
63
+ this.maxZ += z
64
+ return this
65
+ }
66
+
67
+ computeOffsetX (other, offsetX) {
68
+ if (other.maxY > this.minY && other.minY < this.maxY && other.maxZ > this.minZ && other.minZ < this.maxZ) {
69
+ if (offsetX > 0.0 && other.maxX <= this.minX) {
70
+ offsetX = Math.min(this.minX - other.maxX, offsetX)
71
+ } else if (offsetX < 0.0 && other.minX >= this.maxX) {
72
+ offsetX = Math.max(this.maxX - other.minX, offsetX)
73
+ }
74
+ }
75
+ return offsetX
76
+ }
77
+
78
+ computeOffsetY (other, offsetY) {
79
+ if (other.maxX > this.minX && other.minX < this.maxX && other.maxZ > this.minZ && other.minZ < this.maxZ) {
80
+ if (offsetY > 0.0 && other.maxY <= this.minY) {
81
+ offsetY = Math.min(this.minY - other.maxY, offsetY)
82
+ } else if (offsetY < 0.0 && other.minY >= this.maxY) {
83
+ offsetY = Math.max(this.maxY - other.minY, offsetY)
84
+ }
85
+ }
86
+ return offsetY
87
+ }
88
+
89
+ computeOffsetZ (other, offsetZ) {
90
+ if (other.maxX > this.minX && other.minX < this.maxX && other.maxY > this.minY && other.minY < this.maxY) {
91
+ if (offsetZ > 0.0 && other.maxZ <= this.minZ) {
92
+ offsetZ = Math.min(this.minZ - other.maxZ, offsetZ)
93
+ } else if (offsetZ < 0.0 && other.minZ >= this.maxZ) {
94
+ offsetZ = Math.max(this.maxZ - other.minZ, offsetZ)
95
+ }
96
+ }
97
+ return offsetZ
98
+ }
99
+
100
+ intersects (other) {
101
+ return this.minX < other.maxX && this.maxX > other.minX &&
102
+ this.minY < other.maxY && this.maxY > other.minY &&
103
+ this.minZ < other.maxZ && this.maxZ > other.minZ
104
+ }
105
+ }
106
+
107
+ module.exports = AABB
@@ -0,0 +1,46 @@
1
+ exports.getAttributeValue = function (prop) {
2
+ let x = prop.value;
3
+ for (const mod of prop.modifiers) {
4
+ if (mod.operation !== 0) continue;
5
+ x += mod.amount;
6
+ }
7
+ let y = x;
8
+ for (const mod of prop.modifiers) {
9
+ if (mod.operation !== 1) continue;
10
+ y += x * mod.amount;
11
+ }
12
+ for (const mod of prop.modifiers) {
13
+ if (mod.operation !== 2) continue;
14
+ y += y * mod.amount;
15
+ }
16
+ return y;
17
+ };
18
+
19
+ exports.createAttributeValue = function (base) {
20
+ const attributes = {
21
+ value: base,
22
+ modifiers: [],
23
+ };
24
+ return attributes;
25
+ };
26
+
27
+ exports.addAttributeModifier = function (attributes, modifier) {
28
+ const end = attributes.modifiers.length;
29
+ // add modifer at the end
30
+ attributes.modifiers[end] = modifier;
31
+ return attributes;
32
+ };
33
+
34
+ exports.checkAttributeModifier = function (attributes, uuid) {
35
+ for (const modifier of attributes.modifiers) {
36
+ if (modifier.uuid === uuid) return true;
37
+ }
38
+ return false;
39
+ };
40
+
41
+ exports.deleteAttributeModifier = function (attributes, uuid) {
42
+ attributes.modifiers = attributes.modifiers.filter(
43
+ (modifier) => modifier.uuid !== uuid,
44
+ );
45
+ return attributes;
46
+ };