Skip to content

Commit 4593da1

Browse files
authored
Add a test for title and improve the title functionality in mineflayer (#3653)
* start adding title test * works * cleanup * implement missing tititle features * fix clear for 1.18 * update example * fix lint * revert change to external test * fix api md * consistent format * refactor * add some content in llm contribute
1 parent 32d9d84 commit 4593da1

5 files changed

Lines changed: 163 additions & 13 deletions

File tree

docs/api.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@
170170
- ["game"](#game)
171171
- ["resourcePack" (url, hash)](#resourcepack-url-hash)
172172
- ["title" (title, type)](#title-title-type)
173+
- ["title_times" (fadeIn, stay, fadeOut)](#title_times-fadein-stay-fadeout)
174+
- ["title_clear"](#title_clear)
173175
- ["rain"](#rain)
174176
- ["weatherUpdate"](#weatherupdate)
175177
- ["time"](#time)
@@ -1241,8 +1243,28 @@ Emitted when the server sends a resource pack.
12411243

12421244
Emitted when the server sends a title
12431245

1244-
* `title` - title's text
1245-
* `type` - title's type "subtitle" or "title"
1246+
* `title` - title's text
1247+
* `type` - title's type "subtitle", "title"
1248+
1249+
#### "title_times" (fadeIn, stay, fadeOut)
1250+
1251+
Emitted when the server sends a title times packet (i.e., when the fade-in, stay, and fade-out times for titles are set or updated).
1252+
1253+
* `fadeIn` - fade-in time in ticks (number)
1254+
* `stay` - stay time in ticks (number)
1255+
* `fadeOut` - fade-out time in ticks (number)
1256+
1257+
Example:
1258+
1259+
```js
1260+
bot.on('title_times', (fadeIn, stay, fadeOut) => {
1261+
console.log(`Title times: fadeIn=${fadeIn}, stay=${stay}, fadeOut=${fadeOut}`)
1262+
})
1263+
```
1264+
1265+
#### "title_clear"
1266+
1267+
Emitted when the server clears all titles.
12461268

12471269
#### "rain"
12481270

docs/llm_contribute.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,51 @@ module.exports = () => async (bot) => {
186186
- **Use `bot.chat`**: For issuing commands, use `bot.chat` directly instead of `bot.test.runCommand` to ensure commands are sent correctly.
187187
- **Debugging**: Use `console.log` for debugging, but remove these statements before finalizing the test.
188188

189+
## Title Plugin Implementation Details
190+
191+
### Version-Specific Title Handling
192+
- Title packets changed significantly between versions:
193+
- 1.8.8 uses a single `title` packet with an action field
194+
- 1.14.4+ uses separate packets for different title operations
195+
- Use `bot.supportFeature('titleUsesLegacyPackets')` to detect version
196+
- Handle both JSON and plain text title formats
197+
198+
### Title Testing Strategy
199+
```javascript
200+
// Example of testing title functionality
201+
const titleTests = [
202+
{ type: 'title', text: 'Main Title' },
203+
{ type: 'subtitle', text: 'Subtitle Text' },
204+
{ type: 'clear' }
205+
]
206+
207+
for (const test of titleTests) {
208+
if (test.type === 'clear') {
209+
bot.test.sayEverywhere('/title @a clear')
210+
} else {
211+
bot.test.sayEverywhere(`/title @a ${test.type} {"text":"${test.text}"}`)
212+
}
213+
await once(bot, 'title')
214+
// Verify title state
215+
}
216+
```
217+
218+
### Title-Specific Best Practices
219+
1. **Event Handling**
220+
- Listen for both legacy and modern title events
221+
- Handle title clear events separately
222+
- Parse JSON title text properly
223+
224+
2. **Version Compatibility**
225+
- Test title display, subtitle, and clear operations
226+
- Verify title timing settings work
227+
- Check title text parsing across versions
228+
229+
3. **Error Prevention**
230+
- Handle malformed JSON in title text
231+
- Provide fallbacks for unsupported operations
232+
- Log title-related errors for debugging
233+
189234
## Conclusion
190235

191236
When adding or modifying tests:

examples/titles.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
22
* An example of how to handle title events from the server.
3+
* Demonstrates title, subtitle, timing, and clearing functionality.
34
*/
45
const mineflayer = require('mineflayer')
56

@@ -15,8 +16,26 @@ const bot = mineflayer.createBot({
1516
password: process.argv[5]
1617
})
1718

18-
// This event is triggered when the server sends a title to the client.
19+
// This event is triggered when the server sends a title or subtitle
1920
bot.on('title', (text, type) => {
2021
// type is either "title" or "subtitle"
2122
console.log(`Received ${type}: ${text}`)
2223
})
24+
25+
// This event is triggered when the server sets title display times
26+
bot.on('title_times', (fadeIn, stay, fadeOut) => {
27+
console.log(`Title timing: fadeIn=${fadeIn}ms, stay=${stay}ms, fadeOut=${fadeOut}ms`)
28+
})
29+
30+
// This event is triggered when the server clears all titles
31+
bot.on('title_clear', () => {
32+
console.log('All titles cleared')
33+
})
34+
35+
bot.on('spawn', () => {
36+
console.log('Bot spawned! Try these commands:')
37+
console.log('/title @a title {"text":"Hello World"}')
38+
console.log('/title @a subtitle {"text":"Welcome!"}')
39+
console.log('/title @a times 10 20 30')
40+
console.log('/title @a clear')
41+
})

lib/plugins/title.js

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,37 @@
11
module.exports = inject
22
function inject (bot) {
3-
bot._client.on('title', (packet) => {
4-
if (packet.action === 0 || packet.action === 1) {
5-
bot.emit('title', packet.text, 'title')
3+
function parseTitle (text) {
4+
try {
5+
const parsed = JSON.parse(text)
6+
return typeof parsed === 'string' ? parsed : (parsed.text || text)
7+
} catch {
8+
return typeof text === 'string' ? text.replace(/^"|"$/g, '') : text
69
}
7-
})
8-
bot._client.on('set_title_text', packet => {
9-
bot.emit('title', packet.text, 'title')
10-
})
11-
bot._client.on('set_title_subtitle', packet => {
12-
bot.emit('title', packet.text, 'subtitle')
13-
})
10+
}
11+
12+
if (bot.supportFeature('titleUsesLegacyPackets')) {
13+
bot._client.on('title', (packet) => {
14+
if (packet.action === 0) bot.emit('title', parseTitle(packet.text), 'title')
15+
else if (packet.action === 1) bot.emit('title', parseTitle(packet.text), 'subtitle')
16+
else if (packet.action === 2) bot.emit('title_times', packet.fadeIn, packet.stay, packet.fadeOut)
17+
else if (packet.action === 3) {
18+
if (packet.fadeIn !== undefined) bot.emit('title_times', packet.fadeIn, packet.stay, packet.fadeOut)
19+
else bot.emit('title_clear')
20+
} else if (packet.action === 4) bot.emit('title_clear')
21+
})
22+
} else if (bot.supportFeature('titleUsesNewPackets')) {
23+
function getText (packet) {
24+
let text = packet.text
25+
if (typeof text === 'object' && text.value !== undefined) text = text.value
26+
return parseTitle(text)
27+
}
28+
bot._client.on('set_title_text', (packet) => bot.emit('title', getText(packet), 'title'))
29+
bot._client.on('set_title_subtitle', (packet) => bot.emit('title', getText(packet), 'subtitle'))
30+
bot._client.on('set_title_time', (packet) => {
31+
if (typeof packet.fadeIn === 'number' && typeof packet.stay === 'number' && typeof packet.fadeOut === 'number') {
32+
bot.emit('title_times', packet.fadeIn, packet.stay, packet.fadeOut)
33+
}
34+
})
35+
bot._client.on('clear_titles', () => bot.emit('title_clear'))
36+
}
1437
}

test/externalTests/title.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const { once } = require('../../lib/promise_utils')
2+
3+
module.exports = () => async (bot) => {
4+
// Test title
5+
bot.chat('/title @a title {"text":"Test Title"}')
6+
const [title, type] = await once(bot, 'title', 2000)
7+
if (title !== 'Test Title' || type !== 'title') {
8+
throw new Error(`Title test failed: expected "Test Title" but got "${title}" with type "${type}"`)
9+
}
10+
11+
// Test subtitle
12+
bot.chat('/title @a subtitle {"text":"Test Subtitle"}')
13+
const [subtitle, subtitleType] = await once(bot, 'title', 20000)
14+
if (subtitle !== 'Test Subtitle' || subtitleType !== 'subtitle') {
15+
throw new Error(`Subtitle test failed: expected "Test Subtitle" but got "${subtitle}" with type "${subtitleType}"`)
16+
}
17+
18+
// Test title_times event
19+
bot.chat('/title @a times 10 20 30')
20+
const [fadeIn, stay, fadeOut] = await once(bot, 'title_times', 20000)
21+
if (fadeIn !== 10 || stay !== 20 || fadeOut !== 30) {
22+
throw new Error(`title_times event failed: expected (10,20,30) but got (${fadeIn},${stay},${fadeOut})`)
23+
}
24+
25+
// Test combined title and subtitle
26+
bot.chat('/title @a title {"text":"Test Title"}')
27+
const [combinedTitle, combinedTitleType] = await once(bot, 'title', 20000)
28+
if (combinedTitle !== 'Test Title' || combinedTitleType !== 'title') {
29+
throw new Error(`Combined title test failed: expected "Test Title" but got "${combinedTitle}" with type "${combinedTitleType}"`)
30+
}
31+
32+
bot.chat('/title @a subtitle {"text":"Test Subtitle"}')
33+
const [combinedSubtitle, combinedSubtitleType] = await once(bot, 'title', 20000)
34+
if (combinedSubtitle !== 'Test Subtitle' || combinedSubtitleType !== 'subtitle') {
35+
throw new Error(`Combined subtitle test failed: expected "Test Subtitle" but got "${combinedSubtitle}" with type "${combinedSubtitleType}"`)
36+
}
37+
38+
// Test clearing title
39+
bot.chat('/title @a clear')
40+
await once(bot, 'title_clear', 2000)
41+
}

0 commit comments

Comments
 (0)