Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PrismarineJS
GitHub Repository: PrismarineJS/mineflayer
Path: blob/master/lib/plugins/game.js
1467 views
1
const nbt = require('prismarine-nbt')
2
module.exports = inject
3
4
const difficultyNames = ['peaceful', 'easy', 'normal', 'hard']
5
const gameModes = ['survival', 'creative', 'adventure', 'spectator']
6
7
const dimensionNames = {
8
'-1': 'the_nether',
9
0: 'overworld',
10
1: 'the_end'
11
}
12
13
const parseGameMode = gameModeBits => {
14
if (gameModeBits < 0 || gameModeBits > 0b11) {
15
return 'survival'
16
}
17
return gameModes[(gameModeBits & 0b11)] // lower two bits
18
}
19
20
function inject (bot, options) {
21
function getBrandCustomChannelName () {
22
if (bot.supportFeature('customChannelMCPrefixed')) {
23
return 'MC|Brand'
24
} else if (bot.supportFeature('customChannelIdentifier')) {
25
return 'minecraft:brand'
26
}
27
throw new Error('Unsupported brand channel name')
28
}
29
30
function handleRespawnPacketData (packet) {
31
bot.game.levelType = packet.levelType ?? (packet.isFlat ? 'flat' : 'default')
32
bot.game.hardcore = packet.isHardcore ?? Boolean(packet.gameMode & 0b100)
33
// Either a respawn packet or a login packet. Depending on the packet it can be "gamemode" or "gameMode"
34
if (bot.supportFeature('spawnRespawnWorldDataField')) { // 1.20.5
35
bot.game.gameMode = packet.gamemode
36
} else {
37
bot.game.gameMode = parseGameMode(packet.gamemode ?? packet.gameMode)
38
}
39
if (bot.supportFeature('segmentedRegistryCodecData')) { // 1.20.5
40
if (typeof packet.dimension === 'number') {
41
bot.game.dimension = bot.registry.dimensionsArray[packet.dimension]?.name?.replace('minecraft:', '')
42
} else if (typeof packet.dimension === 'string') { // iirc, in 1.21 it's back to a string
43
bot.game.dimension = packet.dimension.replace('minecraft:', '')
44
}
45
} else if (bot.supportFeature('dimensionIsAnInt')) {
46
bot.game.dimension = dimensionNames[packet.dimension]
47
} else if (bot.supportFeature('dimensionIsAString')) {
48
bot.game.dimension = packet.dimension.replace('minecraft:', '')
49
} else if (bot.supportFeature('dimensionIsAWorld')) {
50
bot.game.dimension = packet.worldName.replace('minecraft:', '')
51
} else {
52
throw new Error('Unsupported dimension type in login packet')
53
}
54
55
if (packet.dimensionCodec) {
56
bot.registry.loadDimensionCodec(packet.dimensionCodec)
57
}
58
59
bot.game.minY = 0
60
bot.game.height = 256
61
62
if (bot.supportFeature('dimensionDataInCodec')) { // 1.19+
63
// pre 1.20.5 before we consolidated login and respawn's SpawnInfo structure into one type,
64
// "dimension" was called "worldType" in login_packet's payload but not respawn.
65
if (packet.worldType && !bot.game.dimension) {
66
bot.game.dimension = packet.worldType.replace('minecraft:', '')
67
}
68
const dimData = bot.registry.dimensionsByName[bot.game.dimension]
69
if (dimData) {
70
bot.game.minY = dimData.minY
71
bot.game.height = dimData.height
72
}
73
} else if (bot.supportFeature('dimensionDataIsAvailable')) { // 1.16.2+
74
const dimensionData = nbt.simplify(packet.dimension)
75
bot.game.minY = dimensionData.min_y
76
bot.game.height = dimensionData.height
77
}
78
79
if (packet.difficulty) {
80
bot.game.difficulty = difficultyNames[packet.difficulty]
81
}
82
}
83
84
bot.game = {}
85
86
const brandChannel = getBrandCustomChannelName()
87
bot._client.registerChannel(brandChannel, ['string', []])
88
89
// 1.20.2
90
bot._client.on('registry_data', (packet) => {
91
bot.registry.loadDimensionCodec(packet.codec || packet)
92
})
93
94
bot._client.on('login', (packet) => {
95
handleRespawnPacketData(packet.worldState || packet)
96
97
bot.game.maxPlayers = packet.maxPlayers
98
if (packet.enableRespawnScreen) {
99
bot.game.enableRespawnScreen = packet.enableRespawnScreen
100
}
101
if (packet.viewDistance) {
102
bot.game.serverViewDistance = packet.viewDistance
103
}
104
105
bot.emit('login')
106
bot.emit('game')
107
108
// varint length-prefixed string as data
109
bot._client.writeChannel(brandChannel, options.brand)
110
})
111
112
bot._client.on('respawn', (packet) => {
113
// in 1.20.5+ protocol we move the shared spawn data into one SpawnInfo type under .worldState
114
handleRespawnPacketData(packet.worldState || packet)
115
bot.emit('game')
116
})
117
118
bot._client.on('game_state_change', (packet) => {
119
if (packet?.reason === 4 && packet?.gameMode === 1) {
120
bot._client.write('client_command', { action: 0 })
121
}
122
if (packet.reason === 3) {
123
bot.game.gameMode = parseGameMode(packet.gameMode)
124
bot.emit('game')
125
}
126
})
127
128
bot._client.on('difficulty', (packet) => {
129
bot.game.difficulty = difficultyNames[packet.difficulty]
130
})
131
132
bot._client.on(brandChannel, (serverBrand) => {
133
bot.game.serverBrand = serverBrand
134
})
135
136
// mimic the vanilla 1.17 client to prevent anticheat kicks
137
bot._client.on('ping', (data) => {
138
bot._client.write('pong', {
139
id: data.id
140
})
141
})
142
}
143
144