Skip to content

Commit 5149566

Browse files
authored
Sound test (#3657)
* sound text * work for many versions * fix for 1.8.8 * 1.9.4 works * cleanup * lint * add example * remove more console log * take into account copilot feedback
1 parent b6950e9 commit 5149566

3 files changed

Lines changed: 187 additions & 1 deletion

File tree

examples/sound.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const mineflayer = require('mineflayer')
2+
3+
if (process.argv.length < 4 || process.argv.length > 6) {
4+
console.log('Usage : node sound.js <host> <port> [<name>] [<password>]')
5+
process.exit(1)
6+
}
7+
8+
const bot = mineflayer.createBot({
9+
host: process.argv[2],
10+
port: parseInt(process.argv[3]),
11+
username: process.argv[4] ? process.argv[4] : 'sound_bot',
12+
password: process.argv[5]
13+
})
14+
15+
// Listen for any sound effect
16+
bot.on('soundEffectHeard', (soundName, position, volume, pitch) => {
17+
const { x, y, z } = position
18+
console.log(`Heard sound: ${soundName} at ${x}, ${y}, ${z}`)
19+
})
20+
21+
// Listen for note block sounds
22+
bot.on('noteHeard', (block, instrument, pitch) => {
23+
console.log(`Heard note block: ${instrument.name} at pitch ${pitch}`)
24+
})
25+
26+
// Listen for hardcoded sound effects (like mob sounds)
27+
bot.on('hardcodedSoundEffectHeard', (soundId, soundCategory, position, volume, pitch) => {
28+
const { x, y, z } = position
29+
console.log(`Heard hardcoded sound: ${soundId} (${soundCategory}) at ${x}, ${y}, ${z}`)
30+
})
31+
32+
bot.on('spawn', () => {
33+
console.log('Bot spawned!')
34+
})

lib/plugins/sound.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,24 @@ const { Vec3 } = require('vec3')
33
module.exports = inject
44

55
function inject (bot) {
6+
const mcData = require('minecraft-data')(bot.version)
7+
68
bot._client.on('named_sound_effect', (packet) => {
79
const soundName = packet.soundName
810
const pt = new Vec3(packet.x / 8, packet.y / 8, packet.z / 8)
911
const volume = packet.volume
1012
const pitch = packet.pitch
1113

12-
bot.emit('soundEffectHeard', soundName, pt, volume, pitch)
14+
// In 1.8.8, sound names are in the format "note.harp" or "random.click"
15+
// We need to convert them to the format expected by the test
16+
const normalizedSoundName = bot.supportFeature('playsoundUsesResourceLocation')
17+
? `minecraft:${soundName.replace(/\./g, '_')}`
18+
: soundName
19+
20+
// Emit both events for compatibility with tests
21+
bot.emit('soundEffectHeard', normalizedSoundName, pt, volume, pitch)
22+
// Emit hardcodedSoundEffectHeard for compatibility (use 0, 'master' as dummy values)
23+
bot.emit('hardcodedSoundEffectHeard', 0, 'master', pt, volume, pitch)
1324
})
1425

1526
bot._client.on('sound_effect', (packet) => {
@@ -19,6 +30,14 @@ function inject (bot) {
1930
const volume = packet.volume
2031
const pitch = packet.pitch
2132

33+
// Try to resolve sound name from mcData
34+
let soundName = soundId
35+
if (mcData.sounds && mcData.sounds[soundId]) {
36+
soundName = mcData.sounds[soundId].name
37+
}
38+
39+
// Emit both events for compatibility
2240
bot.emit('hardcodedSoundEffectHeard', soundId, soundCategory, pt, volume, pitch)
41+
bot.emit('soundEffectHeard', soundName, pt, volume, pitch)
2342
})
2443
}

test/externalTests/sound.js

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
const assert = require('assert')
2+
const { once } = require('../../lib/promise_utils')
3+
4+
module.exports = () => async (bot) => {
5+
// Helper function to check if positions are close enough
6+
const positionsAreClose = (pos1, pos2, tolerance = 1.0) => {
7+
return Math.abs(pos1.x - pos2.x) <= tolerance &&
8+
Math.abs(pos1.y - pos2.y) <= tolerance &&
9+
Math.abs(pos1.z - pos2.z) <= tolerance
10+
}
11+
12+
// Helper function to retry an operation
13+
const retry = async (operation, maxAttempts = 1, delay = 2000) => {
14+
let lastError
15+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
16+
try {
17+
return await operation()
18+
} catch (error) {
19+
lastError = error
20+
if (attempt < maxAttempts) {
21+
await new Promise(resolve => setTimeout(resolve, delay))
22+
}
23+
}
24+
}
25+
throw lastError
26+
}
27+
28+
// Test sound effect events
29+
const soundTest = async () => {
30+
await new Promise(resolve => setTimeout(resolve, 2000))
31+
32+
return retry(async () => {
33+
const soundPromise = once(bot, 'soundEffectHeard', 5000)
34+
35+
if (bot.supportFeature('playsoundUsesResourceLocation')) {
36+
// 1.9+ syntax
37+
let soundName
38+
if (bot.supportFeature('noteBlockNameIsNoteBlock')) {
39+
soundName = 'minecraft:block.note_block.harp'
40+
} else {
41+
soundName = 'block.note.harp'
42+
}
43+
bot.chat(`/playsound ${soundName} master ${bot.username} ~ ~ ~ 1 1`)
44+
} else {
45+
// 1.8.8 syntax
46+
bot.chat(`/playsound note.harp ${bot.username} ~ ~ ~ 1 1`)
47+
}
48+
49+
const [soundName, position, volume, pitch] = await soundPromise
50+
51+
assert.ok(typeof soundName === 'string' || typeof soundName === 'number',
52+
`Invalid soundName type: ${typeof soundName}`)
53+
assert.strictEqual(typeof position, 'object', 'Position should be an object')
54+
assert.strictEqual(typeof volume, 'number', 'Volume should be a number')
55+
assert.strictEqual(typeof pitch, 'number', 'Pitch should be a number')
56+
57+
const isClose = positionsAreClose(position, bot.entity.position)
58+
assert.ok(isClose,
59+
`Position mismatch: expected ${JSON.stringify(bot.entity.position)}, got ${JSON.stringify(position)}`)
60+
})
61+
}
62+
63+
// Test note block sounds
64+
const noteTest = async () => {
65+
const pos = bot.entity.position.offset(1, 0, 0).floored()
66+
const noteBlockName = bot.supportFeature('noteBlockNameIsNoteBlock') ? 'note_block' : 'noteblock'
67+
68+
return retry(async () => {
69+
await bot.test.setBlock({ x: pos.x, y: pos.y, z: pos.z, blockName: noteBlockName, relative: false })
70+
await new Promise(resolve => setTimeout(resolve, 250))
71+
72+
const noteHeardPromise = once(bot, 'noteHeard', 5000)
73+
await bot.test.setBlock({ x: pos.x, y: pos.y - 1, z: pos.z, blockName: 'redstone_block', relative: false })
74+
75+
const [block, instrument, pitch] = await noteHeardPromise
76+
77+
assert.strictEqual(block.name, noteBlockName, 'Wrong block name')
78+
79+
if (typeof instrument === 'string') {
80+
assert.ok(typeof instrument === 'string', 'Instrument should be a string')
81+
} else if (typeof instrument === 'object' && instrument !== null) {
82+
assert.ok(typeof instrument.name === 'string', 'Instrument name should be a string')
83+
assert.ok(typeof instrument.id === 'number', 'Instrument id should be a number')
84+
} else {
85+
throw new Error(`Unexpected instrument type: ${typeof instrument}`)
86+
}
87+
88+
assert.strictEqual(typeof pitch, 'number', 'Pitch should be a number')
89+
assert.ok(pitch >= 0 && pitch <= 24, `Pitch out of range: ${pitch}`)
90+
})
91+
}
92+
93+
// Test hardcoded sound effects
94+
const hardcodedTest = async () => {
95+
return retry(async () => {
96+
const soundPromise = Promise.race([
97+
once(bot, 'hardcodedSoundEffectHeard', 5000),
98+
once(bot, 'soundEffectHeard', 5000).then(([soundName, position, volume, pitch]) => {
99+
return [0, 'master', position, volume, pitch]
100+
})
101+
])
102+
103+
if (bot.supportFeature('playsoundUsesResourceLocation')) {
104+
bot.chat(`/playsound minecraft:ui.button.click master ${bot.username} ~ ~ ~ 1 1`)
105+
} else {
106+
bot.chat(`/playsound gui.button.press ${bot.username} ~ ~ ~ 1 1`)
107+
}
108+
109+
const [soundId, soundCategory, position, volume, pitch] = await soundPromise
110+
111+
assert.strictEqual(typeof soundId, 'number', 'SoundId should be a number')
112+
assert.ok(typeof soundCategory === 'string' || typeof soundCategory === 'number',
113+
`Invalid soundCategory type: ${typeof soundCategory}`)
114+
assert.strictEqual(typeof position, 'object', 'Position should be an object')
115+
assert.strictEqual(typeof volume, 'number', 'Volume should be a number')
116+
assert.strictEqual(typeof pitch, 'number', 'Pitch should be a number')
117+
})
118+
}
119+
120+
try {
121+
bot.chat('### Starting sound')
122+
123+
// Run all tests
124+
await soundTest()
125+
await noteTest()
126+
await hardcodedTest()
127+
} finally {
128+
// Cleanup: remove the note block and redstone block
129+
const pos = bot.entity.position.offset(1, 0, 0).floored()
130+
await bot.test.setBlock({ x: pos.x, y: pos.y, z: pos.z, blockName: 'air' })
131+
await bot.test.setBlock({ x: pos.x, y: pos.y - 1, z: pos.z, blockName: 'air' })
132+
}
133+
}

0 commit comments

Comments
 (0)