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))