mineflayer-cicerone 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.
- package/index.js +41 -0
- package/lib/digging.js +56 -0
- package/lib/movement.js +142 -0
- package/lib/path.js +103 -0
- package/lib/pathElement.js +112 -0
- package/lib/placement.js +128 -0
- package/lib/utils/inventory.js +61 -0
- package/lib/utils/item.js +39 -0
- package/package.json +24 -0
package/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const Path = require('./lib/path');
|
|
2
|
+
const { PathElement, ParentPathElement } = require('./lib/pathElement');
|
|
3
|
+
const injectMovement = require('./lib/movement');
|
|
4
|
+
const injectDigging = require('./lib/digging');
|
|
5
|
+
const injectPlacement = require('./lib/placement');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {import('mineflayer').Bot} bot
|
|
9
|
+
*/
|
|
10
|
+
function plugin(bot) {
|
|
11
|
+
if (!bot.cicerone) {
|
|
12
|
+
bot.cicerone = {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
bot.cicerone.settings = {
|
|
16
|
+
buildingBlocks: [
|
|
17
|
+
'cobblestone',
|
|
18
|
+
'dirt',
|
|
19
|
+
'stone',
|
|
20
|
+
'oak_planks',
|
|
21
|
+
'spruce_planks',
|
|
22
|
+
'birch_planks',
|
|
23
|
+
'jungle_planks',
|
|
24
|
+
'acacia_planks',
|
|
25
|
+
'dark_oak_planks',
|
|
26
|
+
'andesite',
|
|
27
|
+
'diorite',
|
|
28
|
+
'granite'
|
|
29
|
+
]
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
injectMovement(bot);
|
|
33
|
+
injectDigging(bot);
|
|
34
|
+
injectPlacement(bot);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
plugin.Path = Path;
|
|
38
|
+
plugin.PathElement = PathElement;
|
|
39
|
+
plugin.ParentPathElement = ParentPathElement;
|
|
40
|
+
|
|
41
|
+
module.exports = plugin;
|
package/lib/digging.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {import('mineflayer').Bot} bot
|
|
3
|
+
* */ const {harvestTool} = require("./utils/inventory");
|
|
4
|
+
module.exports = (bot) => {
|
|
5
|
+
bot.once('spawn', () => {
|
|
6
|
+
const originalDigTime = bot.digTime.bind(bot);
|
|
7
|
+
|
|
8
|
+
bot.digTime = (block) => {
|
|
9
|
+
if (!block) return 0;
|
|
10
|
+
|
|
11
|
+
const digTime = originalDigTime.call(bot, block);
|
|
12
|
+
const blockName = block.name;
|
|
13
|
+
const isSlowBlock = blockName.includes('_ore') || blockName === 'cobbled_deepslate'
|
|
14
|
+
|
|
15
|
+
return isSlowBlock ? Math.round(digTime * 1.5) + 250 : Math.round(digTime);
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Breaks the block at the specified coordinates.
|
|
21
|
+
*
|
|
22
|
+
* @param {import('vec3').Vec3} position - Position of the target block.
|
|
23
|
+
* @returns {Promise<void>}
|
|
24
|
+
* */
|
|
25
|
+
bot.cicerone.digBlock = async (position) => {
|
|
26
|
+
while (!bot.entity.onGround) {
|
|
27
|
+
await bot.waitForTicks(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let block = bot.blockAt(position);
|
|
31
|
+
if (!block || !block.name || block.name === 'air') return;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const tool = harvestTool(bot, block);
|
|
35
|
+
if (tool) {
|
|
36
|
+
await bot.equip(tool, 'hand');
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {
|
|
39
|
+
console.warn(`[Cicerone] Tool selection warning: ${e.message}. Attempting to dig by hand.`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
while (block.name !== 'air' && block.name !== 'cave_air') {
|
|
43
|
+
try {
|
|
44
|
+
await bot.lookAt(block.position.clone().offset(0.5, 0.5, 0.5), true);
|
|
45
|
+
await bot.waitForTicks(5);
|
|
46
|
+
await bot.dig(block, false, 'raycast');
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.error(`[Cicerone] Failed to dig block at ${position}:`, e.message);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await bot.waitForTicks(2);
|
|
53
|
+
block = bot.blockAt(position);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
package/lib/movement.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} GotoOptions
|
|
3
|
+
* @property {number} [precision=0.15] - Hit radius in blocks.
|
|
4
|
+
* @property {number} [timeout=30_000] - Time to move in ms.
|
|
5
|
+
* */
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {import('mineflayer').Bot} bot
|
|
9
|
+
* */
|
|
10
|
+
module.exports = (bot) => {
|
|
11
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
12
|
+
|
|
13
|
+
let isMoving = false;
|
|
14
|
+
let moveTimeout = null;
|
|
15
|
+
let tickListener = null;
|
|
16
|
+
|
|
17
|
+
let activeResolve = null;
|
|
18
|
+
let activeReject = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Internal method for completely stopping movement and cleaning up resources.
|
|
22
|
+
* @param {Error|null} [err] - If passed, the promise will complete with an error (rejection).
|
|
23
|
+
* */
|
|
24
|
+
const stopMovement = (err = null) => {
|
|
25
|
+
if (!isMoving) return;
|
|
26
|
+
|
|
27
|
+
isMoving = false;
|
|
28
|
+
bot.clearControlStates();
|
|
29
|
+
|
|
30
|
+
if (moveTimeout) {
|
|
31
|
+
clearTimeout(moveTimeout);
|
|
32
|
+
moveTimeout = null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (tickListener) {
|
|
36
|
+
bot.removeListener('physicsTick', tickListener);
|
|
37
|
+
tickListener = null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const resolve = activeResolve;
|
|
41
|
+
const reject = activeReject;
|
|
42
|
+
|
|
43
|
+
activeResolve = null;
|
|
44
|
+
activeReject = null;
|
|
45
|
+
|
|
46
|
+
if (err) {
|
|
47
|
+
if (reject) reject(err);
|
|
48
|
+
} else {
|
|
49
|
+
if (resolve) resolve();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Moves the bot to the specified point along the horizontal plane (X, Z).
|
|
55
|
+
*
|
|
56
|
+
* @param {import('vec3').Vec3} vec - Coordinates of the target point.
|
|
57
|
+
* @param {GotoOptions} [options] - Movement settings.
|
|
58
|
+
* @returns {Promise<void>} - Allowed on successful arrival, rejected on error/timeout/abortion.
|
|
59
|
+
* */
|
|
60
|
+
bot.cicerone.goto = async (vec, options = {}) => {
|
|
61
|
+
const precision = options.precision ?? 0.15;
|
|
62
|
+
const timeoutMs = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
63
|
+
|
|
64
|
+
if (isMoving) {
|
|
65
|
+
stopMovement(new Error('Movement interrupted by a new goto call.'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
isMoving = true;
|
|
69
|
+
|
|
70
|
+
// Offset target to block center.
|
|
71
|
+
const targetX = vec.x + 0.5;
|
|
72
|
+
const targetZ = vec.z + 0.5;
|
|
73
|
+
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
activeResolve = resolve;
|
|
76
|
+
activeReject = reject;
|
|
77
|
+
|
|
78
|
+
let lastDistance = Infinity;
|
|
79
|
+
|
|
80
|
+
moveTimeout = setTimeout(() => {
|
|
81
|
+
stopMovement(new Error(`Movement timeout at position: ${vec}`));
|
|
82
|
+
}, timeoutMs );
|
|
83
|
+
|
|
84
|
+
tickListener = () => {
|
|
85
|
+
if (!bot.entity) return;
|
|
86
|
+
|
|
87
|
+
const pos = bot.entity.position;
|
|
88
|
+
const dx = targetX - pos.x;
|
|
89
|
+
const dz = targetZ - pos.z;
|
|
90
|
+
const distance = Math.sqrt(dx * dx + dz * dz);
|
|
91
|
+
|
|
92
|
+
// Successful arrival
|
|
93
|
+
if (distance <= precision) {
|
|
94
|
+
stopMovement();
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (distance > lastDistance + 0.05 && distance < 0.8) {
|
|
99
|
+
stopMovement();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
lastDistance = distance;
|
|
104
|
+
|
|
105
|
+
const targetYaw = Math.atan2(-dx, -dz);
|
|
106
|
+
|
|
107
|
+
// Normalization of angle differences to the range [-PI, PI]
|
|
108
|
+
let diffYaw = (targetYaw - bot.entity.yaw) % (Math.PI * 2);
|
|
109
|
+
if (diffYaw > Math.PI) diffYaw -= Math.PI * 2;
|
|
110
|
+
if (diffYaw < -Math.PI) diffYaw += Math.PI * 2;
|
|
111
|
+
|
|
112
|
+
bot.look(targetYaw, bot.entity.pitch, true).catch(() => {});
|
|
113
|
+
|
|
114
|
+
// Starts walking forward only if the bot is looking roughly in the direction of the target.
|
|
115
|
+
// Margin of error ~2.86 degrees.
|
|
116
|
+
if (Math.abs(diffYaw) < 0.05) {
|
|
117
|
+
bot.setControlState('forward', true);
|
|
118
|
+
} else {
|
|
119
|
+
bot.setControlState('forward', false);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// It slows down (sneak) when getting very close, to avoid overshooting the point.
|
|
123
|
+
if (distance < 0.6) {
|
|
124
|
+
bot.setControlState('sneak', true);
|
|
125
|
+
} else {
|
|
126
|
+
bot.setControlState('sneak', false);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
bot.on('physicsTick', tickListener);
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Forcefully stops the bot's current movement.
|
|
136
|
+
* */
|
|
137
|
+
bot.cicerone.stopMove = () => {
|
|
138
|
+
if (isMoving) {
|
|
139
|
+
stopMovement(new Error('Movement was forcefully interrupted.'));
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
};
|
package/lib/path.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const vec3 = require('vec3');
|
|
2
|
+
const { PathElement, ParentPathElement } = require('./pathElement');
|
|
3
|
+
|
|
4
|
+
class Path {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.pathElements = [];
|
|
7
|
+
this.lastParentElement = null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Adds a new element to the path.
|
|
12
|
+
*
|
|
13
|
+
* @param {import('vec3').Vec3} position
|
|
14
|
+
* @param {string} type
|
|
15
|
+
*/
|
|
16
|
+
add(position, type) {
|
|
17
|
+
if (this.pathElements.length === 0 || type === 'move') {
|
|
18
|
+
const parent = new ParentPathElement(position, type);
|
|
19
|
+
this.pathElements.push(parent);
|
|
20
|
+
this.lastParentElement = parent;
|
|
21
|
+
} else if (this.lastParentElement) {
|
|
22
|
+
const child = new PathElement(position, type);
|
|
23
|
+
this.lastParentElement.addChild(child);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Clear path.
|
|
29
|
+
* */
|
|
30
|
+
reset() {
|
|
31
|
+
this.pathElements = [];
|
|
32
|
+
this.lastParentElement = null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Performs the route.
|
|
37
|
+
*
|
|
38
|
+
* @param {import('mineflayer').Bot} bot
|
|
39
|
+
*/
|
|
40
|
+
async resolve(bot) {
|
|
41
|
+
while (this.pathElements.length > 0) {
|
|
42
|
+
const currentParent = this.pathElements.shift();
|
|
43
|
+
await currentParent.resolve(bot);
|
|
44
|
+
|
|
45
|
+
while (currentParent.hasChildren()) {
|
|
46
|
+
const child = currentParent.children.shift();
|
|
47
|
+
await child.resolve(bot);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Performs the return route.
|
|
54
|
+
*
|
|
55
|
+
* @param {import('mineflayer').Bot} bot
|
|
56
|
+
*/
|
|
57
|
+
async revert(bot) {
|
|
58
|
+
while (this.pathElements.length > 0) {
|
|
59
|
+
const currentParent = this.pathElements.pop();
|
|
60
|
+
await currentParent.revert(bot);
|
|
61
|
+
|
|
62
|
+
while (currentParent.hasChildren()) {
|
|
63
|
+
const child = currentParent.children.pop();
|
|
64
|
+
await child.revert(bot);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
toJSON() {
|
|
70
|
+
return this.pathElements;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reconstructs the path from JSON format.
|
|
75
|
+
*
|
|
76
|
+
* @param {Array|string} json
|
|
77
|
+
* @returns {Path}
|
|
78
|
+
*/
|
|
79
|
+
static fromJSON(json) {
|
|
80
|
+
const rawElements = typeof json === 'string' ? JSON.parse(json) : json;
|
|
81
|
+
const pathInstance = new Path();
|
|
82
|
+
|
|
83
|
+
pathInstance.pathElements = rawElements.map(parentRaw => {
|
|
84
|
+
const parentPos = new vec3(parentRaw.position.x, parentRaw.position.y, parentRaw.position.z);
|
|
85
|
+
const parent = new ParentPathElement(parentPos, parentRaw.type);
|
|
86
|
+
|
|
87
|
+
if (parentRaw.children && Array.isArray(parentRaw.children)) {
|
|
88
|
+
parentRaw.children.forEach(childRaw => {
|
|
89
|
+
const childPos = new vec3(childRaw.position.x, childRaw.position.y, childRaw.position.z);
|
|
90
|
+
const child = new PathElement(childPos, childRaw.type);
|
|
91
|
+
parent.addChild(child);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return parent;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
pathInstance.lastParentElement = pathInstance.pathElements[pathInstance.pathElements.length - 1] || null;
|
|
98
|
+
|
|
99
|
+
return pathInstance;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = Path;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const elementType = Object.freeze({
|
|
2
|
+
move: 'move',
|
|
3
|
+
dig: 'dig',
|
|
4
|
+
place: 'place',
|
|
5
|
+
down: 'down',
|
|
6
|
+
up: 'up',
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const resolveMap = Object.freeze({
|
|
10
|
+
[elementType.move]: async (bot, position) => {
|
|
11
|
+
await bot.cicerone.goto(position);
|
|
12
|
+
},
|
|
13
|
+
[elementType.dig]: async (bot, position) => {
|
|
14
|
+
await bot.cicerone.digBlock(position);
|
|
15
|
+
},
|
|
16
|
+
[elementType.place]: async (bot, position) => {
|
|
17
|
+
await bot.cicerone.placeBuildingBlock(position);
|
|
18
|
+
},
|
|
19
|
+
[elementType.down]: async (bot, position) => {
|
|
20
|
+
await bot.cicerone.digBlock(position);
|
|
21
|
+
},
|
|
22
|
+
[elementType.up]: async (bot, _) => {
|
|
23
|
+
await bot.cicerone.buildUp();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const revertMap = Object.freeze({
|
|
28
|
+
[elementType.move]: async (bot, position) => {
|
|
29
|
+
await bot.cicerone.goto(position);
|
|
30
|
+
},
|
|
31
|
+
[elementType.dig]: async (bot, position) => {
|
|
32
|
+
await bot.cicerone.placeBuildingBlock(position);
|
|
33
|
+
},
|
|
34
|
+
[elementType.place]: async (bot, position) => {
|
|
35
|
+
await bot.cicerone.digBlock(position);
|
|
36
|
+
},
|
|
37
|
+
[elementType.down]: async (bot, _) => {
|
|
38
|
+
await bot.cicerone.buildUp();
|
|
39
|
+
},
|
|
40
|
+
[elementType.up]: async (bot, position) => {
|
|
41
|
+
await bot.cicerone.digBlock(position);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
class PathElement {
|
|
46
|
+
/**
|
|
47
|
+
* @param {import('vec3').Vec3} position
|
|
48
|
+
* @param {keyof typeof elementType} type
|
|
49
|
+
*/
|
|
50
|
+
constructor(position, type) {
|
|
51
|
+
this.position = position;
|
|
52
|
+
this.type = type;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getInfo() {
|
|
56
|
+
return `Type: ${this.type}, Position: ${this.position}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async resolve(bot) {
|
|
60
|
+
const handler = resolveMap[this.type];
|
|
61
|
+
if (handler) {
|
|
62
|
+
try {
|
|
63
|
+
await handler(bot, this.position);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
throw new Error(`[Cicerone] Unable to resolve [${this.getInfo()}]: ${e.message}`);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
throw new Error(`[Cicerone] Unhandled resolve for type: ${this.type}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async revert(bot) {
|
|
73
|
+
const handler = revertMap[this.type];
|
|
74
|
+
if (handler) {
|
|
75
|
+
try {
|
|
76
|
+
await handler(bot, this.position);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
throw new Error(`[Cicerone] Unable to revert [${this.getInfo()}]: ${e.message}`);
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
throw new Error(`[Cicerone] Unhandled revert for type: ${this.type}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
class ParentPathElement extends PathElement {
|
|
87
|
+
/**
|
|
88
|
+
* @param {import('vec3').Vec3} position
|
|
89
|
+
* @param {keyof typeof elementType} type
|
|
90
|
+
*/
|
|
91
|
+
constructor(position, type) {
|
|
92
|
+
super(position, type);
|
|
93
|
+
this.children = [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {PathElement} child
|
|
98
|
+
*/
|
|
99
|
+
addChild(child) {
|
|
100
|
+
this.children.push(child);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
hasChildren() {
|
|
104
|
+
return this.children.length > 0;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
elementType,
|
|
110
|
+
PathElement,
|
|
111
|
+
ParentPathElement
|
|
112
|
+
};
|
package/lib/placement.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
const { Vec3 } = require('vec3');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Finds an adjacent solid block on whose edge a new block can be placed.
|
|
5
|
+
*
|
|
6
|
+
* @param {import('mineflayer').Bot} bot
|
|
7
|
+
* @param {import('vec3').Vec3} position - The target point where the block should be placed.
|
|
8
|
+
* @returns {{ referenceBlock: ReturnType<import('mineflayer').Bot['blockAt']>, faceVector: import('vec3').Vec3 | null }}
|
|
9
|
+
* */
|
|
10
|
+
function getReferenceBlockAndFaceVector(bot, position) {
|
|
11
|
+
const directions = [
|
|
12
|
+
new Vec3(0, -1, 0),
|
|
13
|
+
new Vec3(0, 1, 0),
|
|
14
|
+
new Vec3(-1, 0, 0),
|
|
15
|
+
new Vec3(1, 0, 0),
|
|
16
|
+
new Vec3(0, 0, -1),
|
|
17
|
+
new Vec3(0, 0, 1)
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
for (const dir of directions) {
|
|
21
|
+
const referenceBlockPos = position.plus(dir);
|
|
22
|
+
const block = bot.blockAt(referenceBlockPos);
|
|
23
|
+
|
|
24
|
+
if (block && block.name !== 'air' && block.name !== 'cave_air' && block.name !== 'water' && block.name !== 'lava') {
|
|
25
|
+
const faceVector = dir.scaled(-1);
|
|
26
|
+
return { referenceBlock: block, faceVector };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { referenceBlock: null, faceVector: null };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Finds any solid building block in the bot's inventory and equips it.
|
|
35
|
+
*
|
|
36
|
+
* @param {import('mineflayer').Bot} bot
|
|
37
|
+
* @returns {Promise<import('prismarine-item').Item>}
|
|
38
|
+
*/
|
|
39
|
+
async function equipBuildingBlock(bot) {
|
|
40
|
+
const allowedBlocks = bot.cicerone?.settings?.buildingBlocks || [];
|
|
41
|
+
|
|
42
|
+
if (allowedBlocks.length === 0) {
|
|
43
|
+
throw new Error('[Cicerone] No building blocks configured in bot.cicerone.settings.buildingBlocks');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const item = bot.inventory.items().find(i => allowedBlocks.includes(i.name));
|
|
47
|
+
|
|
48
|
+
if (!item) {
|
|
49
|
+
throw new Error('[Cicerone] No suitable building blocks found in inventory matching your settings.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await bot.equip(item, 'hand');
|
|
53
|
+
return item;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {import('mineflayer').Bot} bot
|
|
58
|
+
*/
|
|
59
|
+
module.exports = (bot) => {
|
|
60
|
+
/**
|
|
61
|
+
* Safely places a building block at the specified coordinates.
|
|
62
|
+
*
|
|
63
|
+
* @param {import('vec3').Vec3} position - The target point where the block should be placed
|
|
64
|
+
* @returns {Promise<void>}
|
|
65
|
+
*/
|
|
66
|
+
bot.cicerone.placeBuildingBlock = async (position) => {
|
|
67
|
+
const { referenceBlock, faceVector } = getReferenceBlockAndFaceVector(bot, position);
|
|
68
|
+
|
|
69
|
+
if (!referenceBlock || !faceVector) {
|
|
70
|
+
throw new Error(`[Cicerone] Reference block not found to place block at position: ${position}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
await equipBuildingBlock(bot);
|
|
74
|
+
|
|
75
|
+
const lookTarget = referenceBlock.position.plus(faceVector.scaled(0.5));
|
|
76
|
+
await bot.lookAt(lookTarget, true);
|
|
77
|
+
await bot.waitForTicks(1);
|
|
78
|
+
|
|
79
|
+
await bot.placeBlock(referenceBlock, faceVector);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Causes the bot to jump and place a building block underneath itself.
|
|
84
|
+
*
|
|
85
|
+
* @returns {Promise<void>}
|
|
86
|
+
*/
|
|
87
|
+
bot.cicerone.buildUp = async () => {
|
|
88
|
+
bot.clearControlStates();
|
|
89
|
+
|
|
90
|
+
while (!bot.entity.onGround) {
|
|
91
|
+
await bot.waitForTicks(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const initialPosition = bot.entity.position.clone();
|
|
95
|
+
const targetBlockPosition = initialPosition.floored();
|
|
96
|
+
|
|
97
|
+
await equipBuildingBlock(bot);
|
|
98
|
+
await bot.waitForTicks(1);
|
|
99
|
+
|
|
100
|
+
bot.setControlState('jump', true);
|
|
101
|
+
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
const yPosListener = async () => {
|
|
104
|
+
if (!bot.entity) {
|
|
105
|
+
bot.removeListener('physicsTick', yPosListener);
|
|
106
|
+
reject(new Error('Bot disconnected during buildup.'));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (bot.entity.position.y >= initialPosition.y + 1.15) {
|
|
111
|
+
bot.removeListener('physicsTick', yPosListener);
|
|
112
|
+
bot.setControlState('jump', false);
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
await bot.cicerone.placeBuildingBlock(targetBlockPosition);
|
|
116
|
+
resolve();
|
|
117
|
+
} catch (err) {
|
|
118
|
+
reject(err);
|
|
119
|
+
} finally {
|
|
120
|
+
bot.clearControlStates();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
bot.on('physicsTick', yPosListener);
|
|
126
|
+
});
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const {getEnchantments, getDurability} = require('./item')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Finds the most suitable tool for mining the specified block in the bot's inventory.
|
|
5
|
+
*
|
|
6
|
+
* @param {import('mineflayer').Bot} bot - An instance of the mineflayer bot.
|
|
7
|
+
* @param {import('prismarine-block').Block} block - Target block.
|
|
8
|
+
* @returns {import('prismarine-item').Item | null} The optimal tool, or null if the tool is not required.
|
|
9
|
+
* @throws {Error} If a block requires a tool, but there is no suitable one in the inventory.
|
|
10
|
+
* */
|
|
11
|
+
function harvestTool(bot, block) {
|
|
12
|
+
const MIN_DURABILITY = 5;
|
|
13
|
+
|
|
14
|
+
if (!block.material) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const materialParts = block.material.split('/');
|
|
19
|
+
const toolName = materialParts[1];
|
|
20
|
+
|
|
21
|
+
if (!toolName) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let isOre = block.name.includes('_ore');
|
|
26
|
+
|
|
27
|
+
const tools = bot.inventory.items().filter(item => {
|
|
28
|
+
if (!item.name.includes(toolName)) return false;
|
|
29
|
+
|
|
30
|
+
if (item.maxDurability) {
|
|
31
|
+
const remainingDurability = getDurability(item);
|
|
32
|
+
|
|
33
|
+
if (remainingDurability <= MIN_DURABILITY ) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return true;
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (tools.length === 0) {
|
|
42
|
+
throw new Error(`Could not find matching tool of type: ${toolName}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let tool;
|
|
46
|
+
if (isOre) {
|
|
47
|
+
tool = tools.find(item => getEnchantments(item).silk_touch);
|
|
48
|
+
} else {
|
|
49
|
+
tool = tools.find(item => getEnchantments(item).fortune);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!tool) {
|
|
53
|
+
tool = tools[0];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return tool;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
harvestTool,
|
|
61
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts an item's enchantments into "name:level" format.
|
|
3
|
+
*
|
|
4
|
+
* @param {import('prismarine-item').Item | null} item - Item to be checked.
|
|
5
|
+
* @returns {Object<string, number>} Enchantment List.
|
|
6
|
+
* */
|
|
7
|
+
function getEnchantments(item) {
|
|
8
|
+
const enchants = {};
|
|
9
|
+
|
|
10
|
+
if (!item) return enchants;
|
|
11
|
+
|
|
12
|
+
if (item.enchants && Array.isArray(item.enchants)) {
|
|
13
|
+
for (const enchant of item.enchants) {
|
|
14
|
+
enchants[enchant.name] = enchant.lvl;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return enchants;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Calculates the remaining durability of the item.
|
|
23
|
+
*
|
|
24
|
+
* @param {import('prismarine-item').Item | null} item - Item to be checked.
|
|
25
|
+
* @returns {number} Remaining amount of durability units.
|
|
26
|
+
* */
|
|
27
|
+
function getDurability(item) {
|
|
28
|
+
if (!item) return 0;
|
|
29
|
+
const max = item.maxDurability;
|
|
30
|
+
if (!max) return 100;
|
|
31
|
+
|
|
32
|
+
const used = item.durabilityUsed || 0;
|
|
33
|
+
return max - used;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
getEnchantments,
|
|
38
|
+
getDurability,
|
|
39
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mineflayer-cicerone",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An advanced plugin for navigation and action execution for Mineflayer.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"mineflayer",
|
|
11
|
+
"minecraft",
|
|
12
|
+
"bot",
|
|
13
|
+
"navigation",
|
|
14
|
+
"cicerone"
|
|
15
|
+
],
|
|
16
|
+
"author": "Lirihiwa",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"mineflayer": "^4.37.1"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"vec3": "^0.2.0"
|
|
23
|
+
}
|
|
24
|
+
}
|