Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PrismarineJS
GitHub Repository: PrismarineJS/mineflayer
Path: blob/master/test/externalTests/plugins/testCommon.js
9427 views
1
const { Vec3 } = require('vec3')
2
3
const { spawn } = require('child_process')
4
const { once } = require('../../../lib/promise_utils')
5
const process = require('process')
6
const assert = require('assert')
7
const { sleep, onceWithCleanup } = require('../../../lib/promise_utils')
8
9
const timeout = 5000
10
module.exports = inject
11
12
function inject (bot, wrap) {
13
console.log(bot.version)
14
15
bot.test = {}
16
bot.test.groundY = bot.supportFeature('tallWorld') ? -60 : 4
17
bot.test.sayEverywhere = sayEverywhere
18
bot.test.clearInventory = clearInventory
19
bot.test.becomeSurvival = becomeSurvival
20
bot.test.becomeCreative = becomeCreative
21
bot.test.fly = fly
22
bot.test.teleport = teleport
23
bot.test.resetState = resetState
24
bot.test.setInventorySlot = setInventorySlot
25
bot.test.placeBlock = placeBlock
26
bot.test.runExample = runExample
27
bot.test.tellAndListen = tellAndListen
28
bot.test.selfKill = selfKill
29
bot.test.wait = function (ms) {
30
return new Promise((resolve) => { setTimeout(resolve, ms) })
31
}
32
33
bot.test.awaitItemReceived = async (command) => {
34
const p = once(bot.inventory, 'updateSlot')
35
bot.chat(command)
36
await p // await getting the item
37
}
38
// setting relative to true makes x, y, & z relative using ~
39
bot.test.setBlock = async ({ x = 0, y = 0, z = 0, relative, blockName }) => {
40
const { x: _x, y: _y, z: _z } = relative ? bot.entity.position.floored().offset(x, y, z) : { x, y, z }
41
const block = bot.blockAt(new Vec3(_x, _y, _z))
42
if (block.name === blockName) {
43
return
44
}
45
const p = once(bot.world, `blockUpdate:(${_x}, ${_y}, ${_z})`)
46
const prefix = relative ? '~' : ''
47
bot.chat(`/setblock ${prefix}${x} ${prefix}${y} ${prefix}${z} ${blockName}`)
48
await p
49
}
50
51
let grassName
52
if (bot.supportFeature('itemsAreNotBlocks')) {
53
grassName = 'grass_block'
54
} else if (bot.supportFeature('itemsAreAlsoBlocks')) {
55
grassName = 'grass'
56
}
57
58
const layerNames = [
59
'bedrock',
60
'dirt',
61
'dirt',
62
grassName,
63
'air',
64
'air',
65
'air',
66
'air',
67
'air'
68
]
69
70
async function resetBlocksToSuperflat () {
71
const groundY = 4
72
for (let y = groundY + 4; y >= groundY - 1; y--) {
73
const realY = y + bot.test.groundY - 4
74
bot.chat(`/fill ~-5 ${realY} ~-5 ~5 ${realY} ~5 ` + layerNames[y])
75
}
76
await bot.test.wait(100)
77
}
78
79
async function placeBlock (slot, position) {
80
bot.setQuickBarSlot(slot - 36)
81
// always place the block on the top of the block below it, i guess.
82
const referenceBlock = bot.blockAt(position.plus(new Vec3(0, -1, 0)))
83
return bot.placeBlock(referenceBlock, new Vec3(0, 1, 0))
84
}
85
86
// always leaves you in creative mode
87
async function resetState () {
88
await becomeCreative()
89
await clearInventory()
90
bot.creative.startFlying()
91
await teleport(new Vec3(0, bot.test.groundY, 0))
92
await bot.waitForChunksToLoad()
93
await resetBlocksToSuperflat()
94
await clearInventory()
95
}
96
97
async function becomeCreative () {
98
// console.log('become creative')
99
return setCreativeMode(true)
100
}
101
102
async function becomeSurvival () {
103
return setCreativeMode(false)
104
}
105
106
async function setCreativeMode (value) {
107
const mode = value ? 'creative' : 'survival'
108
const modeId = value ? 1 : 0
109
if (bot.game.gameMode === mode) return
110
// Use server console for instant, reliable gamemode change.
111
// The old approach (triple chat command + message parsing) was fragile
112
// and the most common source of flaky test timeouts.
113
const gameModePromise = onceWithCleanup(bot._client, 'game_state_change', {
114
timeout,
115
checkCondition: (packet) => {
116
// reason is 3 (number) on old versions, 'change_game_mode' (string) on new
117
const isGameModeChange = packet.reason === 3 || packet.reason === 'change_game_mode'
118
return isGameModeChange && Math.floor(packet.gameMode) === modeId
119
}
120
})
121
wrap.writeServer(`gamemode ${mode} flatbot\n`)
122
await gameModePromise
123
}
124
125
async function clearInventory () {
126
// Use bot.chat for /give (server console /give doesn't send inventory
127
// update packets on 1.21.9+). Use server console for /clear.
128
bot.chat('/give @a stone 1')
129
await onceWithCleanup(bot.inventory, 'updateSlot', {
130
timeout: 10000,
131
checkCondition: (slot, oldItem, newItem) => newItem?.name === 'stone'
132
})
133
const clearMsg = onceWithCleanup(bot, 'message', {
134
timeout: 10000,
135
checkCondition: msg => msg.translate === 'commands.clear.success.single' || msg.translate === 'commands.clear.success'
136
})
137
bot.chat('/clear')
138
await clearMsg
139
}
140
141
// you need to be in creative mode for this to work
142
async function setInventorySlot (targetSlot, item) {
143
assert(item === null || item.name !== 'unknown', `item should not be unknown ${JSON.stringify(item)}`)
144
return bot.creative.setInventorySlot(targetSlot, item)
145
}
146
147
async function teleport (position) {
148
// Use server console for teleport — works even if bot is in a bad state
149
if (bot.supportFeature('hasExecuteCommand')) {
150
wrap.writeServer(`execute in overworld run teleport ${bot.username} ${position.x} ${position.y} ${position.z}\n`)
151
} else {
152
wrap.writeServer(`tp ${bot.username} ${position.x} ${position.y} ${position.z}\n`)
153
}
154
return onceWithCleanup(bot, 'move', {
155
timeout,
156
checkCondition: () => bot.entity.position.distanceTo(position) < 0.9
157
})
158
}
159
160
function sayEverywhere (message) {
161
bot.chat(message)
162
console.log(message)
163
}
164
165
async function fly (delta) {
166
return bot.creative.flyTo(bot.entity.position.plus(delta))
167
}
168
169
async function tellAndListen (to, what, listen) {
170
const chatMessagePromise = onceWithCleanup(bot, 'chat', {
171
timeout,
172
checkCondition: (username, message) => username === to && listen(message)
173
})
174
175
bot.chat(what)
176
177
return chatMessagePromise
178
}
179
180
async function runExample (file, run) {
181
let childBotName
182
183
const detectChildJoin = async () => {
184
const [message] = await onceWithCleanup(bot, 'message', {
185
checkCondition: message => message.json.translate === 'multiplayer.player.joined'
186
})
187
childBotName = message.json.with[0].insertion
188
bot.chat(`/tp ${childBotName} 50 ${bot.test.groundY} 0`)
189
// Wait for the child entity to arrive at the teleport target,
190
// confirming the server has processed the TP
191
const targetPos = new Vec3(50, bot.test.groundY, 0)
192
while (!bot.players[childBotName]?.entity ||
193
bot.players[childBotName].entity.position.distanceTo(targetPos) > 5) {
194
await sleep(100)
195
}
196
// Let the child's physics engine initialize at the new position
197
// (ground detection, chunk processing) before starting the test
198
await bot.waitForTicks(60)
199
bot.chat('loaded')
200
}
201
202
const runExampleOnReady = async () => {
203
await onceWithCleanup(bot, 'chat', {
204
checkCondition: (username, message) => message === 'Ready!'
205
})
206
return run(childBotName)
207
}
208
209
const child = spawn('node', [file, '127.0.0.1', `${bot.test.port}`])
210
211
// Useful to debug child processes:
212
child.stdout.on('data', (data) => { console.log(`${data}`) })
213
child.stderr.on('data', (data) => { console.error(`${data}`) })
214
215
const closeExample = async (err) => {
216
console.log('kill process ' + child.pid)
217
218
try {
219
process.kill(child.pid, 'SIGTERM')
220
const [code] = await onceWithCleanup(child, 'close', { timeout: 5000 })
221
console.log('close requested', code)
222
} catch (e) {
223
console.log(e)
224
console.log('process termination failed, process may already be closed')
225
}
226
227
if (err) {
228
throw err
229
}
230
}
231
232
// Let mocha's test-level timeout (90s) be the backstop instead of
233
// an inner withTimeout, which was causing premature failures on
234
// slow CI runners.
235
try {
236
await Promise.all([detectChildJoin(), runExampleOnReady()])
237
} catch (err) {
238
console.log(err)
239
return closeExample(err)
240
}
241
return closeExample()
242
}
243
244
function selfKill () {
245
bot.chat('/kill @p')
246
}
247
248
// Debug packet IO when tests are re-run with "Enable debug logging" - https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables
249
if (process.env.RUNNER_DEBUG) {
250
bot._client.on('packet', function (data, meta) {
251
if (['chunk', 'time', 'light', 'alive'].some(e => meta.name.includes(e))) return
252
console.log('->', meta.name, JSON.stringify(data)?.slice(0, 250))
253
})
254
const oldWrite = bot._client.write
255
bot._client.write = function (name, data) {
256
if (['alive', 'pong', 'ping'].some(e => name.includes(e))) return
257
console.log('<-', name, JSON.stringify(data)?.slice(0, 250))
258
oldWrite.apply(bot._client, arguments)
259
}
260
BigInt.prototype.toJSON ??= function () { // eslint-disable-line
261
return this.toString()
262
}
263
}
264
}
265
266