@nxg-org/mineflayer-util-plugin 1.0.0

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.
Files changed (45) hide show
  1. package/LICENSE +674 -0
  2. package/lib/WorldFunctions.d.ts +10 -0
  3. package/lib/WorldFunctions.js +18 -0
  4. package/lib/calcs/aabb.d.ts +36 -0
  5. package/lib/calcs/aabb.js +194 -0
  6. package/lib/commonSense.d.ts +22 -0
  7. package/lib/commonSense.js +220 -0
  8. package/lib/customImplementations/inventory.d.ts +10 -0
  9. package/lib/customImplementations/inventory.js +95 -0
  10. package/lib/entityFunctions.d.ts +36 -0
  11. package/lib/entityFunctions.js +78 -0
  12. package/lib/filterFunctions.d.ts +23 -0
  13. package/lib/filterFunctions.js +45 -0
  14. package/lib/index.d.ts +27 -0
  15. package/lib/index.js +7 -0
  16. package/lib/inventoryFunctions.d.ts +45 -0
  17. package/lib/inventoryFunctions.js +208 -0
  18. package/lib/mathUtil.d.ts +23 -0
  19. package/lib/mathUtil.js +49 -0
  20. package/lib/movementFunctions.d.ts +43 -0
  21. package/lib/movementFunctions.js +109 -0
  22. package/lib/predictiveFunctions.d.ts +25 -0
  23. package/lib/predictiveFunctions.js +172 -0
  24. package/lib/utilFunctions.d.ts +50 -0
  25. package/lib/utilFunctions.js +140 -0
  26. package/lib/worldRelated/predictiveWorld.d.ts +41 -0
  27. package/lib/worldRelated/predictiveWorld.js +108 -0
  28. package/lib/worldRelated/raycastIterator.d.ts +45 -0
  29. package/lib/worldRelated/raycastIterator.js +114 -0
  30. package/package.json +33 -0
  31. package/src/WorldFunctions.ts +19 -0
  32. package/src/calcs/aabb.ts +218 -0
  33. package/src/commonSense.ts +189 -0
  34. package/src/customImplementations/inventory.ts +90 -0
  35. package/src/entityFunctions.ts +71 -0
  36. package/src/filterFunctions.ts +54 -0
  37. package/src/index.ts +29 -0
  38. package/src/inventoryFunctions.ts +187 -0
  39. package/src/mathUtil.ts +56 -0
  40. package/src/movementFunctions.ts +116 -0
  41. package/src/predictiveFunctions.ts +187 -0
  42. package/src/utilFunctions.ts +152 -0
  43. package/src/worldRelated/predictiveWorld.ts +109 -0
  44. package/src/worldRelated/raycastIterator.ts +136 -0
  45. package/tsconfig.json +17 -0
@@ -0,0 +1,187 @@
1
+ import type { Effects } from "minecraft-data";
2
+ import type { Bot } from "mineflayer";
3
+ import type { Entity } from "prismarine-entity";
4
+ import type { Item, NormalizedEnchant } from "prismarine-item";
5
+ import md from "minecraft-data";
6
+ import { Vec3 } from "vec3";
7
+ import { Overwrites, PredictiveWorld } from "./worldRelated/predictiveWorld";
8
+ import type { Block } from "prismarine-block";
9
+
10
+ const armorPieces = ["head", "torso", "legs", "feet"];
11
+
12
+ // https://minecraft.fandom.com/wiki/Explosion
13
+ // Use bot.world, there's no typing yet.
14
+ function calcExposure(playerPos: Vec3, explosionPos: Vec3, world: any) {
15
+ const dx = 1 / (0.6 * 2 + 1);
16
+ const dy = 1 / (1.8 * 2 + 1);
17
+ const dz = 1 / (0.6 * 2 + 1);
18
+
19
+ const d3 = (1 - Math.floor(1 / dx) * dx) / 2;
20
+ const d4 = (1 - Math.floor(1 / dz) * dz) / 2;
21
+
22
+ let sampled = 0;
23
+ let exposed = 0;
24
+ const pos = new Vec3(0, 0, 0);
25
+ for (pos.y = playerPos.y; pos.y <= playerPos.y + 1.8; pos.y += 1.8 * dy) {
26
+ for (pos.x = playerPos.x - 0.3 + d3; pos.x <= playerPos.x + 0.3; pos.x += 0.6 * dx) {
27
+ for (pos.z = playerPos.z - 0.3 + d4; pos.z <= playerPos.z + 0.3; pos.z += 0.6 * dz) {
28
+ const dir = pos.minus(explosionPos);
29
+ const range = dir.norm();
30
+ if (world.raycast(explosionPos, dir.normalize(), range) === null) {
31
+ exposed++;
32
+ }
33
+ sampled++;
34
+ }
35
+ }
36
+ }
37
+ return exposed / sampled;
38
+ }
39
+
40
+ // https://minecraft.fandom.com/wiki/Armor#Damage_protection
41
+ function getDamageAfterAbsorb(damages: number, armorValue: number, toughness: number) {
42
+ const var3 = 2 + toughness / 4;
43
+ const var4 = Math.min(Math.max(armorValue - damages / var3, armorValue * 0.2), 20);
44
+ return damages * (1 - var4 / 25);
45
+ }
46
+
47
+ // https://minecraft.fandom.com/wiki/Attribute#Operations
48
+ function getAttributeValue(prop: any) {
49
+ let X = prop.value;
50
+ for (const mod of prop.modifiers) {
51
+ if (mod.operation !== 0) continue;
52
+ X += mod.amount;
53
+ }
54
+ let Y = X;
55
+ for (const mod of prop.modifiers) {
56
+ if (mod.operation !== 1) continue;
57
+ Y += X * mod.amount;
58
+ }
59
+ for (const mod of prop.modifiers) {
60
+ if (mod.operation !== 2) continue;
61
+ Y += Y * mod.amount;
62
+ }
63
+ return Y;
64
+ }
65
+
66
+ function getDamageWithEnchantments(damage: number, equipment: Item[]) {
67
+ const enchantments = equipment.some((e) => !!e)
68
+ ? equipment
69
+ .map(
70
+ (armor) =>
71
+ armor?.enchants
72
+ .map((enchant: NormalizedEnchant) => {
73
+ switch (enchant?.name) {
74
+ case "protection":
75
+ return enchant.lvl;
76
+ case "blast_protection":
77
+ return enchant.lvl * 2;
78
+ default:
79
+ return 0;
80
+ }
81
+ })
82
+ .reduce((b: number, a: number) => b + a, 0) ?? [0]
83
+ )
84
+ .reduce((b: number, a: number) => b + a, 0)
85
+ : 0;
86
+ return damage * (1 - Math.min(enchantments, 20) / 25);
87
+ }
88
+
89
+ const DIFFICULTY_VALUES = {
90
+ peaceful: 0,
91
+ easy: 1,
92
+ normal: 2,
93
+ hard: 3,
94
+ };
95
+
96
+ export const ARMOR_THOUGHNESS_KEY = "generic.armorToughness";
97
+ const AIR_BLOCK = {type: 0};
98
+
99
+ export class PredictiveFunctions {
100
+ private damageMultiplier = 7; // for 1.12+ 8 for 1.8 TODO check when the change occur (likely 1.9)
101
+ private armorToughnessKey: string; // was renamed in 1.16
102
+ private armorProtectionKey: string;
103
+
104
+ private resistanceIndex = "11";
105
+
106
+ public world: PredictiveWorld;
107
+
108
+ constructor(private bot: Bot) {
109
+ if ((require('minecraft-data') as (typeof import('minecraft-data')))(bot.version).isNewerOrEqualTo("1.16")){
110
+ this.armorToughnessKey = "generic.armorToughness";
111
+ this.armorProtectionKey = "generic.armor";
112
+ } else {
113
+ this.armorToughnessKey = "generic.armorToughness";
114
+ this.armorProtectionKey = "generic.armor";
115
+ }
116
+ const effects = md(bot.version).effects;
117
+ for (const effectId in effects) {
118
+ const effect = effects[effectId];
119
+ if (effect.name.includes("resistance")) {
120
+ this.resistanceIndex = effectId;
121
+ break;
122
+ }
123
+ }
124
+
125
+ this.world = new PredictiveWorld(bot);
126
+ }
127
+
128
+ //There's a mistyping in mineflayer. Effect[] is not accurate. You cannot map over it.
129
+ getDamageWithEffects(damage: number, effects: { [id: string]: { id: number; amplifier: number; duration: number } }) {
130
+ const resistanceLevel = effects?.[this.resistanceIndex]?.amplifier ?? 0;
131
+ return damage * (1 - resistanceLevel / 5);
132
+ }
133
+
134
+ placeBlocks(blocks: Overwrites) {
135
+ this.world.setBlocks(blocks)
136
+ }
137
+
138
+ removePredictedBlocks(positions: Vec3[], force: boolean = false) {
139
+ this.world.removeBlocks(positions, force)
140
+ }
141
+
142
+ selfExplosionDamage(sourcePos: Vec3, power: number, rawDamages = false) {
143
+ const distance = this.bot.entity.position.distanceTo(sourcePos);
144
+ const radius = 2 * power;
145
+ if (distance >= radius) return 0;
146
+ const exposure = calcExposure(this.bot.entity.position, sourcePos, this.world);
147
+ const impact = (1 - distance / radius) * exposure;
148
+ let damages = Math.floor((impact * impact + impact) * this.damageMultiplier * power + 1);
149
+ // The following modifiers are constant for the input bot.entity and doesnt depend
150
+ // on the source position, so if the goal is to compare between positions they can be
151
+ // ignored to save computations
152
+ if (!rawDamages && this.bot.entity.attributes[this.armorProtectionKey]) {
153
+ const armor = getAttributeValue(this.bot.entity.attributes[this.armorProtectionKey]);
154
+ const armorToughness = getAttributeValue(this.bot.entity.attributes[this.armorToughnessKey]);
155
+ damages = getDamageAfterAbsorb(damages, armor, armorToughness);
156
+ const equipment = armorPieces.map((piece) => this.bot.inventory.slots[this.bot.getEquipmentDestSlot(piece)]);
157
+ damages = getDamageWithEnchantments(damages, equipment);
158
+ damages = this.getDamageWithEffects(damages, this.bot.entity.effects as any);
159
+ damages *= DIFFICULTY_VALUES[this.bot.game.difficulty] * 0.5;
160
+ }
161
+ return Math.floor(damages);
162
+ }
163
+
164
+ getExplosionDamage(targetEntity: Entity, sourcePos: Vec3, power: number, rawDamages = false) {
165
+ const distance = targetEntity.position.distanceTo(sourcePos);
166
+ const radius = 2 * power;
167
+ if (distance >= radius) return 0;
168
+ const exposure = calcExposure(targetEntity.position, sourcePos, this.world);
169
+ const impact = (1 - distance / radius) * exposure;
170
+ let damages = Math.floor((impact * impact + impact) * this.damageMultiplier * power + 1);
171
+ // The following modifiers are constant for the input targetEntity and doesnt depend
172
+ // on the source position, so if the goal is to compare between positions they can be
173
+ // ignored to save computations
174
+ if (!rawDamages && targetEntity.attributes[this.armorProtectionKey]) {
175
+ const armor = getAttributeValue(targetEntity.attributes[this.armorProtectionKey]);
176
+ const armorToughness = getAttributeValue(targetEntity.attributes[this.armorToughnessKey]);
177
+ damages = getDamageAfterAbsorb(damages, armor, armorToughness);
178
+ damages = getDamageWithEnchantments(damages, targetEntity.equipment);
179
+ damages = this.getDamageWithEffects(damages, targetEntity.effects as any);
180
+
181
+ if (targetEntity.type === "player") {
182
+ damages *= DIFFICULTY_VALUES[this.bot.game.difficulty] * 0.5;
183
+ }
184
+ }
185
+ return Math.floor(damages);
186
+ }
187
+ }
@@ -0,0 +1,152 @@
1
+ import type { Bot, EquipmentDestination, PrioGroups } from "mineflayer";
2
+ import { EntityFunctions } from "./entityFunctions";
3
+ import { FilterFunctions } from "./filterFunctions";
4
+ import { InventoryFunctions } from "./inventoryFunctions";
5
+ import { MovementFunctions } from "./movementFunctions";
6
+ import { promisify } from "util";
7
+ import type { Item } from "prismarine-item";
8
+ import { PredictiveFunctions } from "./predictiveFunctions";
9
+ import { MathFunctions } from "./mathUtil";
10
+ import { CommonSense } from "./commonSense";
11
+ import { WorldFunctions } from "./WorldFunctions";
12
+
13
+ /**
14
+ * I don't believe I need any locks, as I'm only going to have one instance of this per bot.
15
+ * This is added to bot context so multiple instances will exist in memory.
16
+ * Therefore, I don't need this. https://www.npmjs.com/package/async-lock
17
+ */
18
+
19
+ /**
20
+ * I may add listeners to this class, as halting until an item is equipped may be good.
21
+ */
22
+
23
+ /**
24
+ * I can't inherit from multiple classes. This language sucks.
25
+ * I'm not using mixins. Fuck you, fuck that.
26
+ * I'm just going to segregate these functions into separate categories
27
+ * because once again, fuck you.
28
+ *
29
+ */
30
+
31
+ type priorityStored = [priority: number, executor: () => Promise<void> | void];
32
+ export type BuiltInPriorityOptions = { group: PrioGroups; priority: number; returnIfRunning?: boolean; errCancel?: boolean };
33
+ export type CustomPriorityOptions = { priority: number; group?: PrioGroups; returnIfRunning?: boolean; errCancel?: boolean };
34
+ export class UtilFunctions {
35
+ public inv: InventoryFunctions;
36
+ public move: MovementFunctions;
37
+ public entity: EntityFunctions;
38
+ public predict: PredictiveFunctions;
39
+ public filters: FilterFunctions;
40
+ public math: MathFunctions;
41
+ public commonSense: CommonSense;
42
+ public world: WorldFunctions;
43
+ private builtInsPriorityStore: Partial<{ [funcName: string]: priorityStored[] }>;
44
+ private customPriorityStore: Partial<{ [funcName: string]: priorityStored[] }>;
45
+ private builtInCurrentExecuting: { [funcName: string]: priorityStored | undefined };
46
+ private customCurrentExecuting: { [funcName: string]: priorityStored | undefined };
47
+ constructor(public bot: Bot) {
48
+ this.inv = new InventoryFunctions(bot);
49
+ this.move = new MovementFunctions(bot);
50
+ this.entity = new EntityFunctions(bot);
51
+ this.predict = new PredictiveFunctions(bot);
52
+ this.filters = new FilterFunctions(bot);
53
+ this.commonSense = new CommonSense(bot);
54
+ this.world = new WorldFunctions(bot);
55
+ this.math = new MathFunctions();
56
+ this.builtInsPriorityStore = {};
57
+ this.customPriorityStore = {};
58
+ this.builtInCurrentExecuting = {};
59
+ this.customCurrentExecuting = {};
60
+ }
61
+
62
+ sleep = promisify(setTimeout);
63
+
64
+ isBuiltInsEmpty(name?: string) {
65
+ if (name) {
66
+ return !this.builtInsPriorityStore[name]?.length || !this.builtInCurrentExecuting[name];
67
+ } else {
68
+ return !Object.values(this.builtInsPriorityStore).length || !Object.values(this.builtInCurrentExecuting).length;
69
+ }
70
+ }
71
+
72
+ isCustomEmpty(name?: string) {
73
+ if (name) {
74
+ return !this.customPriorityStore[name]?.length && !this.customCurrentExecuting[name];
75
+ } else {
76
+ return !Object.values(this.customPriorityStore).length && !Object.values(this.customCurrentExecuting).length;
77
+ }
78
+ }
79
+
80
+ /**
81
+ *
82
+ * @param object \{priority, errCancel} => priority of function (highest order first), throw error if already running a function.
83
+ * @param func any custom function.
84
+ * @param args the arguments of passed in function.
85
+ * @returns Error if errCancel and already executing, otherwise result of function.
86
+ */
87
+ customPriority<K extends (...args: any) => any>(
88
+ { priority, group, returnIfRunning, errCancel }: CustomPriorityOptions,
89
+ func: K,
90
+ ...args: Parameters<K>
91
+ ): number | Promise<ReturnType<K> | Error> {
92
+ const name = group ?? func.name ?? "anonymous";
93
+ const actionQueue = (this.customPriorityStore[name] ??= []);
94
+ // console.log("custom", group ?? func.name ?? "anonymous", actionQueue, this.isCustomEmpty(name))
95
+ if (errCancel && actionQueue.length > 1) throw "already executing";
96
+ if (returnIfRunning && !this.isCustomEmpty(name)) return 1;
97
+ // console.log("running.")
98
+ return new Promise(async (res, rej) => {
99
+ const currentlyExecuting = actionQueue.shift();
100
+ if (currentlyExecuting) this.customCurrentExecuting[group ?? currentlyExecuting[1].name ?? "anonymous"] = currentlyExecuting;
101
+ const index = actionQueue.findIndex(([prio]) => priority > prio);
102
+ actionQueue.splice(index === -1 ? actionQueue.length : index, 0, [
103
+ priority,
104
+ async () => {
105
+ try {
106
+ res(await func(...(args as any)));
107
+ } catch (e) {
108
+ rej(e);
109
+ }
110
+ actionQueue.shift();
111
+ await actionQueue[0]?.[1]();
112
+ },
113
+ ]);
114
+ if (currentlyExecuting) {
115
+ actionQueue.unshift(currentlyExecuting);
116
+ this.customCurrentExecuting[group ?? currentlyExecuting[1].name ?? "anonymous"] = undefined;
117
+ } else await actionQueue[0][1]();
118
+ });
119
+ }
120
+
121
+ builtInsPriority<K extends (...args: any) => any>(
122
+ { group, priority, returnIfRunning, errCancel }: BuiltInPriorityOptions,
123
+ func: K,
124
+ ...args: Parameters<K>
125
+ ): number | Promise<ReturnType<K> | Error> {
126
+ const actionQueue = (this.builtInsPriorityStore[group] ??= []);
127
+ // console.log("builtin", group, actionQueue)
128
+ if (errCancel && !this.isBuiltInsEmpty(group)) throw "already executing";
129
+ if (returnIfRunning && !this.isBuiltInsEmpty(group)) return 1;
130
+ return new Promise(async (res, rej) => {
131
+ const currentlyExecuting = actionQueue.shift();
132
+ if (currentlyExecuting) this.customCurrentExecuting[group] = currentlyExecuting;
133
+ const index = actionQueue.findIndex(([prio]) => priority > prio);
134
+ actionQueue.splice(index === -1 ? actionQueue.length : index, 0, [
135
+ priority,
136
+ async () => {
137
+ try {
138
+ res(await func.bind(this.bot)(...(args as any)));
139
+ } catch (e) {
140
+ rej(e);
141
+ }
142
+ actionQueue.shift();
143
+ await actionQueue[0]?.[1]();
144
+ },
145
+ ]);
146
+ if (currentlyExecuting) {
147
+ actionQueue.unshift(currentlyExecuting);
148
+ this.builtInCurrentExecuting[group] = undefined;
149
+ } else await actionQueue[0][1]();
150
+ });
151
+ }
152
+ }
@@ -0,0 +1,109 @@
1
+ import type { Bot } from "mineflayer";
2
+ import type { Block } from "prismarine-block";
3
+ import { Vec3 } from "vec3";
4
+ import { RaycastIterator } from "./raycastIterator";
5
+
6
+ export type Overwrites = { [coord: string]: Block | null };
7
+
8
+ /**
9
+ * A class dedicated to predictive logic.
10
+ *
11
+ * Currently, this class can predict explosion damages of crystals using a custom world.
12
+ */
13
+ export class PredictiveWorld {
14
+ private blocks: Overwrites = {};
15
+ constructor(public bot: Bot) {}
16
+
17
+ raycast(from: Vec3, direction: Vec3, range: number, matcher: ((block: Block) => boolean) | null = null) {
18
+ const iter = new RaycastIterator(from, direction, range);
19
+ let pos = iter.next();
20
+ while (pos) {
21
+ const position = new Vec3(pos.x, pos.y, pos.z);
22
+ const block = this.getBlock(position);
23
+ if (block && (!matcher || matcher(block))) {
24
+ const intersect = iter.intersect(block.shapes as any, position);
25
+ if (intersect) {
26
+ //@ts-expect-error 2
27
+ block.face = intersect.face;
28
+ //@ts-expect-error
29
+ block.intersect = intersect.pos;
30
+ return block;
31
+ }
32
+ }
33
+ pos = iter.next();
34
+ }
35
+ return null;
36
+ }
37
+
38
+ /**
39
+ * this works
40
+ * @param {Block} block
41
+ */
42
+ setBlock(pos: Vec3, block: Block) {
43
+ this.blocks[pos.toString()] ??= block;
44
+ }
45
+
46
+ /**
47
+ * @param {Overwrites} blocks Blocks indexed by position.toString()
48
+ */
49
+ setBlocks(blocks: Overwrites) {
50
+ for (const index in blocks) this.blocks[index] = blocks[index];
51
+ }
52
+
53
+ /**
54
+ * @param {Vec3} pos
55
+ * @returns {Block} Block at position.
56
+ */
57
+ getBlock(pos: Vec3) {
58
+ const pblock = this.blocks[pos.toString()];
59
+ if (pblock !== undefined && pblock !== null) return pblock;
60
+ return this.bot.blockAt(pos);
61
+ }
62
+
63
+ removeBlock(pos: Vec3, force: boolean) {
64
+ if (force) {
65
+ delete this.blocks[pos.toString()];
66
+ } else {
67
+ const realBlock = this.bot.blockAt(pos);
68
+ if (realBlock) this.blocks[pos.toString()] = realBlock;
69
+ else delete this.blocks[pos.toString()];
70
+ }
71
+ }
72
+
73
+ removeBlocks(positions: Vec3[], force: boolean) {
74
+ positions.forEach((pos) => this.removeBlock(pos, force));
75
+ }
76
+
77
+ /**
78
+ * @param playerPos Position of effected entity.
79
+ * @param explosionPos Position of explosion origin.
80
+ * @param block bot.block
81
+ * @returns List of affected blocks that potentially protect the entity.
82
+ */
83
+ getExplosionAffectedBlocks(playerPos: Vec3, explosionPos: Vec3): Overwrites {
84
+ let blocks: Overwrites = {};
85
+ const dx = 1 / (0.6 * 2 + 1);
86
+ const dy = 1 / (1.8 * 2 + 1);
87
+ const dz = 1 / (0.6 * 2 + 1);
88
+
89
+ const d3 = (1 - Math.floor(1 / dx) * dx) / 2;
90
+ const d4 = (1 - Math.floor(1 / dz) * dz) / 2;
91
+
92
+ const pos = new Vec3(0, 0, 0);
93
+ for (pos.y = playerPos.y; pos.y <= playerPos.y + 1.8; pos.y += 1.8 * dy) {
94
+ for (pos.x = playerPos.x - 0.3 + d3; pos.x <= playerPos.x + 0.3; pos.x += 0.6 * dx) {
95
+ for (pos.z = playerPos.z - 0.3 + d4; pos.z <= playerPos.z + 0.3; pos.z += 0.6 * dz) {
96
+ const dir = pos.minus(explosionPos);
97
+ const range = dir.norm();
98
+ const potentialBlock = this.raycast(explosionPos, dir.normalize(), range);
99
+ if (potentialBlock !== null) blocks[potentialBlock.position.toString()] = potentialBlock;
100
+ }
101
+ }
102
+ }
103
+ return blocks;
104
+ }
105
+
106
+ loadExplosionAffectedBlocks(playerPos: Vec3, explosionPos: Vec3) {
107
+ this.setBlocks(this.getExplosionAffectedBlocks(playerPos, explosionPos));
108
+ }
109
+ }
@@ -0,0 +1,136 @@
1
+ import type { Bot } from "mineflayer";
2
+ import type { Block } from "prismarine-block";
3
+ import { Vec3 } from "vec3";
4
+
5
+ export enum BlockFace {
6
+ UNKNOWN = -999,
7
+ BOTTOM = 0,
8
+ TOP = 1,
9
+ NORTH = 2,
10
+ SOUTH = 3,
11
+ WEST = 4,
12
+ EAST = 5,
13
+ }
14
+
15
+ export class RaycastIterator {
16
+ block: { x: number; y: number; z: number; face: number };
17
+ blockVec: Vec3;
18
+ pos: Vec3;
19
+ dir: Vec3;
20
+ invDirX: number;
21
+ invDirY: number;
22
+ invDirZ: number;
23
+ stepX: number;
24
+ stepY: number;
25
+ stepZ: number;
26
+ tDeltaX: number;
27
+ tDeltaY: number;
28
+ tDeltaZ: number;
29
+ tMaxX: number;
30
+ tMaxY: number;
31
+ tMaxZ: number;
32
+ maxDistance: number;
33
+ constructor(pos: Vec3, dir: Vec3, maxDistance: number) {
34
+ this.block = {
35
+ x: Math.floor(pos.x),
36
+ y: Math.floor(pos.y),
37
+ z: Math.floor(pos.z),
38
+ face: BlockFace.UNKNOWN,
39
+ };
40
+
41
+ this.blockVec = new Vec3(Math.floor(pos.x), Math.floor(pos.y), Math.floor(pos.z));
42
+
43
+ this.pos = pos;
44
+ this.dir = dir;
45
+
46
+ this.invDirX = dir.x === 0 ? Number.MAX_VALUE : 1 / dir.x;
47
+ this.invDirY = dir.y === 0 ? Number.MAX_VALUE : 1 / dir.y;
48
+ this.invDirZ = dir.z === 0 ? Number.MAX_VALUE : 1 / dir.z;
49
+
50
+ this.stepX = Math.sign(dir.x);
51
+ this.stepY = Math.sign(dir.y);
52
+ this.stepZ = Math.sign(dir.z);
53
+
54
+ this.tDeltaX = dir.x === 0 ? Number.MAX_VALUE : Math.abs(1 / dir.x);
55
+ this.tDeltaY = dir.y === 0 ? Number.MAX_VALUE : Math.abs(1 / dir.y);
56
+ this.tDeltaZ = dir.z === 0 ? Number.MAX_VALUE : Math.abs(1 / dir.z);
57
+
58
+ this.tMaxX = dir.x === 0 ? Number.MAX_VALUE : Math.abs((this.block.x + (dir.x > 0 ? 1 : 0) - pos.x) / dir.x);
59
+ this.tMaxY = dir.y === 0 ? Number.MAX_VALUE : Math.abs((this.block.y + (dir.y > 0 ? 1 : 0) - pos.y) / dir.y);
60
+ this.tMaxZ = dir.z === 0 ? Number.MAX_VALUE : Math.abs((this.block.z + (dir.z > 0 ? 1 : 0) - pos.z) / dir.z);
61
+
62
+ this.maxDistance = maxDistance;
63
+ }
64
+
65
+ // Returns null if none of the shapes is intersected, otherwise returns intersect pos and face
66
+ // shapes are translated by offset
67
+ //[x0: number,y0: number,z0: number,x1:number,y1:number,z1:number][]
68
+ intersect(shapes: [x0: BlockFace, y0: BlockFace, z0: BlockFace, x1: BlockFace, y1: BlockFace, z1: BlockFace][], offset: Vec3) {
69
+ // Shapes is an array of shapes, each in the form of: [x0, y0, z0, x1, y1, z1]
70
+ let t = Number.MAX_VALUE;
71
+ let f = BlockFace.UNKNOWN;
72
+ const p = this.pos.minus(offset);
73
+ for (const shape of shapes) {
74
+ let tmin = (shape[this.invDirX > 0 ? 0 : 3] - p.x) * this.invDirX;
75
+ let tmax = (shape[this.invDirX > 0 ? 3 : 0] - p.x) * this.invDirX;
76
+ const tymin = (shape[this.invDirY > 0 ? 1 : 4] - p.y) * this.invDirY;
77
+ const tymax = (shape[this.invDirY > 0 ? 4 : 1] - p.y) * this.invDirY;
78
+
79
+ let face = this.stepX > 0 ? BlockFace.WEST : BlockFace.EAST;
80
+
81
+ if (tmin > tymax || tymin > tmax) continue;
82
+ if (tymin > tmin) {
83
+ tmin = tymin;
84
+ face = this.stepY > 0 ? BlockFace.BOTTOM : BlockFace.TOP;
85
+ }
86
+ if (tymax < tmax) tmax = tymax;
87
+
88
+ const tzmin = (shape[this.invDirZ > 0 ? 2 : 5] - p.z) * this.invDirZ;
89
+ const tzmax = (shape[this.invDirZ > 0 ? 5 : 2] - p.z) * this.invDirZ;
90
+
91
+ if (tmin > tzmax || tzmin > tmax) continue;
92
+ if (tzmin > tmin) {
93
+ tmin = tzmin;
94
+ face = this.stepZ > 0 ? BlockFace.NORTH : BlockFace.SOUTH;
95
+ }
96
+ if (tzmax < tmax) tmax = tzmax;
97
+
98
+ if (tmin < t) {
99
+ t = tmin;
100
+ f = face;
101
+ }
102
+ }
103
+ if (t === Number.MAX_VALUE) return null;
104
+ return { pos: this.pos.plus(this.dir.scaled(t)), face: f };
105
+ }
106
+
107
+ next() {
108
+ if (Math.min(Math.min(this.tMaxX, this.tMaxY), this.tMaxZ) > this.maxDistance) {
109
+ return null;
110
+ }
111
+
112
+ if (this.tMaxX < this.tMaxY) {
113
+ if (this.tMaxX < this.tMaxZ) {
114
+ this.block.x += this.stepX;
115
+ this.tMaxX += this.tDeltaX;
116
+ this.block.face = this.stepX > 0 ? BlockFace.WEST : BlockFace.EAST;
117
+ } else {
118
+ this.block.z += this.stepZ;
119
+ this.tMaxZ += this.tDeltaZ;
120
+ this.block.face = this.stepZ > 0 ? BlockFace.NORTH : BlockFace.SOUTH;
121
+ }
122
+ } else {
123
+ if (this.tMaxY < this.tMaxZ) {
124
+ this.block.y += this.stepY;
125
+ this.tMaxY += this.tDeltaY;
126
+ this.block.face = this.stepY > 0 ? BlockFace.BOTTOM : BlockFace.TOP;
127
+ } else {
128
+ this.block.z += this.stepZ;
129
+ this.tMaxZ += this.tDeltaZ;
130
+ this.block.face = this.stepZ > 0 ? BlockFace.NORTH : BlockFace.SOUTH;
131
+ }
132
+ }
133
+ if (isNaN(this.block.x) || isNaN(this.block.y) || isNaN(this.block.z)) return null;
134
+ return this.block;
135
+ }
136
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "module": "commonjs",
5
+ "declaration": true,
6
+ "strict": true,
7
+ "noImplicitAny": true,
8
+ "resolveJsonModule": true,
9
+ "moduleResolution": "node",
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "rootDir": "src/",
14
+ "outDir": "lib/",
15
+ "lib": ["es6", "dom", "es2017"]
16
+ }
17
+ }