Skip to content

Commit

Permalink
1.19 support (#2743)
Browse files Browse the repository at this point in the history
* start work on 1.19

* chat for 1.19

* update doc, fix chat sending

* bump pchunk

* Revert exposed bot.profileKeys

* bump mcdata

* Bump mcdata

* update chat and codec handling

* Bump pchat

* update internal test chat

* lint

* Bump pregistry

* removed awaitMessage2

* remove awaitMessage2

* fix switch world test by extending the respawn packet (#2788)

* fix switch world test by extending the respawn packet

* fix respawn field handling on previous minecraft versions in internalTest

* typo

Co-authored-by: extremeheat <extreme@protonmail.ch>

* fix entities

* depend mc-data from github

* fix fishing test item checking

* update api doc

* Bump mc data

* fix villager trading

* fix trading on older versions

* fix villager attempt 2

* 1.19 support #2743 : Fix fishing errors on test #2791 (#2792)

* 2743 : Fix fishing errors on test

* 2743 : Oops

* mcData -> registry in internalTest

* fix in entities.js

* use minecart name instead of type object in jumpy.js for vehicle selection

Co-authored-by: extremeheat <extreme@protonmail.ch>
Co-authored-by: TeamHelios <78266888+FCKJohni@users.noreply.github.com>
Co-authored-by: Matthew Sherohman <46882932+Shorent@users.noreply.github.com>
  • Loading branch information
4 people authored Oct 12, 2022
1 parent 6fc8a9a commit 286ca6b
Show file tree
Hide file tree
Showing 16 changed files with 243 additions and 98 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
mcVersion: '1.17.1'
- javaVersion: 17
mcVersion: '1.18.2'
- javaVersion: 17
mcVersion: '1.19'
fail-fast: false

steps:
Expand Down
27 changes: 18 additions & 9 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,9 @@
- [Events](#events)
- ["chat" (username, message, translate, jsonMsg, matches)](#chat-username-message-translate-jsonmsg-matches)
- ["whisper" (username, message, translate, jsonMsg, matches)](#whisper-username-message-translate-jsonmsg-matches)
- ["actionBar" (jsonMsg)](#actionbar-jsonmsg)
- ["message" (jsonMsg, position)](#message-jsonmsg-position)
- ["messagestr" (message, messagePosition, jsonMsg)](#messagestr-message-messageposition-jsonmsg)
- ["actionBar" (jsonMsg, verified)](#actionbar-jsonmsg-verified)
- ["message" (jsonMsg, position, sender, verified)](#message-jsonmsg-position-sender-verified)
- ["messagestr" (message, messagePosition, jsonMsg, sender, verified)](#messagestr-message-messageposition-jsonmsg-sender-verified)
- ["inject_allowed"](#inject_allowed)
- ["login"](#login)
- ["spawn"](#spawn)
Expand Down Expand Up @@ -209,8 +209,8 @@
- ["noteHeard" (block, instrument, pitch)](#noteheard-block-instrument-pitch)
- ["pistonMove" (block, isPulling, direction)](#pistonmove-block-ispulling-direction)
- ["chestLidMove" (block, isOpen, block2)](#chestlidmove-block-isopen-block2)
- ["blockBreakProgressObserved" (block, destroyStage)](#blockbreakprogressobserved-block-destroystage)
- ["blockBreakProgressEnd" (block)](#blockbreakprogressend-block)
- ["blockBreakProgressObserved" (block, destroyStage, entity)](#blockbreakprogressobserved-block-destroystage-entity)
- ["blockBreakProgressEnd" (block, entity)](#blockbreakprogressend-block-entity)
- ["diggingCompleted" (block)](#diggingcompleted-block)
- ["diggingAborted" (block)](#diggingaborted-block)
- ["move"](#move)
Expand Down Expand Up @@ -244,7 +244,7 @@
- [bot.waitForChunksToLoad()](#botwaitforchunkstoload)
- [bot.blockInSight(maxSteps, vectorLength)](#botblockinsightmaxsteps-vectorlength)
- [bot.blockAtCursor(maxDistance=256)](#botblockatcursormaxdistance256)
- [bot.entityAtCursor(maxDistance = 3.5)](#botentityatcursormaxdistance35)
- [bot.entityAtCursor(maxDistance=3.5)](#botentityatcursormaxdistance35)
- [bot.blockAtEntityCursor(entity=bot.entity, maxDistance=256)](#botblockatentitycursorentitybotentity-maxdistance256)
- [bot.canSeeBlock(block)](#botcanseeblockblock)
- [bot.findBlocks(options)](#botfindblocksoptions)
Expand Down Expand Up @@ -1106,13 +1106,14 @@ Only emitted when a player chats to you privately.
* `jsonMsg` - unmodified JSON message from the server
* `matches` - array of returned matches from regular expressions. May be null

#### "actionBar" (jsonMsg)
#### "actionBar" (jsonMsg, verified)

Emitted for every server message which appears on the Action Bar.

* `jsonMsg` - unmodified JSON message from the server
* `verified` -> null if non signed, true if signed and correct, false if signed and incorrect

#### "message" (jsonMsg, position)
#### "message" (jsonMsg, position, sender, verified)

Emitted for every server message, including chats.

Expand All @@ -1123,10 +1124,18 @@ Emitted for every server message, including chats.
* system
* game_info

#### "messagestr" (message, messagePosition, jsonMsg)
* `sender` - UUID of sender if known (1.16+), else null

* `verified` -> null if non signed, true if signed and correct, false if signed and incorrect

#### "messagestr" (message, messagePosition, jsonMsg, sender, verified)

Alias for the "message" event but it calls .toString() on the message object to get a string for the message before emitting.

* `sender` - UUID of sender if known (1.16+), else null

* `verified` -> null if non signed, true if signed and correct, false if signed and incorrect

#### "inject_allowed"
Fires when the index file has been loaded, you can load mcData and plugins here but it's better to wait for "spawn" event.

Expand Down
2 changes: 1 addition & 1 deletion examples/jumper.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ bot.on('chat', (username, message) => {
}
break
case 'mount':
entity = bot.nearestEntity((entity) => { return entity.type === 'object' })
entity = bot.nearestEntity((entity) => { return entity.name === 'minecart' })
if (entity) {
bot.mount(entity)
} else {
Expand Down
5 changes: 4 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,10 @@ export interface Player {
gamemode: number
ping: number
entity: Entity
}
profileKeys?: {
publicKey: Buffer
signature: Buffer
}

export interface ChatPattern {
pattern: RegExp
Expand Down
73 changes: 62 additions & 11 deletions lib/plugins/chat.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const { once } = require('events')

const USERNAME_REGEX = '(?:\\(.{1,15}\\)|\\[.{1,15}\\]|.){0,5}?(\\w+)'
const LEGACY_VANILLA_CHAT_REGEX = new RegExp(`^${USERNAME_REGEX}\\s?[>:\\-»\\]\\)~]+\\s(.*)$`)

module.exports = inject

function inject (bot, options) {
Expand Down Expand Up @@ -105,22 +108,43 @@ function inject (bot, options) {

addDefaultPatterns()

// Pre 1.19
bot._client.on('chat', (packet) => {
const msg = ChatMessage.fromNotch(packet.message)

const ChatPositions = {
const chatPositions = {
0: 'chat',
1: 'system',
2: 'game_info'
}
bot.emit('message', msg, chatPositions[packet.position], packet.sender, /* verified */ null)
bot.emit('messagestr', msg.toString(), chatPositions[packet.position], msg, packet.sender, /* verified */ null)
// Position 2 is the action bar
if (packet.position === 2) bot.emit('actionBar', msg, null)
})

const chatPosition = ChatPositions[packet.position]

bot.emit('message', msg, chatPosition)
bot.emit('messagestr', msg.toString(), chatPosition, msg)
// 1.19+
bot._client.on('player_chat', (packet) => {
const message = packet.unsignedChatContent || packet.signedChatContent
let verified = false
const sender = bot.uuidToUsername[packet.senderUuid]
if (sender) {
const { profileKeys } = bot.players[sender]
if (profileKeys) verified = bot._client.verifyMessage(profileKeys.publicKey, packet)
}
const parameters = {
sender: JSON.parse(packet.senderName),
content: JSON.parse(message)
}
const msg = ChatMessage.fromNetwork(packet.type, parameters)
Object.assign(msg, parameters)
bot.emit('message', msg, 'chat', packet.senderUuid, verified)
bot.emit('messagestr', msg.toString(), 'chat', msg, packet.senderUuid, verified)
})

// Position 2 is the action bar
if (packet.position === 2) bot.emit('actionBar', msg)
bot._client.on('system_chat', (packet) => {
const msg = ChatMessage.fromNotch(packet.content)
bot.emit('message', msg, 'system', null)
bot.emit('messagestr', msg.toString(), 'system', msg, null)
})

function chatWithHeader (header, message) {
Expand All @@ -129,14 +153,40 @@ function inject (bot, options) {
throw new Error('Incorrect type! Should be a string or number.')
}

if (bot.supportFeature('signedChat')) {
if (message.startsWith('/')) {
// We send commands as Chat Command packet in 1.19+
const command = message.slice(1)
const timestamp = BigInt(Date.now())
bot._client.write('chat_command', {
command,
timestamp,
salt: 0n,
argumentSignatures: [],
signedPreview: false
})
return
}
}

const lengthLimit = CHAT_LENGTH_LIMIT - header.length
message.split('\n').forEach((subMessage) => {
if (!subMessage) return
let i
let smallMsg
for (i = 0; i < subMessage.length; i += lengthLimit) {
smallMsg = header + subMessage.substring(i, i + lengthLimit)
bot._client.write('chat', { message: smallMsg })
if (bot.supportFeature('signedChat')) {
const timestamp = BigInt(Date.now())
bot._client.write('chat_message', {
message: smallMsg,
timestamp,
salt: 0,
signature: bot._client.profileKeys ? bot._client.signMessage(smallMsg, timestamp) : Buffer.alloc(0)
})
} else {
bot._client.write('chat', { message: smallMsg })
}
}
})
}
Expand Down Expand Up @@ -172,11 +222,12 @@ function inject (bot, options) {
bot.tabComplete = tabComplete

function addDefaultPatterns () {
// 1.19 changes the chat format to move <sender> prefix from message contents to a seperate field.
// TODO: new chat lister to handle this
if (!defaultChatPatterns) return
const USERNAME_REGEX = '(?:\\(.{1,15}\\)|\\[.{1,15}\\]|.){0,5}?(\\w+)'
bot.addChatPattern('whisper', new RegExp(`^${USERNAME_REGEX} whispers(?: to you)?:? (.*)$`), { deprecated: true })
bot.addChatPattern('whisper', new RegExp(`^\\[${USERNAME_REGEX} -> \\w+\\s?\\] (.*)$`), { deprecated: true })
bot.addChatPattern('chat', new RegExp(`^${USERNAME_REGEX}\\s?[>:\\-»\\]\\)~]+\\s(.*)$`), { deprecated: true })
bot.addChatPattern('chat', LEGACY_VANILLA_CHAT_REGEX, { deprecated: true })
}

function awaitMessage (...args) {
Expand Down
31 changes: 24 additions & 7 deletions lib/plugins/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ const entityStatusEvents = {
}

function inject (bot) {
const { objects, mobs, entitiesArray } = bot.registry
const Entity = require('prismarine-entity')(bot.registry)
const Item = require('prismarine-item')(bot.registry)
const { mobs, entitiesArray } = bot.registry
const Entity = require('prismarine-entity')(bot.version)
const Item = require('prismarine-item')(bot.version)
const ChatMessage = require('prismarine-chat')(bot.registry)

// ONLY 1.17 has this destroy_entity packet which is the same thing as entity_destroy packet except the entity is singular
Expand Down Expand Up @@ -69,6 +69,8 @@ function inject (bot) {
bot.uuidToUsername = {}
bot.entities = {}

bot._playerFromUUID = (uuid) => Object.values(bot.players).find(player => player.uuid === uuid)

bot.nearestEntity = (match = (entity) => { return true }) => {
let best = null
let bestDistance = Number.MAX_VALUE
Expand Down Expand Up @@ -192,18 +194,20 @@ function inject (bot) {
}
}

// spawn object/vehicle on versions < 1.19, on versions > 1.19 handles all non-player entities
bot._client.on('spawn_entity', (packet) => {
// spawn object/vehicle
const entity = fetchEntity(packet.entityId)
const entityData = objects[packet.type]
const entityData = bot.registry.entities[packet.type]

entity.type = 'object'
entity.type = entityData.type || 'object'
setEntityData(entity, packet.type, entityData)

if (bot.supportFeature('fixedPointPosition')) {
entity.position.set(packet.x / 32, packet.y / 32, packet.z / 32)
} else if (bot.supportFeature('doublePosition')) {
entity.position.set(packet.x, packet.y, packet.z)
} else if (bot.supportFeature('consolidatedEntitySpawnPacket')) {
entity.headPitch = conv.fromNotchianPitchByte(packet.headPitch)
}

entity.uuid = packet.objectUUID
Expand All @@ -230,6 +234,7 @@ function inject (bot) {
bot.emit('entitySpawn', entity)
})

// This packet is removed since 1.19 and merged into spawn_entity
bot._client.on('spawn_entity_living', (packet) => {
// spawn mob
const entity = fetchEntity(packet.entityId)
Expand Down Expand Up @@ -450,7 +455,13 @@ function inject (bot) {
username: item.name,
ping: item.ping,
uuid: item.UUID,
displayName: new ChatMessage({ text: '', extra: [{ text: item.name }] })
displayName: new ChatMessage({ text: '', extra: [{ text: item.name }] }),
profileKeys: item.crypto
? {
publicKey: item.crypto.publicKey, // DER-encoded public key
signature: item.crypto.signature // Signature
}
: null
}

bot.uuidToUsername[item.UUID] = item.name
Expand All @@ -460,6 +471,12 @@ function inject (bot) {
// Just an Update
player.gamemode = item.gamemode
player.ping = item.ping
if (item.crypto) {
player.profileKeys = {
publicKey: item.crypto.publicKey,
signature: item.crypto.signature
}
}
}

if (item.displayName) {
Expand Down
19 changes: 14 additions & 5 deletions lib/plugins/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,20 @@ function inject (bot, options) {
throw new Error('Unsupported dimension type in login packet')
}

if (bot.supportFeature('dimensionDataIsAvailable')) {
if (packet.dimensionCodec) {
bot.registry.loadDimensionCodec(packet.dimensionCodec)
}

if (bot.supportFeature('dimensionDataInCodec')) { // 1.19+
if (packet.worldType) { // login
bot.game.dimension = packet.worldType.replace('minecraft:', '')
const { minY, height } = bot.registry.dimensionsByName[bot.game.dimension]
bot.game.minY = minY
bot.game.height = height
} else if (packet.dimension) { // respawn
bot.game.dimension = packet.dimension.replace('minecraft:', '')
}
} else if (bot.supportFeature('dimensionDataIsAvailable')) { // 1.18
const dimensionData = nbt.simplify(packet.dimension)
bot.game.minY = dimensionData.min_y
bot.game.height = dimensionData.height
Expand Down Expand Up @@ -70,10 +83,6 @@ function inject (bot, options) {

// varint length-prefixed string as data
bot._client.writeChannel(brandChannel, options.brand)

if (packet.dimensionCodec) {
bot.registry.loadDimensionCodec(packet.dimensionCodec)
}
})

bot._client.on('respawn', (packet) => {
Expand Down
14 changes: 8 additions & 6 deletions lib/plugins/villager.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,20 @@ function inject (bot, { version }) {
if (packet.windowId !== villager.id) return
assert.ok(packet.trades)
villager.trades = packet.trades.map(trade => {
Object.assign(trade, {
inputItem1: Item.fromNotch(trade.inputItem1 || { blockId: -1 }),
inputItem2: Item.fromNotch(trade.inputItem2 || { blockId: -1 }),
outputItem: Item.fromNotch(trade.outputItem || { blockId: -1 })
})
trade.inputs = [trade.inputItem1 = Item.fromNotch(trade.inputItem1 || { blockId: -1 })]
if (trade.inputItem2?.itemCount != null) {
trade.inputs.push(trade.inputItem2 = Item.fromNotch(trade.inputItem2 || { blockId: -1 }))
}

trade.hasItem2 = !!(trade.inputItem2 && trade.inputItem2.type && trade.inputItem2.count)
trade.outputs = [trade.outputItem = Item.fromNotch(trade.outputItem || { blockId: -1 })]

if (trade.demand !== undefined && trade.specialPrice !== undefined) { // the price is affected by demand and reputation
const demandDiff = Math.max(0, Math.floor(trade.inputItem1.count * trade.demand * trade.priceMultiplier))
trade.realPrice = Math.min(Math.max((trade.inputItem1.count + trade.specialPrice + demandDiff), 1), trade.inputItem1.stackSize)
} else {
trade.realPrice = trade.inputItem1.count
}
trade.hasItem2 = !!(trade.inputItem2 && trade.inputItem2.type && trade.inputItem2.count)
return trade
})
if (!ready) {
Expand Down
2 changes: 1 addition & 1 deletion lib/team.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ let MessageBuilder
module.exports = loader

function loader (registry) {
ChatMessage = require('prismarine-chat')(registry) // TODO: update for prismarine-registry
ChatMessage = require('prismarine-chat')(registry)
MessageBuilder = ChatMessage.MessageBuilder
return Team
}
Expand Down
4 changes: 2 additions & 2 deletions lib/version.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
supportedVersions: ['1.8', '1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16', '1.17', '1.18'],
testedVersions: ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2']
supportedVersions: ['1.8', '1.9', '1.10', '1.11', '1.12', '1.13', '1.14', '1.15', '1.16', '1.17', '1.18', '1.19'],
testedVersions: ['1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19']
} // when updating testedVersions, make sure to update CI.yml
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,18 @@
},
"license": "MIT",
"dependencies": {
"minecraft-protocol": "^1.31.0",
"minecraft-data": "^3.15.2",
"minecraft-protocol": "^1.36.0",
"prismarine-biome": "^1.1.1",
"prismarine-block": "^1.13.1",
"prismarine-chat": "^1.3.3",
"prismarine-chunk": "^1.29.0",
"prismarine-chat": "^1.7.1",
"prismarine-chunk": "^1.32.0",
"prismarine-entity": "^2.2.0",
"prismarine-item": "^1.12.1",
"prismarine-nbt": "^2.0.0",
"prismarine-physics": "^1.3.1",
"prismarine-recipe": "^1.3.0",
"prismarine-registry": "^1.0.0",
"prismarine-registry": "^1.5.0",
"prismarine-windows": "^2.5.0",
"prismarine-world": "^3.6.0",
"protodef": "^1.14.0",
Expand Down
Loading

0 comments on commit 286ca6b

Please sign in to comment.