From 3b51a29ae1f89689126ab27ca5174c272a82ecb6 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Tue, 15 May 2018 00:22:05 +0200 Subject: [PATCH 01/18] add blockEntities to map_chunk for 1.12 --- config/default-settings.json | 2 +- src/lib/plugins/world.js | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/config/default-settings.json b/config/default-settings.json index 74dcfda..aa0e035 100644 --- a/config/default-settings.json +++ b/config/default-settings.json @@ -25,5 +25,5 @@ }, "everybody-op":true, "max-entities":100, - "version": "1.8.9" + "version": "1.12.2" } diff --git a/src/lib/plugins/world.js b/src/lib/plugins/world.js index a1f133b..acdde04 100644 --- a/src/lib/plugins/world.js +++ b/src/lib/plugins/world.js @@ -87,7 +87,8 @@ module.exports.player = function (player, serv, settings) { z: chunkZ, groundUp: true, bitMap: 0x0000, - chunkData: Buffer.alloc(0) + chunkData: Buffer.alloc(0), + blockEntities: [] }) } @@ -102,7 +103,8 @@ module.exports.player = function (player, serv, settings) { z: z, groundUp: true, bitMap: 0xffff, - chunkData: chunk.dump() + chunkData: chunk.dump(), + blockEntities: [] }) return Promise.resolve() }) From 069e30cb7e1a12b03c3e6c0583fd059a459c2dda Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Sun, 20 May 2018 14:57:23 +0200 Subject: [PATCH 02/18] use block from inventory instead of heldItem in block_place : fix placing in 1.12 (without breaking 1.8) --- src/lib/plugins/login.js | 2 +- src/lib/plugins/placeBlock.js | 13 +++++++------ src/lib/plugins/useItem.js | 8 ++++---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lib/plugins/login.js b/src/lib/plugins/login.js index 3ecd747..e5b1b4e 100644 --- a/src/lib/plugins/login.js +++ b/src/lib/plugins/login.js @@ -40,7 +40,7 @@ module.exports.player = function (player, serv, settings) { player.username = player._client.username serv.players.push(player) serv.uuidToPlayer[player._client.uuid] = player - player.heldItemSlot = 36 + player.heldItemSlot = 0 player.loadedChunks = {} } diff --git a/src/lib/plugins/placeBlock.js b/src/lib/plugins/placeBlock.js index 6b1d813..0cd0575 100644 --- a/src/lib/plugins/placeBlock.js +++ b/src/lib/plugins/placeBlock.js @@ -13,20 +13,21 @@ const materialToSound = { module.exports.player = function (player, serv, {version}) { const blocks = require('minecraft-data')(version).blocks - player._client.on('block_place', ({direction, heldItem, location} = {}) => { - if (direction === -1 || heldItem.blockId === -1 || !blocks[heldItem.blockId]) return + player._client.on('block_place', ({direction, location} = {}) => { + const heldItem = player.inventory.slots[36 + player.heldItemSlot] + if (direction === -1 || heldItem.type === -1 || !blocks[heldItem.type]) return const referencePosition = new Vec3(location.x, location.y, location.z) const directionVector = directionToVector[direction] const placedPosition = referencePosition.plus(directionVector) player.behavior('placeBlock', { direction: directionVector, heldItem: heldItem, - id: heldItem.blockId, - damage: heldItem.itemDamage, + id: heldItem.type, + damage: heldItem.metadata, position: placedPosition, reference: referencePosition, playSound: true, - sound: 'dig.' + (materialToSound[blocks[heldItem.blockId].material] || 'stone') + sound: 'dig.' + (materialToSound[blocks[heldItem.type].material] || 'stone') }, ({direction, heldItem, position, playSound, sound, id, damage}) => { if (playSound) { serv.playSound(sound, player.world, placedPosition.clone().add(new Vec3(0.5, 0.5, 0.5)), { @@ -36,7 +37,7 @@ module.exports.player = function (player, serv, {version}) { player.inventory.slots[36 + player.heldItemSlot]-- - if (heldItem.blockId !== 323) { + if (heldItem.type !== 323) { player.changeBlock(position, id, damage) } else if (direction === 1) { player.setBlock(position, 63, 0) diff --git a/src/lib/plugins/useItem.js b/src/lib/plugins/useItem.js index d0c5df8..de276a2 100644 --- a/src/lib/plugins/useItem.js +++ b/src/lib/plugins/useItem.js @@ -2,11 +2,11 @@ const Vec3 = require('vec3').Vec3 module.exports.player = function (player, serv, {version}) { const items = require('minecraft-data')(version).items - const Item = require('prismarine-item')(version) - player._client.on('block_place', ({direction, heldItem, location} = {}) => { - if (direction === -1 || heldItem.blockId === -1 || !items[heldItem.blockId]) return - const item = Item.fromNotch(heldItem) + player._client.on('block_place', ({direction, location} = {}) => { + const heldItem = player.inventory.slots[36 + player.heldItemSlot] + if (direction === -1 || heldItem.type === -1 || !items[heldItem.type]) return + const item = heldItem const referencePosition = new Vec3(location.x, location.y, location.z) const directionVector = directionToVector[direction] const position = referencePosition.plus(directionVector) From c01f9dcfca1a36f1ba30a8b263f52baf40b92fac Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Sun, 20 May 2018 15:19:51 +0200 Subject: [PATCH 03/18] chunk unloading for 1.12 and 1.8 --- src/index.js | 7 +++++++ src/lib/plugins/world.js | 23 +++++++++++++++-------- src/lib/version.js | 3 +++ 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 src/lib/version.js diff --git a/src/index.js b/src/index.js index 9a97040..8b76875 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ const mc = require('minecraft-protocol') const EventEmitter = require('events').EventEmitter const path = require('path') const requireIndex = require('./lib/requireindex') +const supportedVersions = require('./lib/version').supportedVersions require('emit-then').register() if (process.env.NODE_ENV === 'dev') { require('longjohn') @@ -31,6 +32,12 @@ class MCServer extends EventEmitter { } connect (options) { + const version = require('minecraft-data')(options.version).version + if (supportedVersions.indexOf(version.majorVersion) === -1) { + throw new Error(`Version ${version.minecraftVersion} is not supported.`) + } + this.majorVersion = version.majorVersion + const plugins = requireIndex(path.join(__dirname, 'lib', 'plugins')) this._server = mc.createServer(options) Object.keys(plugins) diff --git a/src/lib/plugins/world.js b/src/lib/plugins/world.js index acdde04..e5f5cb7 100644 --- a/src/lib/plugins/world.js +++ b/src/lib/plugins/world.js @@ -82,14 +82,21 @@ module.exports.server = async function (serv, {version, worldFolder, generation module.exports.player = function (player, serv, settings) { player.unloadChunk = (chunkX, chunkZ) => { delete player.loadedChunks[chunkX + ',' + chunkZ] - player._client.write('map_chunk', { - x: chunkX, - z: chunkZ, - groundUp: true, - bitMap: 0x0000, - chunkData: Buffer.alloc(0), - blockEntities: [] - }) + + if (serv.majorVersion === '1.8') { + player._client.write('map_chunk', { + x: chunkX, + z: chunkZ, + groundUp: true, + bitMap: 0x0000, + chunkData: Buffer.alloc(0) + }) + } else if (serv.majorVersion === '1.12') { + player._client.write('unload_chunk', { + chunkX, + chunkZ + }) + } } player.sendChunk = (chunkX, chunkZ, column) => { diff --git a/src/lib/version.js b/src/lib/version.js new file mode 100644 index 0000000..b461809 --- /dev/null +++ b/src/lib/version.js @@ -0,0 +1,3 @@ +module.exports = { + supportedVersions: ['1.8', '1.12'] +} From 483dafb6791f89ca39235d81e9711c36ff2579b9 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Sun, 20 May 2018 15:41:39 +0200 Subject: [PATCH 04/18] broken code is not very interesting : removing border.js --- src/lib/plugins/border.js | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 src/lib/plugins/border.js diff --git a/src/lib/plugins/border.js b/src/lib/plugins/border.js deleted file mode 100644 index a96be53..0000000 --- a/src/lib/plugins/border.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports.player = function (player) { - // WIP: Does't work - /* player._client.write('world_border', { - action: 3, - x: 0, - z: 0, - old_radius: 15, - new_radius: 15, - speed: 10000000, - portalBoundary: 15, - warning_time: 5, - warning_blocks: 15 - }); - player._client.write('world_border', { - action: 0, - radius: 15 - }); */ -} From 332ffbca08568f13ffef9b5dbc3bad81ededf3b9 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Sun, 20 May 2018 16:43:43 +0200 Subject: [PATCH 05/18] add soundCategory to named_sound_effect sending --- src/lib/plugins/sound.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/plugins/sound.js b/src/lib/plugins/sound.js index bdefa67..9cfccf1 100644 --- a/src/lib/plugins/sound.js +++ b/src/lib/plugins/sound.js @@ -1,7 +1,7 @@ const Vec3 = require('vec3').Vec3 module.exports.server = function (serv) { - serv.playSound = (sound, world, position, {whitelist, blacklist = [], radius = 32 * 32, volume = 1.0, pitch = 1.0} = {}) => { + serv.playSound = (sound, world, position, {whitelist, blacklist = [], radius = 32 * 32, volume = 1.0, pitch = 1.0, soundCategory = 0} = {}) => { const players = (typeof whitelist !== 'undefined' ? (typeof whitelist instanceof Array ? whitelist : [whitelist]) : serv.getNearby({ world: world, position: position.scaled(32).floored(), @@ -12,6 +12,7 @@ module.exports.server = function (serv) { const pos = (position || player.position.scaled(1 / 32)).scaled(8).floored() player._client.write('named_sound_effect', { soundName: sound, + soundCategory, x: pos.x, y: pos.y, z: pos.z, From 2e749ff49f8eeff2edd4dcbd7c6ea46454e514c3 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Mon, 21 May 2018 00:04:19 +0200 Subject: [PATCH 06/18] move to normal position, fix #40 + multi version for position for 1.8 and 1.12 normal position is now possible because I understood it's necessary to stored the last sent position in order to compensate for the loss of precision next time a relative position packet is sent I introduced a features.json which makes it possible to describe high level differences between version and use that to handle multi version code --- src/index.js | 4 +- src/lib/features.json | 32 ++++++++++++++ src/lib/plugins/blocks.js | 2 +- src/lib/plugins/commands.js | 8 ++-- src/lib/plugins/communication.js | 10 ++--- src/lib/plugins/login.js | 4 +- src/lib/plugins/particle.js | 8 ++-- src/lib/plugins/physics.js | 18 +++++--- src/lib/plugins/portal.js | 2 +- src/lib/plugins/pvp.js | 4 +- src/lib/plugins/sound.js | 14 +++--- src/lib/plugins/spawn.js | 68 ++++++++++++++++++------------ src/lib/plugins/tp.js | 14 +++--- src/lib/plugins/updatePositions.js | 59 +++++++++++++++++--------- src/lib/plugins/world.js | 10 ++--- src/lib/supportFeature.js | 4 ++ 16 files changed, 168 insertions(+), 93 deletions(-) create mode 100644 src/lib/features.json create mode 100644 src/lib/supportFeature.js diff --git a/src/index.js b/src/index.js index 8b76875..c7b3206 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,8 @@ if (process.env.NODE_ENV === 'dev') { require('longjohn') } +const supportFeature = require('./lib/supportFeature') + module.exports = { createMCServer: createMCServer, Behavior: require('./lib/behavior'), @@ -36,7 +38,7 @@ class MCServer extends EventEmitter { if (supportedVersions.indexOf(version.majorVersion) === -1) { throw new Error(`Version ${version.minecraftVersion} is not supported.`) } - this.majorVersion = version.majorVersion + this.supportFeature = feature => supportFeature(feature, version.majorVersion) const plugins = requireIndex(path.join(__dirname, 'lib', 'plugins')) this._server = mc.createServer(options) diff --git a/src/lib/features.json b/src/lib/features.json new file mode 100644 index 0000000..8dbc55e --- /dev/null +++ b/src/lib/features.json @@ -0,0 +1,32 @@ +[ + { + "name": "unloadChunkByEmptyChunk", + "description": "Chunk unloading is done by sending an empty chunk", + "versions": ["1.8"] + }, + { + "name": "unloadChunkDirect", + "description": "Chunk unloading is done by sending directly an unload chunk packet", + "versions": ["1.12"] + }, + { + "name": "fixedPointPosition", + "description": "Entity positions are represented with fixed point numbers", + "versions": ["1.8"] + }, + { + "name": "doublePosition", + "description": "Entity positions are represented with double", + "versions": ["1.12"] + }, + { + "name": "fixedPointDelta", + "description": "Delta of position are represented with fixed point numbers", + "versions": ["1.8"] + }, + { + "name": "fixedPointDelta128", + "description": "Delta of position are represented with fixed point numbers times 128", + "versions": ["1.12"] + } +] \ No newline at end of file diff --git a/src/lib/plugins/blocks.js b/src/lib/plugins/blocks.js index 7f0d957..3220650 100644 --- a/src/lib/plugins/blocks.js +++ b/src/lib/plugins/blocks.js @@ -36,7 +36,7 @@ module.exports.player = function (player, serv) { }, action (params) { let res = params.slice(1, 4) - res = res.map((val, i) => serv.posFromString(val, player.position[['x', 'y', 'z'][i]] / 32)) + res = res.map((val, i) => serv.posFromString(val, player.position[['x', 'y', 'z'][i]])) player.setBlock(new Vec3(res[0], res[1], res[2]).floored(), params[4], params[5] || 0) } }) diff --git a/src/lib/plugins/commands.js b/src/lib/plugins/commands.js index 395bcea..4cccfa5 100644 --- a/src/lib/plugins/commands.js +++ b/src/lib/plugins/commands.js @@ -102,7 +102,7 @@ module.exports.player = function (player, serv, {version}) { return str || false }, action (sel) { - const arr = serv.selectorString(sel, player.position.scaled(1 / 32), player.world) + const arr = serv.selectorString(sel, player.position, player.world) player.chat(JSON.stringify(arr.map(a => a.id))) } }) @@ -119,7 +119,7 @@ module.exports.player = function (player, serv, {version}) { } module.exports.entity = function (entity, serv) { - entity.selectorString = (str) => serv.selectorString(str, entity.position.scaled(1 / 32), entity.world) + entity.selectorString = (str) => serv.selectorString(str, entity.position, entity.world) } module.exports.server = function (serv) { @@ -190,8 +190,8 @@ module.exports.server = function (serv) { }) sample = sample.filter(s => { - if ((notudf(opt.radius) && s.position.scaled(1 / 32).distanceTo(pos) > opt.radius) || - (notudf(opt.minRadius) && s.position.scaled(1 / 32).distanceTo(pos) < opt.minRadius) || + if ((notudf(opt.radius) && s.position.distanceTo(pos) > opt.radius) || + (notudf(opt.minRadius) && s.position.distanceTo(pos) < opt.minRadius) || (notudf(opt.gameMode) && s.gameMode !== opt.gameMode) || (notudf(opt.level) && s.level > opt.level) || (notudf(opt.minLevel) && s.level < opt.minLevel) || diff --git a/src/lib/plugins/communication.js b/src/lib/plugins/communication.js index 11e1662..00d6380 100644 --- a/src/lib/plugins/communication.js +++ b/src/lib/plugins/communication.js @@ -8,12 +8,12 @@ module.exports.server = function (serv) { serv._writeNearby = (packetName, packetFields, loc) => serv._writeArray(packetName, packetFields, serv.getNearby(loc)) - serv.getNearby = ({world, position, radius = 8 * 16 * 32}) => serv.players.filter(player => + serv.getNearby = ({world, position, radius = 8 * 16}) => serv.players.filter(player => player.world === world && player.position.distanceTo(position) <= radius ) - serv.getNearbyEntities = ({world, position, radius = 8 * 16 * 32}) => Object.keys(serv.entities) + serv.getNearbyEntities = ({world, position, radius = 8 * 16}) => Object.keys(serv.entities) .map(eId => serv.entities[eId]) .filter(entity => entity.world === world && @@ -26,7 +26,7 @@ module.exports.entity = function (entity, serv) { .getNearbyEntities({ world: entity.world, position: entity.position, - radius: entity.viewDistance * 32 + radius: entity.viewDistance }) .filter((e) => e !== entity) @@ -34,10 +34,10 @@ module.exports.entity = function (entity, serv) { entity.getOthers = () => serv.entities.filter((e) => e !== entity) - entity.getNearbyPlayers = (radius = entity.viewDistance * 32) => entity.getNearby() + entity.getNearbyPlayers = (radius = entity.viewDistance) => entity.getNearby() .filter((e) => e.type === 'player') - entity.nearbyPlayers = (radius = entity.viewDistance * 32) => entity.nearbyEntities + entity.nearbyPlayers = (radius = entity.viewDistance) => entity.nearbyEntities .filter(e => e.type === 'player') entity._writeOthers = (packetName, packetFields) => diff --git a/src/lib/plugins/login.js b/src/lib/plugins/login.js index e5b1b4e..820f4e6 100644 --- a/src/lib/plugins/login.js +++ b/src/lib/plugins/login.js @@ -55,12 +55,12 @@ module.exports.player = function (player, serv, settings) { reducedDebugInfo: false, maxPlayers: serv._server.maxPlayers }) - player.position = player.spawnPoint.toFixedPosition() + player.position = player.spawnPoint.clone() } function sendChunkWhenMove () { player.on('move', () => { - if (!player.sendingChunks && player.position.distanceTo(player.lastPositionChunkUpdated) > 16 * 32) { player.sendRestMap() } + if (!player.sendingChunks && player.position.distanceTo(player.lastPositionChunkUpdated) > 16) { player.sendRestMap() } }) } diff --git a/src/lib/plugins/particle.js b/src/lib/plugins/particle.js index 1cfa89e..bcbc1db 100644 --- a/src/lib/plugins/particle.js +++ b/src/lib/plugins/particle.js @@ -1,11 +1,11 @@ const Vec3 = require('vec3').Vec3 module.exports.server = function (serv) { - serv.emitParticle = (particle, world, position, {whitelist, blacklist = [], radius = 32 * 32, longDistance = true, size = new Vec3(1, 1, 1), count = 1} = {}) => { + serv.emitParticle = (particle, world, position, {whitelist, blacklist = [], radius = 32, longDistance = true, size = new Vec3(1, 1, 1), count = 1} = {}) => { const players = (typeof whitelist !== 'undefined' ? (whitelist instanceof Array ? whitelist : [whitelist]) : serv.getNearby({ world: world, - position: position.scaled(32).floored(), - radius: radius // 32 blocks, fixed position + position: position, + radius: radius })) serv._writeArray('world_particles', { @@ -45,7 +45,7 @@ module.exports.player = function (player, serv) { return } player.chat('Emitting "' + particle + '" (count: ' + amount + ', size: ' + size.toString() + ')') - serv.emitParticle(particle, player.world, player.position.scaled(1 / 32), {count: amount, size: size}) + serv.emitParticle(particle, player.world, player.position, {count: amount, size: size}) } }) } diff --git a/src/lib/plugins/physics.js b/src/lib/plugins/physics.js index 2288bd1..ecee264 100644 --- a/src/lib/plugins/physics.js +++ b/src/lib/plugins/physics.js @@ -13,9 +13,9 @@ module.exports.entity = function (entity, serv, {version}) { const vSign = getSign(entity.velocity) const sizeSigned = new Vec3(vSign.x * entity.size.x, vSign.y * entity.size.y, vSign.z * entity.size.z) - const xVec = entity.position.offset(entity.velocity.x * delta + sizeSigned.x / 2, 0, 0).scaled(1 / 32).floored() - const yVec = entity.position.offset(0, entity.velocity.y * delta + sizeSigned.y / 2, 0).scaled(1 / 32).floored() - const zVec = entity.position.offset(0, 0, entity.velocity.z * delta + sizeSigned.z / 2).scaled(1 / 32).floored() + const xVec = entity.position.offset(entity.velocity.x * delta + sizeSigned.x / 2, 0, 0) + const yVec = entity.position.offset(0, entity.velocity.y * delta + sizeSigned.y / 2, 0) + const zVec = entity.position.offset(0, 0, entity.velocity.z * delta + sizeSigned.z / 2) // Get block for each (x/y/z)Vec, check to avoid duplicate getBlockTypes const xBlock = blocks[await entity.world.getBlockType(xVec)].boundingBox === 'block' @@ -33,14 +33,18 @@ module.exports.entity = function (entity, serv, {version}) { newPos.y += getMoveAmount('y', yBlock, entity, delta, sizeSigned.y) newPos.z += getMoveAmount('z', zBlock, entity, delta, sizeSigned.z) - // serv.emitParticle(30, serv.overworld, entity.position.scaled(1/32), { size: new Vec3(0, 0, 0) }); + // serv.emitParticle(30, serv.overworld, entity.position, { size: new Vec3(0, 0, 0) }); return { position: newPos, onGround: yBlock } } entity.sendVelocity = (vel, maxVel) => { - const velocity = vel.scaled(32).floored() // Make fixed point - const maxVelocity = maxVel.scaled(32).floored() - const scaledVelocity = velocity.scaled(8000 / 32 / 20).floored() // from fixed-position/second to unit => 1/8000 blocks per tick + const velocity = vel + const maxVelocity = maxVel + let scaledVelocity = velocity.scaled(8000 / 20) // from fixed-position/second to unit => 1/8000 blocks per tick + if (serv.supportFeature('fixedPointPosition')) { + scaledVelocity = scaledVelocity.scaled(1 / 32) + } + scaledVelocity = scaledVelocity.floored() entity._writeNearby('entity_velocity', { entityId: entity.id, velocityX: scaledVelocity.x, diff --git a/src/lib/plugins/portal.js b/src/lib/plugins/portal.js index 02bc915..902a9c2 100644 --- a/src/lib/plugins/portal.js +++ b/src/lib/plugins/portal.js @@ -49,7 +49,7 @@ module.exports.player = function (player, serv, {version}) { const pars = str.split(' ') if (pars.length !== 6) { return false } let [x, y, z, direction, width, height] = pars; - [x, y, z] = [x, y, z].map((val, i) => serv.posFromString(val, player.position[['x', 'y', 'z'][i]] / 32)) + [x, y, z] = [x, y, z].map((val, i) => serv.posFromString(val, player.position[['x', 'y', 'z'][i]])) const bottomLeft = new Vec3(x, y, z) if (direction !== 'x' && direction !== 'z') { throw new UserError('Wrong Direction') } direction = direction === 'x' ? new Vec3(1, 0, 0) : new Vec3(0, 0, 1) diff --git a/src/lib/plugins/pvp.js b/src/lib/plugins/pvp.js index f306498..2f690d4 100644 --- a/src/lib/plugins/pvp.js +++ b/src/lib/plugins/pvp.js @@ -50,9 +50,9 @@ module.exports.player = function (player, serv) { module.exports.entity = function (entity, serv) { entity.takeDamage = ({sound = 'game.player.hurt', damage = 1, velocity = new Vec3(0, 0, 0), maxVelocity = new Vec3(4, 4, 4), animation = true}) => { entity.updateHealth(entity.health - damage) - serv.playSound(sound, entity.world, entity.position.scaled(1 / 32)) + serv.playSound(sound, entity.world, entity.position) - entity.sendVelocity(velocity.scaled(1 / 32), maxVelocity) + entity.sendVelocity(velocity, maxVelocity) if (entity.health <= 0) { if (animation) { diff --git a/src/lib/plugins/sound.js b/src/lib/plugins/sound.js index 9cfccf1..bc8a907 100644 --- a/src/lib/plugins/sound.js +++ b/src/lib/plugins/sound.js @@ -1,15 +1,17 @@ const Vec3 = require('vec3').Vec3 module.exports.server = function (serv) { - serv.playSound = (sound, world, position, {whitelist, blacklist = [], radius = 32 * 32, volume = 1.0, pitch = 1.0, soundCategory = 0} = {}) => { + serv.playSound = (sound, world, position, {whitelist, blacklist = [], radius = 32, volume = 1.0, pitch = 1.0, soundCategory = 0} = {}) => { const players = (typeof whitelist !== 'undefined' ? (typeof whitelist instanceof Array ? whitelist : [whitelist]) : serv.getNearby({ world: world, - position: position.scaled(32).floored(), - radius: radius // 32 blocks, fixed position + position: position, + radius: radius })) players.filter(player => blacklist.indexOf(player) === -1) .forEach(player => { - const pos = (position || player.position.scaled(1 / 32)).scaled(8).floored() + const iniPos = position ? position.scaled(1 / 32) : player.position.scaled(1 / 32) + const pos = iniPos.scaled(8).floored() + // only packet still in fixed position in all versions player._client.write('named_sound_effect', { soundName: sound, soundCategory, @@ -100,13 +102,13 @@ module.exports.player = function (player, serv) { }, action (action) { player.chat('Playing "' + action.sound_name + '" (volume: ' + action.volume + ', pitch: ' + action.pitch + ')') - serv.playSound(action.sound_name, player.world, player.position.scaled(1 / 32), {volume: action.volume, pitch: action.pitch}) + serv.playSound(action.sound_name, player.world, player.position, {volume: action.volume, pitch: action.pitch}) } }) } module.exports.entity = function (entity, serv) { entity.playSoundAtSelf = (sound, opt = {}) => { - serv.playSound(sound, entity.world, entity.position.scaled(1 / 32), opt) + serv.playSound(sound, entity.world, entity.position, opt) } } diff --git a/src/lib/plugins/spawn.js b/src/lib/plugins/spawn.js index d3f8006..874450f 100644 --- a/src/lib/plugins/spawn.js +++ b/src/lib/plugins/spawn.js @@ -29,16 +29,16 @@ module.exports.server = function (serv, options) { } serv.spawnObject = (type, world, position, {pitch = 0, yaw = 0, velocity = new Vec3(0, 0, 0), data = 1, itemId, itemDamage = 0, pickupTime = undefined, deathTime = undefined}) => { - const object = serv.initEntity('object', type, world, position.scaled(32).floored()) + const object = serv.initEntity('object', type, world, position) object.name = objectsById[type].name object.data = data - object.velocity = velocity.scaled(32).floored() + object.velocity = velocity object.pitch = pitch object.yaw = yaw - object.gravity = new Vec3(0, -20 * 32, 0) - object.terminalvelocity = new Vec3(27 * 32, 27 * 32, 27 * 32) - object.friction = new Vec3(15 * 32, 0, 15 * 32) - object.size = new Vec3(0.25 * 32, 0.25 * 32, 0.25 * 32) // Hardcoded, will be dependent on type! + object.gravity = new Vec3(0, -20, 0) + object.terminalvelocity = new Vec3(27, 27, 27) + object.friction = new Vec3(15, 0, 15) + object.size = new Vec3(0.25, 0.25, 0.25) // Hardcoded, will be dependent on type! object.deathTime = deathTime object.pickupTime = pickupTime object.itemId = itemId @@ -48,15 +48,15 @@ module.exports.server = function (serv, options) { } serv.spawnMob = (type, world, position, {pitch = 0, yaw = 0, headPitch = 0, velocity = new Vec3(0, 0, 0), metadata = []} = {}) => { - const mob = serv.initEntity('mob', type, world, position.scaled(32).floored()) + const mob = serv.initEntity('mob', type, world, position) mob.name = mobsById[type].name - mob.velocity = velocity.scaled(32).floored() + mob.velocity = velocity mob.pitch = pitch mob.headPitch = headPitch mob.yaw = yaw - mob.gravity = new Vec3(0, -20 * 32, 0) - mob.terminalvelocity = new Vec3(27 * 32, 27 * 32, 27 * 32) - mob.friction = new Vec3(15 * 32, 0, 15 * 32) + mob.gravity = new Vec3(0, -20, 0) + mob.terminalvelocity = new Vec3(27, 27, 27) + mob.friction = new Vec3(15, 0, 15) mob.size = new Vec3(0.75, 1.75, 0.75) mob.health = 20 mob.metadata = metadata @@ -91,11 +91,11 @@ module.exports.player = function (player, serv, options) { return } if (entity.type === 'mob') { - serv.spawnMob(entity.id, player.world, player.position.scaled(1 / 32), { + serv.spawnMob(entity.id, player.world, player.position, { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } else if (entity.type === 'object') { - serv.spawnObject(entity.id, player.world, player.position.scaled(1 / 32), { + serv.spawnObject(entity.id, player.world, player.position, { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } @@ -122,11 +122,11 @@ module.exports.player = function (player, serv, options) { let s = Math.floor(Math.sqrt(number)) for (let i = 0; i < number; i++) { if (entity.type === 'mob') { - serv.spawnMob(entity.id, player.world, player.position.scaled(1 / 32).offset(Math.floor(i / s * 10), 0, i % s * 10), { + serv.spawnMob(entity.id, player.world, player.position.offset(Math.floor(i / s * 10), 0, i % s * 10), { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } else if (entity.type === 'object') { - serv.spawnObject(entity.id, player.world, player.position.scaled(1 / 32).offset(Math.floor(i / s * 10), 0, i % s * 10), { + serv.spawnObject(entity.id, player.world, player.position.offset(Math.floor(i / s * 10), 0, i % s * 10), { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } @@ -150,11 +150,11 @@ module.exports.player = function (player, serv, options) { if (Object.keys(serv.entities).length > options['max-entities'] - entityTypes.length) { throw new UserError('Too many mobs !') } entityTypes.map(entity => { if (entity.type === 'mob') { - serv.spawnMob(entity.id, player.world, player.position.scaled(1 / 32), { + serv.spawnMob(entity.id, player.world, player.position, { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } else if (entity.type === 'object') { - serv.spawnObject(entity.id, player.world, player.position.scaled(1 / 32), { + serv.spawnObject(entity.id, player.world, player.position, { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } @@ -234,14 +234,26 @@ module.exports.entity = function (entity, serv) { } entity.getSpawnPacket = () => { - const scaledVelocity = entity.velocity.scaled(8000 / 32 / 20).floored() // from fixed-position/second to unit => 1/8000 blocks per tick + let scaledVelocity = entity.velocity.scaled(8000 / 20) // from fixed-position/second to unit => 1/8000 blocks per tick + if (serv.supportFeature('fixedPointPosition')) { + scaledVelocity = scaledVelocity.scaled(1 / 32) + } + scaledVelocity = scaledVelocity.floored() + + let entityPosition + if (serv.supportFeature('fixedPointPosition')) { + entityPosition = entity.position.scaled(32).floored() + } else if (serv.supportFeature('doublePosition')) { + entityPosition = entity.position + } + if (entity.type === 'player') { return { entityId: entity.id, playerUUID: entity._client.uuid, - x: entity.position.x, - y: entity.position.y, - z: entity.position.z, + x: entityPosition.x, + y: entityPosition.y, + z: entityPosition.z, yaw: entity.yaw, pitch: entity.pitch, currentItem: 0, @@ -251,9 +263,9 @@ module.exports.entity = function (entity, serv) { return { entityId: entity.id, type: entity.entityType, - x: entity.position.x, - y: entity.position.y, - z: entity.position.z, + x: entityPosition.x, + y: entityPosition.y, + z: entityPosition.z, pitch: entity.pitch, yaw: entity.yaw, objectData: { @@ -267,9 +279,9 @@ module.exports.entity = function (entity, serv) { return { entityId: entity.id, type: entity.entityType, - x: entity.position.x, - y: entity.position.y, - z: entity.position.z, + x: entityPosition.x, + y: entityPosition.y, + z: entityPosition.z, yaw: entity.yaw, pitch: entity.pitch, headPitch: entity.headPitch, @@ -303,7 +315,7 @@ module.exports.entity = function (entity, serv) { } entity.on('move', () => { - if (entity.position.distanceTo(entity.lastPositionPlayersUpdated) > 2 * 32) { entity.updateAndSpawn() } + if (entity.position.distanceTo(entity.lastPositionPlayersUpdated) > 2) { entity.updateAndSpawn() } }) entity.destroy = () => { diff --git a/src/lib/plugins/tp.js b/src/lib/plugins/tp.js index 926b293..89186c4 100644 --- a/src/lib/plugins/tp.js +++ b/src/lib/plugins/tp.js @@ -18,20 +18,20 @@ module.exports.player = (player, serv) => { if (entityTo.length === 0) throw new UserError('Invalid target') entityTo = entityTo[0] - entitiesFrom.forEach(e => e.teleport(entityTo.position.scaled(1 / 32))) + entitiesFrom.forEach(e => e.teleport(entityTo.position)) } else if (args.length === 3) { - let x = serv.posFromString(args[0], player.position.x / 32) - let y = serv.posFromString(args[1], player.position.y / 32) - let z = serv.posFromString(args[2], player.position.z / 32) + let x = serv.posFromString(args[0], player.position.x) + let y = serv.posFromString(args[1], player.position.y) + let z = serv.posFromString(args[2], player.position.z) player.teleport(new Vec3(x, y, z)) } else if (args.length === 4) { let entitiesFrom = player.selectorString(args[0]) entitiesFrom.forEach(e => e.teleport(new Vec3( - serv.posFromString(args[1], e.position.x / 32), - serv.posFromString(args[2], e.position.y / 32), - serv.posFromString(args[3], e.position.z / 32) + serv.posFromString(args[1], e.position.x), + serv.posFromString(args[2], e.position.y), + serv.posFromString(args[3], e.position.z) ))) } } diff --git a/src/lib/plugins/updatePositions.js b/src/lib/plugins/updatePositions.js index 679a2ae..fde01ca 100644 --- a/src/lib/plugins/updatePositions.js +++ b/src/lib/plugins/updatePositions.js @@ -1,9 +1,5 @@ const Vec3 = require('vec3').Vec3 -Vec3.prototype.toFixedPosition = function () { - return this.scaled(32).floored() -} - module.exports.player = function (player) { player._client.on('look', ({yaw, pitch, onGround} = {}) => sendLook(yaw, pitch, onGround)) @@ -42,19 +38,20 @@ module.exports.player = function (player) { } player._client.on('position', ({x, y, z, onGround} = {}) => { - player.sendPosition((new Vec3(x, y, z)).toFixedPosition(), onGround) + player.sendPosition((new Vec3(x, y, z)), onGround) }) player._client.on('position_look', ({x, y, z, onGround, yaw, pitch} = {}) => { - player.sendPosition((new Vec3(x, y, z)).toFixedPosition(), onGround) + player.sendPosition((new Vec3(x, y, z)), onGround) sendLook(yaw, pitch, onGround) }) player.sendSelfPosition = () => { + // double position in all versions player._client.write('position', { - x: player.position.x / 32, - y: player.position.y / 32, - z: player.position.z / 32, + x: player.position.x, + y: player.position.y, + z: player.position.z, yaw: player.yaw, pitch: player.pitch, flags: 0x00 @@ -62,7 +59,7 @@ module.exports.player = function (player) { } player.teleport = async (position) => { - const notCancelled = await player.sendPosition(position.scaled(32).floored(), false, true) + const notCancelled = await player.sendPosition(position, false, true) if (notCancelled) player.sendSelfPosition() } @@ -83,7 +80,7 @@ module.exports.player = function (player) { } } -module.exports.entity = function (entity) { +module.exports.entity = function (entity, serv) { entity.sendPosition = (position, onGround, teleport = false) => { if (typeof position === 'undefined') throw new Error('undef') if (entity.position.equals(position) && entity.onGround === onGround) return Promise.resolve() @@ -92,23 +89,45 @@ module.exports.entity = function (entity) { onGround: onGround, teleport: teleport }, ({position, onGround}) => { - const diff = position.minus(entity.position) - if (diff.abs().x > 127 || diff.abs().y > 127 || diff.abs().z > 127) { + // known position is very important because the diff (/delta) send to players is floored hence is not precise enough + // storing the known position allows to compensate next time a diff is sent + // without the known position, the error accumulate fast and player position is incorrect from the point of view + // of other players + entity.knownPosition = entity.knownPosition === undefined ? entity.position : entity.knownPosition + + const diff = position.minus(entity.knownPosition) + if (diff.abs().x > 7 || diff.abs().y > 7 || diff.abs().z > 7) { + let entityPosition + + if (serv.supportFeature('fixedPointPosition')) { + entityPosition = position.scaled(32).floored() + } else if (serv.supportFeature('doublePosition')) { + entityPosition = position + } entity._writeOthersNearby('entity_teleport', { entityId: entity.id, - x: position.x, - y: position.y, - z: position.z, + x: entityPosition.x, + y: entityPosition.y, + z: entityPosition.z, yaw: entity.yaw, pitch: entity.pitch, onGround: onGround }) + entity.knownPosition = position } else if (diff.distanceTo(new Vec3(0, 0, 0)) !== 0) { + let delta + if (serv.supportFeature('fixedPointDelta')) { + delta = diff.scaled(32).floored() + entity.knownPosition = entity.knownPosition.plus(delta.scaled(1 / 32)) + } else if (serv.supportFeature('fixedPointDelta128')) { + delta = diff.scaled(32).scaled(128).floored() + entity.knownPosition = entity.knownPosition.plus(delta.scaled(1 / 32 / 128)) + } entity._writeOthersNearby('rel_entity_move', { entityId: entity.id, - dX: diff.x, - dY: diff.y, - dZ: diff.z, + dX: delta.x, + dY: delta.y, + dZ: delta.z, onGround: onGround }) } @@ -121,6 +140,6 @@ module.exports.entity = function (entity) { } entity.teleport = (pos) => { // Overwritten in players inject above - entity.sendPosition(pos.scaled(32), false, true) + entity.sendPosition(pos, false, true) } } diff --git a/src/lib/plugins/world.js b/src/lib/plugins/world.js index e5f5cb7..cec3606 100644 --- a/src/lib/plugins/world.js +++ b/src/lib/plugins/world.js @@ -83,7 +83,7 @@ module.exports.player = function (player, serv, settings) { player.unloadChunk = (chunkX, chunkZ) => { delete player.loadedChunks[chunkX + ',' + chunkZ] - if (serv.majorVersion === '1.8') { + if (serv.supportFeature('unloadChunkByEmptyChunk')) { player._client.write('map_chunk', { x: chunkX, z: chunkZ, @@ -91,7 +91,7 @@ module.exports.player = function (player, serv, settings) { bitMap: 0x0000, chunkData: Buffer.alloc(0) }) - } else if (serv.majorVersion === '1.12') { + } else if (serv.supportFeature('unloadChunkDirect')) { player._client.write('unload_chunk', { chunkX, chunkZ @@ -127,8 +127,8 @@ module.exports.player = function (player, serv, settings) { player.sendNearbyChunks = (view, group) => { player.lastPositionChunkUpdated = player.position - const playerChunkX = Math.floor(player.position.x / 16 / 32) - const playerChunkZ = Math.floor(player.position.z / 16 / 32) + const playerChunkX = Math.floor(player.position.x / 16) + const playerChunkZ = Math.floor(player.position.z / 16) Object.keys(player.loadedChunks) .map((key) => key.split(',').map(a => parseInt(a))) @@ -190,7 +190,7 @@ module.exports.player = function (player, serv, settings) { levelType: 'default' }) await player.findSpawnPoint() - player.position = player.spawnPoint.toFixedPosition() + player.position = player.spawnPoint player.sendSpawnPosition() player.updateAndSpawn() diff --git a/src/lib/supportFeature.js b/src/lib/supportFeature.js new file mode 100644 index 0000000..e09121f --- /dev/null +++ b/src/lib/supportFeature.js @@ -0,0 +1,4 @@ +const features = require('./features') + +module.exports = (feature, version) => + features.some(({name, versions}) => name === feature && versions.includes(version)) From 13ceac4a83c09ef159a558527c4f7d0dc8113bb9 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Wed, 23 May 2018 01:56:55 +0200 Subject: [PATCH 07/18] adapt test for multi-version --- .circleci/config.yml | 1 + src/index.js | 3 +- test/common/parallel.js | 31 +++ test/mineflayer.test.js | 412 ++++++++++++++++++----------------- test/portal.test.js | 472 ++++++++++++++++++++-------------------- test/simple.test.js | 57 +++-- 6 files changed, 523 insertions(+), 453 deletions(-) create mode 100644 test/common/parallel.js diff --git a/.circleci/config.yml b/.circleci/config.yml index df8c3ed..e16171a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,6 +2,7 @@ version: 2 jobs: build: + parallelism: 2 docker: - image: circleci/node:10 steps: diff --git a/src/index.js b/src/index.js index c7b3206..691dac6 100644 --- a/src/index.js +++ b/src/index.js @@ -17,7 +17,8 @@ module.exports = { generations: require('./lib/generations'), experience: require('./lib/experience'), UserError: require('./lib/user_error'), - portal_detector: require('./lib/portal_detector') + portal_detector: require('./lib/portal_detector'), + supportedVersions } function createMCServer (options) { diff --git a/test/common/parallel.js b/test/common/parallel.js new file mode 100644 index 0000000..824116f --- /dev/null +++ b/test/common/parallel.js @@ -0,0 +1,31 @@ +const nodeIndex = parseInt(process.env.CIRCLE_NODE_INDEX) +const nodeTotal = parseInt(process.env.CIRCLE_NODE_TOTAL) +const parallel = process.env.CIRCLE_NODE_INDEX !== undefined && process.env.CIRCLE_NODE_TOTAL !== undefined +const mc = require('../../') + +// expected values : +// (0,4,10) -> (0,2) +// (1,4,10) -> (3,5) +// (2,4,10) -> (6,8) +// (3,4,10) -> (9,9) +function testedRange (nodeIndex, nodeTotal, numberOfVersions) { + const nbFirsts = Math.ceil(numberOfVersions / nodeTotal) + if (nodeIndex === (nodeTotal - 1)) { + return { + firstVersion: nbFirsts * nodeIndex, + lastVersion: numberOfVersions - 1 + } + } + + return { + firstVersion: nodeIndex * nbFirsts, + lastVersion: (nodeIndex + 1) * nbFirsts - 1 + } +} +console.log({ nodeIndex, nodeTotal, versions: mc.supportedVersions.length }) +const { firstVersion, lastVersion } = parallel ? testedRange(nodeIndex, nodeTotal, mc.supportedVersions.length) : { + firstVersion: 0, + lastVersion: mc.supportedVersions.length - 1 +} + +module.exports = { firstVersion, lastVersion } diff --git a/test/mineflayer.test.js b/test/mineflayer.test.js index 20552ed..e0503b8 100644 --- a/test/mineflayer.test.js +++ b/test/mineflayer.test.js @@ -4,7 +4,6 @@ const squid = require('flying-squid') const settings = require('../config/default-settings') const mineflayer = require('mineflayer') const { Vec3 } = require('vec3') -const Item = require('prismarine-item')('1.8') function assertPosEqual (actual, expected) { expect(actual.distanceTo(expected)).toBeLessThan(1) @@ -12,228 +11,243 @@ function assertPosEqual (actual, expected) { const once = require('event-promise') -describe('server with mineflayer connection', () => { - jest.setTimeout(60 * 1000) - let bot - let bot2 - let serv +const { firstVersion, lastVersion } = require('./common/parallel') - async function onGround (bot) { - await new Promise((resolve) => { - const l = () => { - if (bot.entity.onGround) { - bot.removeListener('move', l) - resolve() - } - } - bot.on('move', l) - }) +squid.supportedVersions.forEach((supportedVersion, i) => { + if (!(i >= firstVersion && i <= lastVersion)) { + return } - async function waitMessage (bot, message) { - const msg1 = await once(bot, 'message') - expect(msg1.extra[0].text).toEqual(message) - } + const mcData = require('minecraft-data')(supportedVersion) + const version = mcData.version - async function waitMessages (bot, messages) { - const toReceive = messages.reduce((acc, message) => { - acc[message] = 1 - return acc - }, {}) - const received = {} - return new Promise(resolve => { - const listener = msg => { - const message = msg.extra[0].text - if (!toReceive[message]) throw new Error('Received ' + message + ' , expected to receive one of ' + messages) - if (received[message]) throw new Error('Received ' + message + ' two times') - received[message] = 1 - if (Object.keys(received).length === messages.length) { - bot.removeListener('message', listener) - resolve() - } - } - bot.on('message', listener) - }) - } + const Item = require('prismarine-item')(supportedVersion) - async function waitLoginMessage (bot) { - return Promise.all([waitMessages(bot, ['bot joined the game.', 'bot2 joined the game.'])]) - } + describe('server with mineflayer connection ' + version.minecraftVersion, () => { + jest.setTimeout(60 * 1000) + let bot + let bot2 + let serv - beforeEach(async () => { - const options = settings - options['online-mode'] = false - options['port'] = 25566 - options['view-distance'] = 2 - options['worldFolder'] = undefined - options['logging'] = false - - serv = squid.createMCServer(options) - - await once(serv, 'listening') - bot = mineflayer.createBot({ - host: 'localhost', - port: 25566, - username: 'bot', - version: '1.8' - }) - bot2 = mineflayer.createBot({ - host: 'localhost', - port: 25566, - username: 'bot2', - version: '1.8' - }) - - await Promise.all([once(bot, 'login'), once(bot2, 'login')]) - bot.entity.onGround = false - bot2.entity.onGround = false - }) - - afterEach(async () => { - await serv.quit() - }) - - describe('actions', () => { - function waitSpawnZone (bot, view) { - const nbChunksExpected = (view * 2) * (view * 2) - let c = 0 - return new Promise(resolve => { - const listener = () => { - c++ - if (c === nbChunksExpected) { - bot.removeListener('chunkColumnLoad', listener) + async function onGround (bot) { + await new Promise((resolve) => { + const l = () => { + if (bot.entity.onGround) { + bot.removeListener('move', l) resolve() } } - bot.on('chunkColumnLoad', listener) + bot.on('move', l) }) } - test('can dig', async () => { - await Promise.all([waitSpawnZone(bot, 2), waitSpawnZone(bot2, 2), onGround(bot), onGround(bot2)]) + async function waitMessage (bot, message) { + const msg1 = await once(bot, 'message') + expect(msg1.extra[0].text).toEqual(message) + } - const pos = bot.entity.position.offset(0, -1, 0).floored() - bot.dig(bot.blockAt(pos)) + async function waitMessages (bot, messages) { + const toReceive = messages.reduce((acc, message) => { + acc[message] = 1 + return acc + }, {}) + const received = {} + return new Promise(resolve => { + const listener = msg => { + const message = msg.extra[0].text + if (!toReceive[message]) throw new Error('Received ' + message + ' , expected to receive one of ' + messages) + if (received[message]) throw new Error('Received ' + message + ' two times') + received[message] = 1 + if (Object.keys(received).length === messages.length) { + bot.removeListener('message', listener) + resolve() + } + } + bot.on('message', listener) + }) + } - let [, newBlock] = await once(bot2, 'blockUpdate', {array: true}) - assertPosEqual(newBlock.position, pos) - expect(newBlock.type).toEqual(0) + async function waitLoginMessage (bot) { + return Promise.all([waitMessages(bot, ['bot joined the game.', 'bot2 joined the game.'])]) + } + + beforeEach(async () => { + const PORT = Math.round(30000 + Math.random() * 20000) + const options = settings + options['online-mode'] = false + options['port'] = PORT + options['view-distance'] = 2 + options['worldFolder'] = undefined + options['logging'] = false + options['version'] = version.minecraftVersion + + serv = squid.createMCServer(options) + + await once(serv, 'listening') + bot = mineflayer.createBot({ + host: 'localhost', + port: PORT, + username: 'bot', + version: version.minecraftVersion + }) + bot2 = mineflayer.createBot({ + host: 'localhost', + port: PORT, + username: 'bot2', + version: version.minecraftVersion + }) + + await Promise.all([once(bot, 'login'), once(bot2, 'login')]) + bot.entity.onGround = false + bot2.entity.onGround = false }) - test('can place a block', async () => { - await Promise.all([waitSpawnZone(bot, 2), waitSpawnZone(bot2, 2), onGround(bot), onGround(bot2)]) + afterEach(async () => { + await serv.quit() + }) - const pos = bot.entity.position.offset(0, -2, 0).floored() - bot.dig(bot.blockAt(pos)) + describe('actions', () => { + function waitSpawnZone (bot, view) { + const nbChunksExpected = (view * 2) * (view * 2) + let c = 0 + return new Promise(resolve => { + const listener = () => { + c++ + if (c === nbChunksExpected) { + bot.removeListener('chunkColumnLoad', listener) + resolve() + } + } + bot.on('chunkColumnLoad', listener) + }) + } - let [, newBlock] = await once(bot2, 'blockUpdate', {array: true}) - assertPosEqual(newBlock.position, pos) - expect(newBlock.type).toEqual(0) + test('can dig', async () => { + await Promise.all([waitSpawnZone(bot, 2), waitSpawnZone(bot2, 2), onGround(bot), onGround(bot2)]) - bot.creative.setInventorySlot(36, new Item(1, 1)) - await new Promise((resolve) => { - bot.inventory.on('windowUpdate', (slot, oldItem, newItem) => { - if (slot === 36 && newItem && newItem.type === 1) { resolve() } + const pos = bot.entity.position.offset(0, -1, 0).floored() + bot.dig(bot.blockAt(pos)) + + let [, newBlock] = await once(bot2, 'blockUpdate', {array: true}) + assertPosEqual(newBlock.position, pos) + expect(newBlock.type).toEqual(0) + }) + + test('can place a block', async () => { + await Promise.all([waitSpawnZone(bot, 2), waitSpawnZone(bot2, 2), onGround(bot), onGround(bot2)]) + + const pos = bot.entity.position.offset(0, -2, 0).floored() + bot.dig(bot.blockAt(pos)) + + let [, newBlock] = await once(bot2, 'blockUpdate', {array: true}) + assertPosEqual(newBlock.position, pos) + expect(newBlock.type).toEqual(0) + + bot.creative.setInventorySlot(36, new Item(1, 1)) + await new Promise((resolve) => { + bot.inventory.on('windowUpdate', (slot, oldItem, newItem) => { + if (slot === 36 && newItem && newItem.type === 1) { resolve() } + }) + }) + + bot.placeBlock(bot.blockAt(pos.offset(0, -1, 0)), new Vec3(0, 1, 0)); + + [, newBlock] = await once(bot2, 'blockUpdate', {array: true}) + assertPosEqual(newBlock.position, pos) + expect(newBlock.type).toEqual(1) + }) + }) + + describe('commands', () => { + jest.setTimeout(10 * 1000) + test('has an help command', async () => { + await waitLoginMessage(bot) + bot.chat('/help') + await once(bot, 'message') + }) + test('can use /particle', async () => { + bot.chat('/particle 5 10 100 100 100') + await once(bot._client, 'world_particles') + }) + test('can use /playsound', async () => { + bot.chat('/playsound ambient.weather.rain') + await once(bot, 'soundEffectHeard') + }) + + function waitDragon () { + return new Promise((resolve) => { + const listener = (entity) => { + if (entity.name === 'EnderDragon') { + bot.removeListener('entitySpawn', listener) + resolve() + } + } + bot.on('entitySpawn', listener) + }) + } + + test('can use /summon', async () => { + bot.chat('/summon EnderDragon') + await waitDragon() + }) + test('can use /kill', async () => { + bot.chat('/summon EnderDragon') + await waitDragon() + bot.chat('/kill @e[type=EnderDragon]') + const entity = await once(bot, 'entityDead') + expect(entity.name).toEqual('EnderDragon') + }) + describe('can use /tp', () => { + test('can tp myself', async () => { + bot.chat('/tp 2 3 4') + await once(bot, 'forcedMove') + assertPosEqual(bot.entity.position, new Vec3(2, 3, 4)) + }) + test('can tp somebody else', async () => { + bot.chat('/tp bot2 2 3 4') + await once(bot2, 'forcedMove') + assertPosEqual(bot2.entity.position, new Vec3(2, 3, 4)) + }) + test('can tp to somebody else', async () => { + await onGround(bot) + bot.chat('/tp bot2 bot') + await once(bot2, 'forcedMove') + assertPosEqual(bot2.entity.position, bot.entity.position) + }) + test('can tp with relative positions', async () => { + await onGround(bot) + const initialPosition = bot.entity.position.clone() + bot.chat('/tp ~1 ~-2 ~3') + await once(bot, 'forcedMove') + assertPosEqual(bot.entity.position, initialPosition.offset(1, -2, 3)) + }) + test('can tp somebody else with relative positions', async () => { + await Promise.all([onGround(bot), onGround(bot2)]) + const initialPosition = bot2.entity.position.clone() + bot.chat('/tp bot2 ~1 ~-2 ~3') + await once(bot2, 'forcedMove') + assertPosEqual(bot2.entity.position, initialPosition.offset(1, -2, 3)) }) }) - - bot.placeBlock(bot.blockAt(pos.offset(0, -1, 0)), new Vec3(0, 1, 0)); - - [, newBlock] = await once(bot2, 'blockUpdate', {array: true}) - assertPosEqual(newBlock.position, pos) - expect(newBlock.type).toEqual(1) - }) - }) - - describe('commands', () => { - jest.setTimeout(10 * 1000) - test('has an help command', async () => { - await waitLoginMessage(bot) - bot.chat('/help') - await once(bot, 'message') - }) - test('can use /particle', async () => { - bot.chat('/particle 5 10 100 100 100') - await once(bot._client, 'world_particles') - }) - test('can use /playsound', async () => { - bot.chat('/playsound ambient.weather.rain') - await once(bot, 'soundEffectHeard') - }) - - function waitDragon () { - return new Promise((resolve) => { - const listener = (entity) => { - if (entity.name === 'EnderDragon') { - bot.removeListener('entitySpawn', listener) - resolve() - } - } - bot.on('entitySpawn', listener) + test('can use /deop', async () => { + await waitLoginMessage(bot) + bot.chat('/deop bot') + await waitMessage(bot, 'bot is deopped') + bot.chat('/op bot') + await waitMessage(bot, 'You do not have permission to use this command') + serv.getPlayer('bot').op = true }) - } - - test('can use /summon', async () => { - bot.chat('/summon EnderDragon') - await waitDragon() - }) - test('can use /kill', async () => { - bot.chat('/summon EnderDragon') - await waitDragon() - bot.chat('/kill @e[type=EnderDragon]') - const entity = await once(bot, 'entityDead') - expect(entity.name).toEqual('EnderDragon') - }) - describe('can use /tp', () => { - test('can tp myself', async () => { - bot.chat('/tp 2 3 4') - await once(bot, 'forcedMove') - assertPosEqual(bot.entity.position, new Vec3(2, 3, 4)) + test('can use /setblock', async () => { + await once(bot, 'chunkColumnLoad') + bot.chat('/setblock 1 2 3 95 0') + let [, newBlock] = await once(bot, 'blockUpdate:' + new Vec3(1, 2, 3), {array: true}) + expect(newBlock.type).toEqual(95) }) - test('can tp somebody else', async () => { - bot.chat('/tp bot2 2 3 4') - await once(bot2, 'forcedMove') - assertPosEqual(bot2.entity.position, new Vec3(2, 3, 4)) + test('can use /xp', async () => { + bot.chat('/xp 100') + await once(bot, 'experience') + expect(bot.experience.points).toEqual(100) }) - test('can tp to somebody else', async () => { - await onGround(bot) - bot.chat('/tp bot2 bot') - await once(bot2, 'forcedMove') - assertPosEqual(bot2.entity.position, bot.entity.position) - }) - test('can tp with relative positions', async () => { - await onGround(bot) - const initialPosition = bot.entity.position.clone() - bot.chat('/tp ~1 ~-2 ~3') - await once(bot, 'forcedMove') - assertPosEqual(bot.entity.position, initialPosition.offset(1, -2, 3)) - }) - test('can tp somebody else with relative positions', async () => { - await Promise.all([onGround(bot), onGround(bot2)]) - const initialPosition = bot2.entity.position.clone() - bot.chat('/tp bot2 ~1 ~-2 ~3') - await once(bot2, 'forcedMove') - assertPosEqual(bot2.entity.position, initialPosition.offset(1, -2, 3)) - }) - }) - test('can use /deop', async () => { - await waitLoginMessage(bot) - bot.chat('/deop bot') - await waitMessage(bot, 'bot is deopped') - bot.chat('/op bot') - await waitMessage(bot, 'You do not have permission to use this command') - serv.getPlayer('bot').op = true - }) - test('can use /setblock', async () => { - await once(bot, 'chunkColumnLoad') - bot.chat('/setblock 1 2 3 95 0') - let [, newBlock] = await once(bot, 'blockUpdate:' + new Vec3(1, 2, 3), {array: true}) - expect(newBlock.type).toEqual(95) - }) - test('can use /xp', async () => { - bot.chat('/xp 100') - await once(bot, 'experience') - expect(bot.experience.points).toEqual(100) }) }) }) diff --git a/test/portal.test.js b/test/portal.test.js index 6ff8878..c5db7f1 100644 --- a/test/portal.test.js +++ b/test/portal.test.js @@ -1,250 +1,260 @@ /* eslint-env jest */ -const { - detectFrame, - findPotentialLines, - findBorder, - getAir, - generateLine, - generatePortal, - makeWorldWithPortal -} = require('flying-squid').portal_detector('1.8') +const squid = require('flying-squid') +const { firstVersion, lastVersion } = require('./common/parallel') -const { Vec3 } = require('vec3') +squid.supportedVersions.forEach((supportedVersion, i) => { + if (!(i >= firstVersion && i <= lastVersion)) return -describe('generate portal', () => { - test('generate a line', () => { - expect(generateLine(new Vec3(3, 1, 1), new Vec3(1, 0, 0), 2)).toEqual([new Vec3(3, 1, 1), new Vec3(4, 1, 1)]) - }) + const mcData = require('minecraft-data')(supportedVersion) + const version = mcData.version - test('generate a portal', () => { - expect(generatePortal(new Vec3(2, 1, 1), new Vec3(1, 0, 0), 4, 5)).toEqual({ - bottom: generateLine(new Vec3(3, 1, 1), new Vec3(1, 0, 0), 2), - left: generateLine(new Vec3(2, 2, 1), new Vec3(0, 1, 0), 3), - right: generateLine(new Vec3(5, 2, 1), new Vec3(0, 1, 0), 3), - top: generateLine(new Vec3(3, 5, 1), new Vec3(1, 0, 0), 2), - air: generateLine(new Vec3(3, 2, 1), new Vec3(0, 1, 0), 3).concat(generateLine(new Vec3(4, 2, 1), new Vec3(0, 1, 0), 3)) + const { + detectFrame, + findPotentialLines, + findBorder, + getAir, + generateLine, + generatePortal, + makeWorldWithPortal + } = squid.portal_detector(version.minecraftVersion) + + const { Vec3 } = require('vec3') + + describe('generate portal ' + version.minecraftVersion, () => { + test('generate a line', () => { + expect(generateLine(new Vec3(3, 1, 1), new Vec3(1, 0, 0), 2)).toEqual([new Vec3(3, 1, 1), new Vec3(4, 1, 1)]) }) - }) -}) -describe('detect portal', () => { - jest.setTimeout(60 * 1000) - const portalData = [] - - portalData.push({ - name: 'simple portal frame x', - bottomLeft: new Vec3(2, 1, 1), - direction: new Vec3(1, 0, 0), - width: 4, - height: 5, - additionalAir: [], - additionalObsidian: [] - }) - - portalData.push({ - name: 'simple portal frame z', - bottomLeft: new Vec3(2, 1, 1), - direction: new Vec3(0, 0, 1), - width: 4, - height: 5, - additionalAir: [], - additionalObsidian: [] - }) - - portalData.push({ - name: 'big simple portal frame x', - bottomLeft: new Vec3(2, 1, 1), - direction: new Vec3(1, 0, 0), - width: 10, - height: 10, - additionalAir: [], - additionalObsidian: [] - }) - - portalData.push({ - name: 'simple portal frame x with borders', - bottomLeft: new Vec3(2, 1, 1), - direction: new Vec3(1, 0, 0), - width: 4, - height: 5, - additionalAir: [], - additionalObsidian: [new Vec3(2, 1, 1), new Vec3(5, 1, 1), new Vec3(2, 6, 1), new Vec3(5, 6, 1)] - }) - - const {bottom, left, right, top, air} = generatePortal(new Vec3(2, 1, 2), new Vec3(1, 0, 0), 4, 5) - - portalData.push({ - name: '2 portals', - bottomLeft: new Vec3(2, 1, 1), - direction: new Vec3(1, 0, 0), - width: 4, - height: 5, - additionalAir: air, - additionalObsidian: [].concat([], [bottom, left, right, top]) - }) - - portalData.push({ - name: 'huge simple portal frame z', - bottomLeft: new Vec3(2, 1, 1), - direction: new Vec3(0, 0, 1), - width: 50, - height: 50, - additionalAir: [], - additionalObsidian: [] - }) - - portalData.forEach(({name, bottomLeft, direction, width, height, additionalAir, additionalObsidian}) => { - const portal = generatePortal(bottomLeft, direction, width, height) - const {bottom, left, right, top, air} = portal - describe('Detect ' + name, () => { - const expectedBorder = {bottom, left, right, top} - - let world - beforeAll(async function () { - world = await makeWorldWithPortal(portal, additionalAir, additionalObsidian) - }) - - describe('detect potential first lines', () => { - test('detect potential first lines from bottom left', async () => { - let potentialLines = await findPotentialLines(world, bottom[0], new Vec3(0, 1, 0)) - expect(potentialLines).toContainEqual({ - 'direction': direction, - 'line': bottom - }) - }) - - test('detect potential first lines from bottom right', async () => { - let potentialLines = await findPotentialLines(world, bottom[bottom.length - 1], new Vec3(0, 1, 0)) - expect(potentialLines).toContainEqual({ - 'direction': direction, - 'line': bottom - }) - }) - - test('detect potential first lines from top left', async () => { - let potentialLines = await findPotentialLines(world, top[0], new Vec3(0, -1, 0)) - expect(potentialLines).toContainEqual({ - 'direction': direction, - 'line': top - }) - }) - - test('detect potential first lines from top right', async () => { - let potentialLines = await findPotentialLines(world, top[top.length - 1], new Vec3(0, -1, 0)) - expect(potentialLines).toContainEqual({ - 'direction': direction, - 'line': top - }) - }) - - test('detect potential first lines from left top', async () => { - let potentialLines = await findPotentialLines(world, left[left.length - 1], direction) - expect(potentialLines).toEqual([{ - 'direction': new Vec3(0, 1, 0), - 'line': left - }]) - }) - - test('detect potential first lines from right bottom', async () => { - let potentialLines = await findPotentialLines(world, right[0], direction.scaled(-1)) - expect(potentialLines).toEqual([{ - 'direction': new Vec3(0, 1, 0), - 'line': right - }]) - }) - }) - - describe('find borders', () => { - test('find borders from bottom', async () => { - const border = await findBorder(world, { - 'direction': direction, - 'line': bottom - }, new Vec3(0, 1, 0)) - expect(border).toEqual(expectedBorder) - }) - - test('find borders from top', async () => { - const border = await findBorder(world, { - 'direction': direction, - 'line': top - }, new Vec3(0, -1, 0)) - expect(border).toEqual(expectedBorder) - }) - - test('find borders from left', async () => { - const border = await findBorder(world, { - 'direction': new Vec3(0, 1, 0), - 'line': left - }, direction) - expect(border).toEqual(expectedBorder) - }) - test('find borders from right', async () => { - const border = await findBorder(world, { - 'direction': new Vec3(0, 1, 0), - 'line': right - }, direction.scaled(-1)) - expect(border).toEqual(expectedBorder) - }) - }) - - describe('detect portals', () => { - test('detect portals from bottom left', async () => { - const portals = await detectFrame(world, bottom[0], new Vec3(0, 1, 0)) - expect(portals).toEqual([portal]) - }) - test('detect portals from top left', async () => { - const portals = await detectFrame(world, top[0], new Vec3(0, -1, 0)) - expect(portals).toEqual([portal]) - }) - test('detect portals from right top', async () => { - const portals = await detectFrame(world, right[right.length - 1], direction.scaled(-1)) - expect(portals).toEqual([portal]) - }) - }) - - test('get air', () => { - const foundAir = getAir(expectedBorder) - expect(foundAir).toEqual(air) + test('generate a portal', () => { + expect(generatePortal(new Vec3(2, 1, 1), new Vec3(1, 0, 0), 4, 5)).toEqual({ + bottom: generateLine(new Vec3(3, 1, 1), new Vec3(1, 0, 0), 2), + left: generateLine(new Vec3(2, 2, 1), new Vec3(0, 1, 0), 3), + right: generateLine(new Vec3(5, 2, 1), new Vec3(0, 1, 0), 3), + top: generateLine(new Vec3(3, 5, 1), new Vec3(1, 0, 0), 2), + air: generateLine(new Vec3(3, 2, 1), new Vec3(0, 1, 0), 3).concat(generateLine(new Vec3(4, 2, 1), new Vec3(0, 1, 0), 3)) }) }) }) -}) -describe("doesn't detect non-portal", () => { - const portalData = [] + describe('detect portal ' + version.minecraftVersion, () => { + jest.setTimeout(60 * 1000) + const portalData = [] - portalData.push({ - name: 'simple portal frame x with one obsidian in the middle', - bottomLeft: new Vec3(2, 1, 1), - direction: new Vec3(1, 0, 0), - width: 5, - height: 5, - additionalAir: [], - additionalObsidian: [new Vec3(4, 3, 1)] + portalData.push({ + name: 'simple portal frame x', + bottomLeft: new Vec3(2, 1, 1), + direction: new Vec3(1, 0, 0), + width: 4, + height: 5, + additionalAir: [], + additionalObsidian: [] + }) + + portalData.push({ + name: 'simple portal frame z', + bottomLeft: new Vec3(2, 1, 1), + direction: new Vec3(0, 0, 1), + width: 4, + height: 5, + additionalAir: [], + additionalObsidian: [] + }) + + portalData.push({ + name: 'big simple portal frame x', + bottomLeft: new Vec3(2, 1, 1), + direction: new Vec3(1, 0, 0), + width: 10, + height: 10, + additionalAir: [], + additionalObsidian: [] + }) + + portalData.push({ + name: 'simple portal frame x with borders', + bottomLeft: new Vec3(2, 1, 1), + direction: new Vec3(1, 0, 0), + width: 4, + height: 5, + additionalAir: [], + additionalObsidian: [new Vec3(2, 1, 1), new Vec3(5, 1, 1), new Vec3(2, 6, 1), new Vec3(5, 6, 1)] + }) + + const {bottom, left, right, top, air} = generatePortal(new Vec3(2, 1, 2), new Vec3(1, 0, 0), 4, 5) + + portalData.push({ + name: '2 portals', + bottomLeft: new Vec3(2, 1, 1), + direction: new Vec3(1, 0, 0), + width: 4, + height: 5, + additionalAir: air, + additionalObsidian: [].concat([], [bottom, left, right, top]) + }) + + portalData.push({ + name: 'huge simple portal frame z', + bottomLeft: new Vec3(2, 1, 1), + direction: new Vec3(0, 0, 1), + width: 50, + height: 50, + additionalAir: [], + additionalObsidian: [] + }) + + portalData.forEach(({name, bottomLeft, direction, width, height, additionalAir, additionalObsidian}) => { + const portal = generatePortal(bottomLeft, direction, width, height) + const {bottom, left, right, top, air} = portal + describe('Detect ' + name, () => { + const expectedBorder = {bottom, left, right, top} + + let world + beforeAll(async function () { + world = await makeWorldWithPortal(portal, additionalAir, additionalObsidian) + }) + + describe('detect potential first lines', () => { + test('detect potential first lines from bottom left', async () => { + let potentialLines = await findPotentialLines(world, bottom[0], new Vec3(0, 1, 0)) + expect(potentialLines).toContainEqual({ + 'direction': direction, + 'line': bottom + }) + }) + + test('detect potential first lines from bottom right', async () => { + let potentialLines = await findPotentialLines(world, bottom[bottom.length - 1], new Vec3(0, 1, 0)) + expect(potentialLines).toContainEqual({ + 'direction': direction, + 'line': bottom + }) + }) + + test('detect potential first lines from top left', async () => { + let potentialLines = await findPotentialLines(world, top[0], new Vec3(0, -1, 0)) + expect(potentialLines).toContainEqual({ + 'direction': direction, + 'line': top + }) + }) + + test('detect potential first lines from top right', async () => { + let potentialLines = await findPotentialLines(world, top[top.length - 1], new Vec3(0, -1, 0)) + expect(potentialLines).toContainEqual({ + 'direction': direction, + 'line': top + }) + }) + + test('detect potential first lines from left top', async () => { + let potentialLines = await findPotentialLines(world, left[left.length - 1], direction) + expect(potentialLines).toEqual([{ + 'direction': new Vec3(0, 1, 0), + 'line': left + }]) + }) + + test('detect potential first lines from right bottom', async () => { + let potentialLines = await findPotentialLines(world, right[0], direction.scaled(-1)) + expect(potentialLines).toEqual([{ + 'direction': new Vec3(0, 1, 0), + 'line': right + }]) + }) + }) + + describe('find borders', () => { + test('find borders from bottom', async () => { + const border = await findBorder(world, { + 'direction': direction, + 'line': bottom + }, new Vec3(0, 1, 0)) + expect(border).toEqual(expectedBorder) + }) + + test('find borders from top', async () => { + const border = await findBorder(world, { + 'direction': direction, + 'line': top + }, new Vec3(0, -1, 0)) + expect(border).toEqual(expectedBorder) + }) + + test('find borders from left', async () => { + const border = await findBorder(world, { + 'direction': new Vec3(0, 1, 0), + 'line': left + }, direction) + expect(border).toEqual(expectedBorder) + }) + test('find borders from right', async () => { + const border = await findBorder(world, { + 'direction': new Vec3(0, 1, 0), + 'line': right + }, direction.scaled(-1)) + expect(border).toEqual(expectedBorder) + }) + }) + + describe('detect portals', () => { + test('detect portals from bottom left', async () => { + const portals = await detectFrame(world, bottom[0], new Vec3(0, 1, 0)) + expect(portals).toEqual([portal]) + }) + test('detect portals from top left', async () => { + const portals = await detectFrame(world, top[0], new Vec3(0, -1, 0)) + expect(portals).toEqual([portal]) + }) + test('detect portals from right top', async () => { + const portals = await detectFrame(world, right[right.length - 1], direction.scaled(-1)) + expect(portals).toEqual([portal]) + }) + }) + + test('get air', () => { + const foundAir = getAir(expectedBorder) + expect(foundAir).toEqual(air) + }) + }) + }) }) - portalData.forEach(({name, bottomLeft, direction, width, height, additionalAir, additionalObsidian}) => { - const portal = generatePortal(bottomLeft, direction, width, height) - const {bottom, right, top} = portal - describe("doesn't detect detect " + name, () => { - let world - beforeAll(async function () { - world = await makeWorldWithPortal(portal, additionalAir, additionalObsidian) - }) + describe("doesn't detect non-portal " + version.minecraftVersion, () => { + const portalData = [] - describe("doesn't detect portals", () => { - test("doesn't detect portals from bottom left", async () => { - const portals = await detectFrame(world, bottom[0], new Vec3(0, 1, 0)) - expect(portals).toEqual([]) + portalData.push({ + name: 'simple portal frame x with one obsidian in the middle', + bottomLeft: new Vec3(2, 1, 1), + direction: new Vec3(1, 0, 0), + width: 5, + height: 5, + additionalAir: [], + additionalObsidian: [new Vec3(4, 3, 1)] + }) + + portalData.forEach(({name, bottomLeft, direction, width, height, additionalAir, additionalObsidian}) => { + const portal = generatePortal(bottomLeft, direction, width, height) + const {bottom, right, top} = portal + describe("doesn't detect detect " + name, () => { + let world + beforeAll(async function () { + world = await makeWorldWithPortal(portal, additionalAir, additionalObsidian) }) - test("doesn't detect portals from top left", async () => { - const portals = await detectFrame(world, top[0], new Vec3(0, -1, 0)) - expect(portals).toEqual([]) - }) - test("doesn't detect portals from right top", async () => { - const portals = await detectFrame(world, right[right.length - 1], direction.scaled(-1)) - expect(portals).toEqual([]) + + describe("doesn't detect portals", () => { + test("doesn't detect portals from bottom left", async () => { + const portals = await detectFrame(world, bottom[0], new Vec3(0, 1, 0)) + expect(portals).toEqual([]) + }) + test("doesn't detect portals from top left", async () => { + const portals = await detectFrame(world, top[0], new Vec3(0, -1, 0)) + expect(portals).toEqual([]) + }) + test("doesn't detect portals from right top", async () => { + const portals = await detectFrame(world, right[right.length - 1], direction.scaled(-1)) + expect(portals).toEqual([]) + }) }) }) }) diff --git a/test/simple.test.js b/test/simple.test.js index 59edf81..feb98bb 100644 --- a/test/simple.test.js +++ b/test/simple.test.js @@ -5,33 +5,46 @@ const squid = require('flying-squid') const settings = require('../config/default-settings') -describe('server', () => { - let serv +const { firstVersion, lastVersion } = require('./common/parallel') - beforeAll(done => { - const options = settings - options['online-mode'] = false - options['port'] = 25566 - options['view-distance'] = 2 - options['worldFolder'] = undefined - options['logging'] = false - serv = squid.createMCServer(options) +squid.supportedVersions.forEach((supportedVersion, i) => { + if (!(i >= firstVersion && i <= lastVersion)) { + return + } + const PORT = Math.round(30000 + Math.random() * 20000) - serv.on('listening', () => { - done() + const mcData = require('minecraft-data')(supportedVersion) + const version = mcData.version + + describe(`simple server ${version.minecraftVersion}`, () => { + let serv + + beforeAll(done => { + const options = settings + options['online-mode'] = false + options['port'] = PORT + options['view-distance'] = 2 + options['worldFolder'] = undefined + options['logging'] = false + options['version'] = version.minecraftVersion + serv = squid.createMCServer(options) + + serv.on('listening', () => { + done() + }) }) - }) - afterAll(done => { - serv._server.close() - serv._server.on('close', () => { - done() + afterAll(done => { + serv._server.close() + serv._server.on('close', () => { + done() + }) }) - }) - test('is running', done => { - const client = net.Socket() - client.connect(serv._server.socketServer.address().port, '127.0.0.1', done) - client.on('error', done) + test('is running', done => { + const client = net.Socket() + client.connect(serv._server.socketServer.address().port, '127.0.0.1', done) + client.on('error', done) + }) }) }) From b133bb80932a1774401175e712300f629a87d258 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Fri, 25 May 2018 00:35:49 +0200 Subject: [PATCH 08/18] update diamond square for its multi version support --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a80aa50..4cf77bd 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "node": ">=8" }, "dependencies": { - "diamond-square": "1.0.0", + "diamond-square": "^1.1.0", "emit-then": "^2.0.0", "event-promise": "^0.0.1", "flatmap": "^0.0.3", From 0dabd933e2d4e045395fe3248e2c664072c961d4 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Fri, 25 May 2018 00:44:08 +0200 Subject: [PATCH 09/18] fix delta support in updatePositions for 1.8 --- src/lib/plugins/updatePositions.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/lib/plugins/updatePositions.js b/src/lib/plugins/updatePositions.js index fde01ca..67d3546 100644 --- a/src/lib/plugins/updatePositions.js +++ b/src/lib/plugins/updatePositions.js @@ -96,7 +96,15 @@ module.exports.entity = function (entity, serv) { entity.knownPosition = entity.knownPosition === undefined ? entity.position : entity.knownPosition const diff = position.minus(entity.knownPosition) - if (diff.abs().x > 7 || diff.abs().y > 7 || diff.abs().z > 7) { + + let maxDelta + if (serv.supportFeature('fixedPointDelta')) { + maxDelta = 3 + } else if (serv.supportFeature('fixedPointDelta128')) { + maxDelta = 7 + } + + if (diff.abs().x > maxDelta || diff.abs().y > maxDelta || diff.abs().z > maxDelta) { let entityPosition if (serv.supportFeature('fixedPointPosition')) { From 754ec4bced7ddc221566f652083b8aea9d987100 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Fri, 25 May 2018 00:50:33 +0200 Subject: [PATCH 10/18] use port 0 + run test in // --- package.json | 2 +- test/mineflayer.test.js | 8 ++++---- test/simple.test.js | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 4cf77bd..bff7fc8 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "scripts": { "prepare": "require-self", "lint": "standard test/*.test.js src/**/*.js src/**/**/*.js src/*.js examples/*.js *.js", - "test": "jest --verbose --runInBand" + "test": "jest --verbose" }, "keywords": [], "license": "MIT", diff --git a/test/mineflayer.test.js b/test/mineflayer.test.js index e0503b8..a92684c 100644 --- a/test/mineflayer.test.js +++ b/test/mineflayer.test.js @@ -72,10 +72,9 @@ squid.supportedVersions.forEach((supportedVersion, i) => { } beforeEach(async () => { - const PORT = Math.round(30000 + Math.random() * 20000) const options = settings options['online-mode'] = false - options['port'] = PORT + options['port'] = 0 options['view-distance'] = 2 options['worldFolder'] = undefined options['logging'] = false @@ -84,15 +83,16 @@ squid.supportedVersions.forEach((supportedVersion, i) => { serv = squid.createMCServer(options) await once(serv, 'listening') + const port = serv._server.socketServer.address().port bot = mineflayer.createBot({ host: 'localhost', - port: PORT, + port: port, username: 'bot', version: version.minecraftVersion }) bot2 = mineflayer.createBot({ host: 'localhost', - port: PORT, + port: port, username: 'bot2', version: version.minecraftVersion }) diff --git a/test/simple.test.js b/test/simple.test.js index feb98bb..b96457e 100644 --- a/test/simple.test.js +++ b/test/simple.test.js @@ -11,7 +11,6 @@ squid.supportedVersions.forEach((supportedVersion, i) => { if (!(i >= firstVersion && i <= lastVersion)) { return } - const PORT = Math.round(30000 + Math.random() * 20000) const mcData = require('minecraft-data')(supportedVersion) const version = mcData.version @@ -22,7 +21,7 @@ squid.supportedVersions.forEach((supportedVersion, i) => { beforeAll(done => { const options = settings options['online-mode'] = false - options['port'] = PORT + options['port'] = 0 options['view-distance'] = 2 options['worldFolder'] = undefined options['logging'] = false From d6eede202ee0ee015b185e2bf872f8e4646998e2 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Fri, 25 May 2018 01:06:45 +0200 Subject: [PATCH 11/18] fix timeout --- test/mineflayer.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/mineflayer.test.js b/test/mineflayer.test.js index a92684c..5ceda45 100644 --- a/test/mineflayer.test.js +++ b/test/mineflayer.test.js @@ -159,7 +159,6 @@ squid.supportedVersions.forEach((supportedVersion, i) => { }) describe('commands', () => { - jest.setTimeout(10 * 1000) test('has an help command', async () => { await waitLoginMessage(bot) bot.chat('/help') From c213a06c38dcaead66409a1887cf9395755d6454 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Fri, 25 May 2018 01:20:44 +0200 Subject: [PATCH 12/18] normal timeout + sequential --- package.json | 2 +- test/mineflayer.test.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bff7fc8..4cf77bd 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "scripts": { "prepare": "require-self", "lint": "standard test/*.test.js src/**/*.js src/**/**/*.js src/*.js examples/*.js *.js", - "test": "jest --verbose" + "test": "jest --verbose --runInBand" }, "keywords": [], "license": "MIT", diff --git a/test/mineflayer.test.js b/test/mineflayer.test.js index 5ceda45..a92684c 100644 --- a/test/mineflayer.test.js +++ b/test/mineflayer.test.js @@ -159,6 +159,7 @@ squid.supportedVersions.forEach((supportedVersion, i) => { }) describe('commands', () => { + jest.setTimeout(10 * 1000) test('has an help command', async () => { await waitLoginMessage(bot) bot.chat('/help') From ed2f5df53899202e20a05fee33425c872e61dd20 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Fri, 25 May 2018 23:09:29 +0200 Subject: [PATCH 13/18] fix entities in 1.12 : uuid support --- package.json | 2 +- src/lib/features.json | 10 ++++++++++ src/lib/plugins/entities.js | 2 +- src/lib/plugins/login.js | 17 +++++++++-------- src/lib/plugins/logout.js | 4 ++-- src/lib/plugins/moderation.js | 8 ++++---- src/lib/plugins/physics.js | 8 ++------ src/lib/plugins/spawn.js | 8 ++++++-- test/mineflayer.test.js | 18 ++++++++++++------ 9 files changed, 47 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 4cf77bd..2b7a958 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,6 @@ "minecraft-protocol": "^1.5.2", "mkdirp": "^0.5.1", "moment": "^2.10.6", - "node-uuid": "^1.4.3", "prismarine-chunk": "^1.9.0", "prismarine-entity": "^0.2.0", "prismarine-item": "^1.0.1", @@ -44,6 +43,7 @@ "request": "^2.83.0", "request-promise": "^4.1.0", "spiralloop": "^1.0.2", + "uuid-1345": "^0.99.6", "vec3": "^0.1.3" }, "repository": { diff --git a/src/lib/features.json b/src/lib/features.json index 8dbc55e..838e6e8 100644 --- a/src/lib/features.json +++ b/src/lib/features.json @@ -28,5 +28,15 @@ "name": "fixedPointDelta128", "description": "Delta of position are represented with fixed point numbers times 128", "versions": ["1.12"] + }, + { + "name": "entityCamelCase", + "description": "entity names are in camel case", + "versions": ["1.8"] + }, + { + "name": "entitySnakeCase", + "description": "entity name are in snake case", + "versions": ["1.12"] } ] \ No newline at end of file diff --git a/src/lib/plugins/entities.js b/src/lib/plugins/entities.js index 430a785..1fd1fdf 100644 --- a/src/lib/plugins/entities.js +++ b/src/lib/plugins/entities.js @@ -13,7 +13,7 @@ module.exports.server = function (serv) { const players = serv.getNearby({ world: entity.world, position: entity.position, - radius: 1.5 * 32 // Seems good for now + radius: 1.5 // Seems good for now }) if (players.length) { players[0].collect(entity) diff --git a/src/lib/plugins/login.js b/src/lib/plugins/login.js index 820f4e6..c68e282 100644 --- a/src/lib/plugins/login.js +++ b/src/lib/plugins/login.js @@ -38,8 +38,9 @@ module.exports.player = function (player, serv, settings) { player.crouching = false // Needs added in prismarine-entity later player.op = settings['everybody-op'] // REMOVE THIS WHEN OUT OF TESTING player.username = player._client.username + player.uuid = player._client.uuid serv.players.push(player) - serv.uuidToPlayer[player._client.uuid] = player + serv.uuidToPlayer[player.uuid] = player player.heldItemSlot = 0 player.loadedChunks = {} } @@ -80,7 +81,7 @@ module.exports.player = function (player, serv, settings) { serv._writeAll('player_info', { action: 1, data: [{ - UUID: player._client.uuid, + UUID: player.uuid, gamemode: player.gameMode }] }) @@ -91,7 +92,7 @@ module.exports.player = function (player, serv, settings) { player._writeOthers('player_info', { action: 0, data: [{ - UUID: player._client.uuid, + UUID: player.uuid, name: player.username, properties: player.profileProperties, gamemode: player.gameMode, @@ -102,7 +103,7 @@ module.exports.player = function (player, serv, settings) { player._client.write('player_info', { action: 0, data: serv.players.map((otherPlayer) => ({ - UUID: otherPlayer._client.uuid, + UUID: otherPlayer.uuid, name: otherPlayer.username, properties: otherPlayer.profileProperties, gamemode: otherPlayer.gameMode, @@ -112,7 +113,7 @@ module.exports.player = function (player, serv, settings) { setInterval(() => player._client.write('player_info', { action: 2, data: serv.players.map(otherPlayer => ({ - UUID: otherPlayer._client.uuid, + UUID: otherPlayer.uuid, ping: otherPlayer._client.latency })) }), 5000) @@ -135,12 +136,12 @@ module.exports.player = function (player, serv, settings) { } player.login = async () => { - if (serv.uuidToPlayer[player._client.uuid]) { + if (serv.uuidToPlayer[player.uuid]) { player.kick('You are already connected') return } - if (serv.bannedPlayers[player._client.uuid]) { - player.kick(serv.bannedPlayers[player._client.uuid].reason) + if (serv.bannedPlayers[player.uuid]) { + player.kick(serv.bannedPlayers[player.uuid].reason) return } if (serv.bannedIPs[player._client.socket.remoteAddress]) { diff --git a/src/lib/plugins/logout.js b/src/lib/plugins/logout.js index 605010e..0977415 100644 --- a/src/lib/plugins/logout.js +++ b/src/lib/plugins/logout.js @@ -22,7 +22,7 @@ module.exports.player = function (player, serv) { player._writeOthers('player_info', { action: 4, data: [{ - UUID: player._client.uuid + UUID: player.uuid }] }) player.nearbyPlayers().forEach(otherPlayer => otherPlayer.despawnEntities([player])) @@ -32,7 +32,7 @@ module.exports.player = function (player, serv) { if (index > -1) { serv.players.splice(index, 1) } - delete serv.uuidToPlayer[player._client.uuid] + delete serv.uuidToPlayer[player.uuid] } }) } diff --git a/src/lib/plugins/moderation.js b/src/lib/plugins/moderation.js index 4e8633b..46cb785 100644 --- a/src/lib/plugins/moderation.js +++ b/src/lib/plugins/moderation.js @@ -1,6 +1,6 @@ const moment = require('moment') const rp = require('request-promise') -const nodeUuid = require('node-uuid') +const UUID = require('uuid-1345') module.exports.server = function (serv) { serv.ban = (uuid, reason) => { @@ -20,7 +20,7 @@ module.exports.server = function (serv) { } function uuidInParts (plainUUID) { - return nodeUuid.unparse(nodeUuid.parse(plainUUID)) + return UUID.stringify(UUID.parse(plainUUID)) } serv.getUUIDFromUsername = username => { @@ -65,7 +65,7 @@ module.exports.player = function (player, serv) { player.ban = reason => { reason = reason || 'You were banned!' player.kick(reason) - const uuid = player._client.uuid + const uuid = player.uuid serv.ban(uuid, reason) } player.banIP = reason => { @@ -74,7 +74,7 @@ module.exports.player = function (player, serv) { serv.banIP(player._client.socket.remoteAddress) } - player.pardon = () => serv.pardon(player._client.uuid) + player.pardon = () => serv.pardon(player.uuid) player.commands.add({ base: 'kick', diff --git a/src/lib/plugins/physics.js b/src/lib/plugins/physics.js index ecee264..d19e2e8 100644 --- a/src/lib/plugins/physics.js +++ b/src/lib/plugins/physics.js @@ -60,9 +60,9 @@ module.exports.entity = function (entity, serv, {version}) { function getMoveAmount (dir, block, entity, delta, sizeSigned) { if (block) { entity.velocity[dir] = 0 - return Math.floor(-1 * (entity.position[dir] + sizeSigned / 2 - floorInDirection(entity.position[dir], -sizeSigned))) + return -1 * (entity.position[dir] + sizeSigned / 2 - entity.position[dir]) } else { - return Math.floor(entity.velocity[dir] * delta) + return entity.velocity[dir] * delta } } @@ -70,10 +70,6 @@ module.exports.entity = function (entity, serv, {version}) { return new Vec3(Math.sign(vec.x), Math.sign(vec.y), Math.sign(vec.z)) } - function floorInDirection (a, b) { - return b < 0 ? Math.floor(a) : Math.ceil(a) - } - function addGravity (entity, dir, delta) { if (entity.velocity[dir] < entity.terminalvelocity[dir] && entity.velocity[dir] > -entity.terminalvelocity[dir]) { entity.velocity[dir] = clamp(-entity.terminalvelocity[dir], entity.velocity[dir] + entity.gravity[dir] * delta, entity.terminalvelocity[dir]) diff --git a/src/lib/plugins/spawn.js b/src/lib/plugins/spawn.js index 874450f..46e49df 100644 --- a/src/lib/plugins/spawn.js +++ b/src/lib/plugins/spawn.js @@ -3,7 +3,7 @@ const path = require('path') const requireIndex = require('../requireindex') const plugins = requireIndex(path.join(__dirname, '..', 'plugins')) const UserError = require('flying-squid').UserError - +const UUID = require('uuid-1345') const Vec3 = require('vec3').Vec3 module.exports.server = function (serv, options) { @@ -30,6 +30,7 @@ module.exports.server = function (serv, options) { serv.spawnObject = (type, world, position, {pitch = 0, yaw = 0, velocity = new Vec3(0, 0, 0), data = 1, itemId, itemDamage = 0, pickupTime = undefined, deathTime = undefined}) => { const object = serv.initEntity('object', type, world, position) + object.uuid = UUID.v4() object.name = objectsById[type].name object.data = data object.velocity = velocity @@ -49,6 +50,7 @@ module.exports.server = function (serv, options) { serv.spawnMob = (type, world, position, {pitch = 0, yaw = 0, headPitch = 0, velocity = new Vec3(0, 0, 0), metadata = []} = {}) => { const mob = serv.initEntity('mob', type, world, position) + mob.uuid = UUID.v4() mob.name = mobsById[type].name mob.velocity = velocity mob.pitch = pitch @@ -250,7 +252,7 @@ module.exports.entity = function (entity, serv) { if (entity.type === 'player') { return { entityId: entity.id, - playerUUID: entity._client.uuid, + playerUUID: entity.uuid, x: entityPosition.x, y: entityPosition.y, z: entityPosition.z, @@ -262,6 +264,7 @@ module.exports.entity = function (entity, serv) { } else if (entity.type === 'object') { return { entityId: entity.id, + objectUUID: entity.uuid, type: entity.entityType, x: entityPosition.x, y: entityPosition.y, @@ -278,6 +281,7 @@ module.exports.entity = function (entity, serv) { } else if (entity.type === 'mob') { return { entityId: entity.id, + entityUUID: entity.uuid, type: entity.entityType, x: entityPosition.x, y: entityPosition.y, diff --git a/test/mineflayer.test.js b/test/mineflayer.test.js index a92684c..3b5a059 100644 --- a/test/mineflayer.test.js +++ b/test/mineflayer.test.js @@ -28,6 +28,7 @@ squid.supportedVersions.forEach((supportedVersion, i) => { let bot let bot2 let serv + let entityName async function onGround (bot) { await new Promise((resolve) => { @@ -81,6 +82,11 @@ squid.supportedVersions.forEach((supportedVersion, i) => { options['version'] = version.minecraftVersion serv = squid.createMCServer(options) + if (serv.supportFeature('entityCamelCase')) { + entityName = 'EnderDragon' + } else { + entityName = 'ender_dragon' + } await once(serv, 'listening') const port = serv._server.socketServer.address().port @@ -159,7 +165,7 @@ squid.supportedVersions.forEach((supportedVersion, i) => { }) describe('commands', () => { - jest.setTimeout(10 * 1000) + jest.setTimeout(20 * 1000) test('has an help command', async () => { await waitLoginMessage(bot) bot.chat('/help') @@ -177,7 +183,7 @@ squid.supportedVersions.forEach((supportedVersion, i) => { function waitDragon () { return new Promise((resolve) => { const listener = (entity) => { - if (entity.name === 'EnderDragon') { + if (entity.name === entityName) { bot.removeListener('entitySpawn', listener) resolve() } @@ -187,15 +193,15 @@ squid.supportedVersions.forEach((supportedVersion, i) => { } test('can use /summon', async () => { - bot.chat('/summon EnderDragon') + bot.chat('/summon ' + entityName) await waitDragon() }) test('can use /kill', async () => { - bot.chat('/summon EnderDragon') + bot.chat('/summon ' + entityName) await waitDragon() - bot.chat('/kill @e[type=EnderDragon]') + bot.chat('/kill @e[type=' + entityName + ']') const entity = await once(bot, 'entityDead') - expect(entity.name).toEqual('EnderDragon') + expect(entity.name).toEqual(entityName) }) describe('can use /tp', () => { test('can tp myself', async () => { From 9f2e8e0a364393950718b7ff86f32f39282e3c85 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Fri, 25 May 2018 23:17:49 +0200 Subject: [PATCH 14/18] increase test timeout --- test/mineflayer.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/mineflayer.test.js b/test/mineflayer.test.js index 3b5a059..e5d749e 100644 --- a/test/mineflayer.test.js +++ b/test/mineflayer.test.js @@ -24,7 +24,7 @@ squid.supportedVersions.forEach((supportedVersion, i) => { const Item = require('prismarine-item')(supportedVersion) describe('server with mineflayer connection ' + version.minecraftVersion, () => { - jest.setTimeout(60 * 1000) + jest.setTimeout(100 * 1000) let bot let bot2 let serv @@ -165,7 +165,7 @@ squid.supportedVersions.forEach((supportedVersion, i) => { }) describe('commands', () => { - jest.setTimeout(20 * 1000) + jest.setTimeout(30 * 1000) test('has an help command', async () => { await waitLoginMessage(bot) bot.chat('/help') From f0cdc3bc2347e841e792d61805fa730b557c9974 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Fri, 25 May 2018 23:20:53 +0200 Subject: [PATCH 15/18] add teleportId --- src/lib/plugins/updatePositions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/plugins/updatePositions.js b/src/lib/plugins/updatePositions.js index 67d3546..43665a0 100644 --- a/src/lib/plugins/updatePositions.js +++ b/src/lib/plugins/updatePositions.js @@ -54,7 +54,8 @@ module.exports.player = function (player) { z: player.position.z, yaw: player.yaw, pitch: player.pitch, - flags: 0x00 + flags: 0x00, + teleportId: 1 }) } From d127704022262baac724669382f0e627aee9c277 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Sun, 27 May 2018 13:50:27 +0200 Subject: [PATCH 16/18] Several fixes * fix login sequence : fix spawn position * fix gamemode != 0 : should not remove items from inventory * update pworld and pprovider-anvil --- package.json | 4 ++-- src/lib/plugins/login.js | 4 ++-- src/lib/plugins/placeBlock.js | 2 +- src/lib/plugins/spawn.js | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 2b7a958..8376332 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,9 @@ "prismarine-chunk": "^1.9.0", "prismarine-entity": "^0.2.0", "prismarine-item": "^1.0.1", - "prismarine-provider-anvil": "^2.0.0", + "prismarine-provider-anvil": "^2.1.0", "prismarine-windows": "^1.0.1", - "prismarine-world": "^2.0.0", + "prismarine-world": "^2.1.0", "random-seed": "^0.3.0", "range": "^0.0.3", "request": "^2.83.0", diff --git a/src/lib/plugins/login.js b/src/lib/plugins/login.js index c68e282..e126809 100644 --- a/src/lib/plugins/login.js +++ b/src/lib/plugins/login.js @@ -152,11 +152,11 @@ module.exports.player = function (player, serv, settings) { addPlayer() await player.findSpawnPoint() sendLogin() - await player.sendMap() player.sendSpawnPosition() player.sendSelfPosition() - player.updateHealth(player.health) player.sendAbilities() + await player.sendMap() + player.updateHealth(player.health) updateTime() fillTabList() diff --git a/src/lib/plugins/placeBlock.js b/src/lib/plugins/placeBlock.js index 0cd0575..3436b30 100644 --- a/src/lib/plugins/placeBlock.js +++ b/src/lib/plugins/placeBlock.js @@ -35,7 +35,7 @@ module.exports.player = function (player, serv, {version}) { }) } - player.inventory.slots[36 + player.heldItemSlot]-- + if (player.gameMode === 0) { player.inventory.slots[36 + player.heldItemSlot]-- } if (heldItem.type !== 323) { player.changeBlock(position, id, damage) diff --git a/src/lib/plugins/spawn.js b/src/lib/plugins/spawn.js index 46e49df..9abebaf 100644 --- a/src/lib/plugins/spawn.js +++ b/src/lib/plugins/spawn.js @@ -160,6 +160,7 @@ module.exports.player = function (player, serv, options) { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } + return entity }) .reduce((prec, entity) => { if (prec !== null) { prec.attach(entity) } From efc4ab786db6be4422be292a8dc02ecf2b3eb4f1 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Sun, 27 May 2018 18:24:59 +0200 Subject: [PATCH 17/18] fixed respawn and attach (/pile) --- src/lib/features.json | 20 ++++++++++++++++++++ src/lib/plugins/respawn.js | 12 ++++++++++-- src/lib/plugins/spawn.js | 27 ++++++++++++++++++--------- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/lib/features.json b/src/lib/features.json index 838e6e8..4238a92 100644 --- a/src/lib/features.json +++ b/src/lib/features.json @@ -38,5 +38,25 @@ "name": "entitySnakeCase", "description": "entity name are in snake case", "versions": ["1.12"] + }, + { + "name": "respawnIsPayload", + "description": "respawn field is payload", + "versions": ["1.8"] + }, + { + "name": "respawnIsActionId", + "description": "respawn field is action id", + "versions": ["1.12"] + }, + { + "name": "attachStackEntity", + "description": "attach is used to stack entities", + "versions": ["1.8"] + }, + { + "name": "setPassengerStackEntity", + "description": "set passengers is used to stack entities", + "versions": ["1.12"] } ] \ No newline at end of file diff --git a/src/lib/plugins/respawn.js b/src/lib/plugins/respawn.js index 71348c8..b914acb 100644 --- a/src/lib/plugins/respawn.js +++ b/src/lib/plugins/respawn.js @@ -1,6 +1,14 @@ module.exports.player = function (player, serv) { - player._client.on('client_command', ({payload}) => { - if (payload === 0) { + player._client.on('client_command', (data) => { + let actionId + + if (serv.supportFeature('respawnIsPayload')) { + actionId = data['payload'] + } else if (serv.supportFeature('respawnIsActionId')) { + actionId = data['actionId'] + } + + if (actionId === 0) { player.behavior('requestRespawn', {}, () => { player._client.write('respawn', { dimension: 0, diff --git a/src/lib/plugins/spawn.js b/src/lib/plugins/spawn.js index 9abebaf..1f2131c 100644 --- a/src/lib/plugins/spawn.js +++ b/src/lib/plugins/spawn.js @@ -152,15 +152,14 @@ module.exports.player = function (player, serv, options) { if (Object.keys(serv.entities).length > options['max-entities'] - entityTypes.length) { throw new UserError('Too many mobs !') } entityTypes.map(entity => { if (entity.type === 'mob') { - serv.spawnMob(entity.id, player.world, player.position, { + return serv.spawnMob(entity.id, player.world, player.position, { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } else if (entity.type === 'object') { - serv.spawnObject(entity.id, player.world, player.position, { + return serv.spawnObject(entity.id, player.world, player.position, { velocity: Vec3((Math.random() - 0.5) * 10, Math.random() * 10 + 10, (Math.random() - 0.5) * 10) }) } - return entity }) .reduce((prec, entity) => { if (prec !== null) { prec.attach(entity) } @@ -328,12 +327,22 @@ module.exports.entity = function (entity, serv) { } entity.attach = (attachedEntity, leash = false) => { - const p = { - entityId: attachedEntity.id, - vehicleId: entity.id, - leash: leash + if (serv.supportFeature('attachStackEntity') || (serv.supportFeature('setPassengerStackEntity') && leash)) { + const p = { + entityId: attachedEntity.id, + vehicleId: entity.id, + leash: leash + } + if (entity.type === 'player') { entity._client.write('attach_entity', p) } + entity._writeOthersNearby('attach_entity', p) + } + if (serv.supportFeature('setPassengerStackEntity')) { + const p = { + entityId: entity.id, + passengers: [ attachedEntity.id ] + } + if (entity.type === 'player') { entity._client.write('set_passengers', p) } + entity._writeOthersNearby('set_passengers', p) } - if (entity.type === 'player') { entity._client.write('attach_entity', p) } - entity._writeOthersNearby('attach_entity', p) } } From 36b11e6163368013fa790c9c23ba0893ab8751e3 Mon Sep 17 00:00:00 2001 From: Romain Beaumont Date: Sun, 27 May 2018 19:14:41 +0200 Subject: [PATCH 18/18] fixed nether by adding some glowstone to have some light (necessary in 1.12) --- src/lib/worldGenerations/nether.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/worldGenerations/nether.js b/src/lib/worldGenerations/nether.js index ded6ab4..f156fbe 100644 --- a/src/lib/worldGenerations/nether.js +++ b/src/lib/worldGenerations/nether.js @@ -16,7 +16,7 @@ function generation ({version, seed, level = 50} = {}) { let data if (y < bedrockheightbottom) block = 7 - else if (y < level) block = 87 + else if (y < level) block = seedRand(50) === 0 ? 89 : 87 else if (y > 127 - bedrockheighttop) block = 7 const pos = new Vec3(x, y, z)