Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PrismarineJS
GitHub Repository: PrismarineJS/mineflayer
Path: blob/master/lib/plugins/game.js
9427 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
if (bot.supportFeature('dimensionDataInCodec')) {
51
// For 1.19+, we need the dimension TYPE name (not the world/level name) so
52
// the codec lookup succeeds. In login packets the type is "worldType"; in
53
// respawn packets it is "dimension". worldName is the level name which may
54
// differ from the type on proxy/modded servers.
55
const dimType = packet.worldType ?? packet.dimension
56
bot.game.dimension = typeof dimType === 'string'
57
? dimType.replace('minecraft:', '')
58
: packet.worldName.replace('minecraft:', '')
59
} else {
60
bot.game.dimension = packet.worldName.replace('minecraft:', '')
61
}
62
} else {
63
throw new Error('Unsupported dimension type in login packet')
64
}
65
66
if (packet.dimensionCodec) {
67
bot.registry.loadDimensionCodec(packet.dimensionCodec)
68
}
69
70
bot.game.minY = 0
71
bot.game.height = 256
72
73
if (bot.supportFeature('dimensionDataInCodec')) { // 1.19+
74
const dimData = bot.registry.dimensionsByName[bot.game.dimension]
75
if (dimData) {
76
bot.game.minY = dimData.minY
77
bot.game.height = dimData.height
78
}
79
} else if (bot.supportFeature('dimensionDataIsAvailable')) { // 1.16.2+
80
const dimensionData = nbt.simplify(packet.dimension)
81
bot.game.minY = dimensionData.min_y
82
bot.game.height = dimensionData.height
83
}
84
85
if (packet.difficulty) {
86
bot.game.difficulty = difficultyNames[packet.difficulty]
87
}
88
}
89
90
bot.game = {}
91
92
const brandChannel = getBrandCustomChannelName()
93
bot._client.registerChannel(brandChannel, ['string', []])
94
95
// 1.20.2
96
bot._client.on('registry_data', (packet) => {
97
bot.registry.loadDimensionCodec(packet.codec || packet)
98
})
99
100
bot._client.on('login', (packet) => {
101
handleRespawnPacketData(packet.worldState || packet)
102
103
bot.game.maxPlayers = packet.maxPlayers
104
if (packet.enableRespawnScreen) {
105
bot.game.enableRespawnScreen = packet.enableRespawnScreen
106
}
107
if (packet.viewDistance) {
108
bot.game.serverViewDistance = packet.viewDistance
109
}
110
111
bot.emit('login')
112
bot.emit('game')
113
114
// varint length-prefixed string as data
115
bot._client.writeChannel(brandChannel, options.brand)
116
})
117
118
bot._client.on('respawn', (packet) => {
119
// in 1.20.5+ protocol we move the shared spawn data into one SpawnInfo type under .worldState
120
handleRespawnPacketData(packet.worldState || packet)
121
bot.emit('game')
122
})
123
124
bot._client.on('game_state_change', (packet) => {
125
if ((packet.reason === 4 || packet.reason === 'win_game') && packet.gameMode === 1) {
126
bot._client.write('client_command', { action: 0 })
127
}
128
if ((packet.reason === 3) || (packet.reason === 'change_game_mode')) {
129
bot.game.gameMode = parseGameMode(packet.gameMode)
130
bot.emit('game')
131
}
132
})
133
134
bot._client.on('difficulty', (packet) => {
135
bot.game.difficulty = difficultyNames[packet.difficulty]
136
})
137
138
bot._client.on(brandChannel, (serverBrand) => {
139
bot.game.serverBrand = serverBrand
140
})
141
142
// mimic the vanilla 1.17 client to prevent anticheat kicks
143
bot._client.on('ping', (data) => {
144
bot._client.write('pong', {
145
id: data.id
146
})
147
})
148
}
149
150