CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PrismarineJS

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

GitHub Repository: PrismarineJS/mineflayer
Path: blob/master/test/externalTests/plugins/testCommon.js
Views: 789
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, withTimeout } = require('../../../lib/promise_utils')
8
9
const timeout = 5000
10
module.exports = inject
11
12
function inject (bot) {
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 sleep(1000)
95
await clearInventory()
96
}
97
98
async function becomeCreative () {
99
// console.log('become creative')
100
return setCreativeMode(true)
101
}
102
103
async function becomeSurvival () {
104
return setCreativeMode(false)
105
}
106
107
const gameModeChangedMessages = ['commands.gamemode.success.self', 'gameMode.changed']
108
109
async function setCreativeMode (value) {
110
const getGM = val => val ? 'creative' : 'survival'
111
// this function behaves the same whether we start in creative mode or not.
112
// also, creative mode is always allowed for ops, even if server.properties says force-gamemode=true in survival mode.
113
let i = 0
114
const msgProm = onceWithCleanup(bot, 'message', {
115
timeout,
116
checkCondition: msg => gameModeChangedMessages.includes(msg.translate) && i++ > 0 && bot.game.gameMode === getGM(value)
117
})
118
119
// do it three times to ensure that we get feedback
120
bot.chat(`/gamemode ${getGM(value)}`)
121
bot.chat(`/gamemode ${getGM(!value)}`)
122
bot.chat(`/gamemode ${getGM(value)}`)
123
return msgProm
124
}
125
126
async function clearInventory () {
127
const msgProm = onceWithCleanup(bot, 'message', {
128
timeout,
129
checkCondition: msg => msg.translate === 'commands.clear.success.single' || msg.translate === 'commands.clear.success'
130
})
131
bot.chat('/give @a stone 1')
132
bot.inventory.on('updateSlot', (...e) => {
133
// console.log('inventory.updateSlot', e)
134
})
135
await onceWithCleanup(bot.inventory, 'updateSlot', { timeout: 1000 * 20, checkCondition: (slot, oldItem, newItem) => newItem?.name === 'stone' })
136
bot.chat('/clear') // don't rely on the message (as it'll come to early), wait for the result of /clear instead
137
await msgProm // wait for the message so it doesn't leak into chat tests
138
139
// Check that the inventory is clear
140
for (const slot of bot.inventory.slots) {
141
if (slot && slot.itemCount <= 0) throw new Error('Inventory was not cleared: ' + JSON.stringify(bot.inventory.slots))
142
}
143
}
144
145
// you need to be in creative mode for this to work
146
async function setInventorySlot (targetSlot, item) {
147
assert(item === null || item.name !== 'unknown', `item should not be unknown ${JSON.stringify(item)}`)
148
return bot.creative.setInventorySlot(targetSlot, item)
149
}
150
151
async function teleport (position) {
152
if (bot.supportFeature('hasExecuteCommand')) {
153
bot.test.sayEverywhere(`/execute in overworld run teleport ${bot.username} ${position.x} ${position.y} ${position.z}`)
154
} else {
155
bot.test.sayEverywhere(`/tp ${bot.username} ${position.x} ${position.y} ${position.z}`)
156
}
157
return onceWithCleanup(bot, 'move', {
158
timeout,
159
checkCondition: () => bot.entity.position.distanceTo(position) < 0.9
160
})
161
}
162
163
function sayEverywhere (message) {
164
bot.chat(message)
165
console.log(message)
166
}
167
168
async function fly (delta) {
169
return bot.creative.flyTo(bot.entity.position.plus(delta))
170
}
171
172
async function tellAndListen (to, what, listen) {
173
const chatMessagePromise = onceWithCleanup(bot, 'chat', {
174
timeout,
175
checkCondition: (username, message) => username === to && listen(message)
176
})
177
178
bot.chat(what)
179
180
return chatMessagePromise
181
}
182
183
async function runExample (file, run) {
184
let childBotName
185
186
const detectChildJoin = async () => {
187
const [message] = await onceWithCleanup(bot, 'message', {
188
timeout,
189
checkCondition: message => message.json.translate === 'multiplayer.player.joined'
190
})
191
childBotName = message.json.with[0].insertion
192
bot.chat(`/tp ${childBotName} 50 ${bot.test.groundY} 0`)
193
setTimeout(() => {
194
bot.chat('loaded')
195
}, 5000)
196
}
197
198
const runExampleOnReady = async () => {
199
await onceWithCleanup(bot, 'chat', {
200
checkCondition: (username, message) => message === 'Ready!'
201
})
202
return run(childBotName)
203
}
204
205
const child = spawn('node', [file, '127.0.0.1', `${bot.test.port}`])
206
207
// Useful to debug child processes:
208
child.stdout.on('data', (data) => { console.log(`${data}`) })
209
child.stderr.on('data', (data) => { console.error(`${data}`) })
210
211
const closeExample = async (err) => {
212
console.log('kill process ' + child.pid)
213
214
try {
215
process.kill(child.pid, 'SIGTERM')
216
const [code] = await onceWithCleanup(child, 'close', { timeout: 1000 })
217
console.log('close requested', code)
218
} catch (e) {
219
console.log(e)
220
console.log('process termination failed, process may already be closed')
221
}
222
223
if (err) {
224
throw err
225
}
226
}
227
228
try {
229
await withTimeout(Promise.all([detectChildJoin(), runExampleOnReady()]), 30000)
230
} catch (err) {
231
console.log(err)
232
return closeExample(err)
233
}
234
return closeExample()
235
}
236
237
function selfKill () {
238
bot.chat('/kill @p')
239
}
240
241
// 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
242
if (process.env.RUNNER_DEBUG) {
243
bot._client.on('packet', function (data, meta) {
244
if (['chunk', 'time', 'light', 'alive'].some(e => meta.name.includes(e))) return
245
console.log('->', meta.name, JSON.stringify(data)?.slice(0, 250))
246
})
247
const oldWrite = bot._client.write
248
bot._client.write = function (name, data) {
249
if (['alive', 'pong', 'ping'].some(e => name.includes(e))) return
250
console.log('<-', name, JSON.stringify(data)?.slice(0, 250))
251
oldWrite.apply(bot._client, arguments)
252
}
253
BigInt.prototype.toJSON ??= function () { // eslint-disable-line
254
return this.toString()
255
}
256
}
257
}
258
259