Skip to content

Commit

Permalink
Add 1.16.5 support and future-proof version system (#645)
Browse files Browse the repository at this point in the history
* 1.16.5 players can now join

* oops forgot abot this

* sign block doesn't exists, only oak_sign

* Add 1.16.5 to version.js

* refactor: oops remove this console.log

* fix: loginPacket may be null

* Revert unneccesary example change

* Remove another out of scope change

* Update version list to include more recent and remove redundant

* Supported versions should be explicit

* Explicit support versions

* Detailed feature data

* Use previous supportFeature data

* Remove bad newline

* Test suite runs again (not sure if it passes)

* Add newer versions to feature data

* Fix duplicate item place handlers

* Fix another duplicate registration bug

* Remove non-1.16 versions

* Fix wrong block bug

* Empty commit to re-trigger CI

* Hopefully this fixes the issue with the failing tests

* Standard JS

* Fixed placeBlock

* A few logic fixes

* Slight logic improvement

* Update mineflayer to latest

* Small bug I found

* Another stupid bug fixed

* Why the heck should *that* be necessary?

* Update supportFeature stuff

* Use mineflayer version system

* Couple support fixes

* Remove dumpCodec and move to 1.16.5

* Lint, begone!

* Use updated node-minecraft-data

* Use actual mineflayer version support logic

* I'm stupid lol

* Eliminate serv.supportFeature

* Remove minecraft-data package

* Typos

* Fix a couple lint issues

* Linter is happy now

---------

Co-authored-by: ThallesP <[email protected]>
Co-authored-by: Gavin John <>
Co-authored-by: Romain Beaumont <[email protected]>
  • Loading branch information
3 people authored Jan 6, 2024
1 parent 98fc454 commit f776de8
Show file tree
Hide file tree
Showing 40 changed files with 341 additions and 728 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
node-version: 18.x
- run: npm i && npm run lint

PrepareSupportedVersions:
PrepareTestedVersions:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
Expand All @@ -32,15 +32,15 @@ jobs:
- id: set-matrix
run: |
node -e "
const supportedVersions = require('./src/lib/version').supportedVersions;
console.log('matrix='+JSON.stringify({'include': supportedVersions.map(mcVersion => ({mcVersion}))}))
const { testedVersions } = require('./src/lib/version');
console.log('matrix='+JSON.stringify({'include': testedVersions.map(mcVersion => ({mcVersion}))}))
" >> $GITHUB_OUTPUT
Test:
needs: PrepareSupportedVersions
needs: PrepareTestedVersions
runs-on: ubuntu-latest
strategy:
matrix: ${{fromJson(needs.PrepareSupportedVersions.outputs.matrix)}}
matrix: ${{fromJson(needs.PrepareTestedVersions.outputs.matrix)}}
fail-fast: false

steps:
Expand Down
2 changes: 1 addition & 1 deletion config/default-settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@
},
"everybody-op": false,
"max-entities":100,
"version": "1.16.1"
"version": "1.16.5"
}
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ mcServer.createMCServer({
},
'everybody-op': true,
'max-entities': 100,
'version': '1.16.1'
'version': '1.16.5'
})
```

Expand Down
16 changes: 11 additions & 5 deletions docs/block-updates.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
### Block placement customization

Some blocks have a different id than the item they are spawned from or have a metadata dependent on the placement context. These blocks can be customized by registering an itemPlace handler on the held item type (one handler per item type). For instance, the plugin that customize the signs placement:

```javascript
module.exports.server = (serv, { version }) => {
const mcData = require('minecraft-data')(version)
const registry = require('prismarine-registry')(version)

const oakSignType = mcData.blocksByName.standing_sign.id
const oakWallSignType = mcData.blocksByName.wall_sign.id
const oakSignType = registry.blocksByName.standing_sign.id
const oakWallSignType = registry.blocksByName.wall_sign.id

serv.on('asap', () => {
serv.onItemPlace('sign', ({ item, direction, angle }) => {
Expand All @@ -18,11 +19,13 @@ module.exports.server = (serv, { version }) => {
})
}
```

The argument given to the handler is an object containing the held item that triggered the event, the direction (face) on which the player clicked, the angle of the player around the placed block. It should return an object containing the id and data of the block to place.

### Block interaction

This handler is called when a player interact with a block.

```javascript
module.exports.server = (serv, { version }) => {
serv.on('asap', () => {
Expand All @@ -33,14 +36,16 @@ module.exports.server = (serv, { version }) => {
}
serv.onBlockInteraction('powered_repeater', repeaterInteraction)
serv.onBlockInteraction('unpowered_repeater', repeaterInteraction)
}
})
}
```

The argument given to the handler is an object containing the clicked block and the player. It should return true if the block interaction occurred and the block placement should be cancelled.

### Block update

This handler is called when a block of the given type is updated. It should verify that the block state is still correct according to the game's rules. It is triggered when a neighboring block has been modified.

```javascript
module.exports.server = (serv, { version }) => {
serv.on('asap', () => {
Expand All @@ -57,7 +62,8 @@ module.exports.server = (serv, { version }) => {
// ... rest of redstone logic
return changed
})
}
})
}
```

The arguments of the handler are the world in which the update occurred, the block, fromTick the tick at which the update was triggered, the tick the update was scheduled to (current tick), and optional data (null most of the time) that can be used to transmit data between block updates. The handler should return true if the block was changed so the update manager can send a multiBlockChange packet for all the changes that occurred within the tick. The state of the block should be modified by using the world's setBlockXXX functions instead of serv.setBlock (that would send redundant updates to players).
2 changes: 1 addition & 1 deletion examples/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ mcServer.createMCServer({
},
'everybody-op': true,
'max-entities': 100,
version: '1.16.1'
version: '1.16.5'
})
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"exit-hook": "^2.2.1",
"flatmap": "^0.0.3",
"long": "^5.1.0",
"minecraft-data": "^3.42.1",
"minecraft-protocol": "^1.44.0",
"moment": "^2.10.6",
"needle": "^2.5.0",
Expand All @@ -47,6 +46,7 @@
"prismarine-item": "^1.14.0",
"prismarine-nbt": "^2.2.1",
"prismarine-provider-anvil": "^2.7.0",
"prismarine-registry": "^1.7.0",
"prismarine-windows": "^2.8.0",
"prismarine-world": "^3.6.2",
"random-seed": "^0.3.0",
Expand All @@ -69,7 +69,7 @@
"flying-squid": "file:.",
"longjohn": "^0.2.12",
"minecraft-wrap": "^1.2.3",
"mineflayer": "^4.0.0",
"mineflayer": "^4.17.0",
"mocha": "^10.0.0",
"standard": "^17.0.0"
}
Expand Down
19 changes: 11 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ if (typeof process !== 'undefined' && !process.browser && process.platform !== '
const { createServer } = require('minecraft-protocol')

const EventEmitter = require('events').EventEmitter
const supportedVersions = require('./lib/version').supportedVersions
const { testedVersions, latestSupportedVersion, oldestSupportedVersion } = require('./lib/version')
const Command = require('./lib/command')
const plugins = require('./lib/plugins')
require('emit-then').register()
if (process.env.NODE_ENV === 'dev') {
require('longjohn')
}

const supportFeature = require('./lib/supportFeature')

module.exports = {
createMCServer,
Behavior: require('./lib/behavior'),
Expand All @@ -26,7 +24,7 @@ module.exports = {
experience: require('./lib/experience'),
UserError: require('./lib/user_error'),
portal_detector: require('./lib/portal_detector'),
supportedVersions
testedVersions
}

function createMCServer (options) {
Expand All @@ -45,11 +43,16 @@ class MCServer extends EventEmitter {
}

connect (options) {
const version = require('minecraft-data')(options.version).version
if (!supportedVersions.some(v => v.includes(version.majorVersion))) {
throw new Error(`Version ${version.minecraftVersion} is not supported.`)
const registry = require('prismarine-registry')(options.version)
if (!registry?.version) throw new Error(`Server version '${registry?.version}' is not supported, no data for version`)

const versionData = registry.version
if (versionData['>'](latestSupportedVersion)) {
throw new Error(`Server version '${registry?.version}' is not supported. Latest supported version is '${latestSupportedVersion}'.`)
} else if (versionData['<'](oldestSupportedVersion)) {
throw new Error(`Server version '${registry?.version}' is not supported. Oldest supported version is '${oldestSupportedVersion}'.`)
}
this.supportFeature = feature => supportFeature(feature, version.majorVersion)

this.commands = new Command({})
this._server = createServer(options)

Expand Down
132 changes: 0 additions & 132 deletions src/lib/features.json

This file was deleted.

35 changes: 29 additions & 6 deletions src/lib/plugins/blockUpdates.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
const { performance } = require('perf_hooks')

let multiBlockChangeHasTrustEdges
class ChunkUpdates {
constructor () {
this.chunks = new Map()
}

add (pos) {
const chunkX = Math.floor(pos.x / 16)
const chunkY = Math.floor(pos.y / 16)
const chunkZ = Math.floor(pos.z / 16)
const key = `${chunkX},${chunkZ}`
const key = `${chunkX},${chunkY},${chunkZ}`
if (!this.chunks.has(key)) {
this.chunks.set(key, { chunkX, chunkZ, updates: new Set() })
this.chunks.set(key, { chunkX, chunkZ, chunkY, updates: new Set() })
}
this.chunks.get(key).updates.add(pos)
}
Expand All @@ -25,24 +27,45 @@ class ChunkUpdates {

async getMultiBlockPackets (world) {
const packets = []
for (const { chunkX, chunkZ, updates } of this.chunks.values()) {

for (const { chunkX, chunkZ, chunkY, updates } of this.chunks.values()) {
const records = []
for (const p of updates.values()) {
const state = await world.getBlockStateId(p)

if (multiBlockChangeHasTrustEdges) {
records.push(state << 12 | (p.x << 8 | p.z << 4 | p.y))
continue
}

records.push({
horizontalPos: ((p.x & 0xF) << 4) | (p.z & 0xF),
y: p.y,
blockId: state
})
}
packets.push({ chunkX, chunkZ, records })

if (multiBlockChangeHasTrustEdges) {
packets.push({
chunkCoordinates: {
x: chunkX,
y: chunkY, // TODO:
z: chunkZ
},
notTrustEdges: false, // Update light's "Trust Edges" is always true
records
})
} else packets.push({ chunkX, chunkZ, records })
}

return packets
}
}

module.exports.server = (serv, { version }) => {
const mcData = require('minecraft-data')(version)
const registry = require('prismarine-registry')(version)

multiBlockChangeHasTrustEdges = registry.supportFeature('multiBlockChangeHasTrustEdges')

serv.MAX_UPDATES_PER_TICK = 10000

Expand Down Expand Up @@ -109,7 +132,7 @@ module.exports.server = (serv, { version }) => {
* It should return true if the block changed its state
*/
serv.onBlockUpdate = (name, handler) => {
const block = mcData.blocksByName[name]
const block = registry.blocksByName[name]
if (updateHandlers.has(block.id)) {
serv.warn(`onBlockUpdate handler was registered twice for ${name}`)
}
Expand Down
Loading

0 comments on commit f776de8

Please sign in to comment.