commit 817912180be6d9cb20e79428eded376aff376818 Author: roblabla Date: Sun Jan 5 02:01:01 2014 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..59b2a01 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +mc-plain-server +============== + +This is mc-plain-server. + + diff --git a/mc-plain-server.js b/mc-plain-server.js new file mode 100644 index 0000000..8292ae5 --- /dev/null +++ b/mc-plain-server.js @@ -0,0 +1,110 @@ +var mc = require('minecraft-protocol'); +var states = mc.protocol.states; +var World = require('./world'); + +var options = { + motd: 'Vox Industries', + 'max-players': 127, + port: 25565, + 'online-mode': false, +}; + +var world = new World(); +for (var x = 0; x < 16;x++) { + for (var z = 0; z < 16; z++) { + world.put(x, 50, z, "block_data", 2); + } +} + +var server = mc.createServer(options); + +server.on('login', function(client) { + broadcast({ text: client.username+' joined the game.', color: "yellow" }); + var addr = client.socket.remoteAddress + ':' + client.socket.remotePort; + console.log(client.username+' connected', '('+addr+')'); + + client.on('end', function() { + broadcast({ text: client.username+' left the game.', color: "yellow" }); + console.log(client.username+' disconnected', '('+addr+')'); + }); + + // send init data so client will start rendering world + client.write(0x01, { + entityId: client.id, + levelType: 'default', + gameMode: 1, + dimension: 0, + difficulty: 0, + maxPlayers: server.maxPlayers + }); + var chunkBulk = world.packMapChunkBulk([0,0], function(err, packetData) { + if (err) { + console.log(err); + return; + } + client.write(0x26, packetData); + client.write(0x08, { + x: 6, + y: 53, + z: 6, + yaw: 0, + pitch: 0, + onGround: true + }); + console.log("Written position, player should spawn"); + client.write(0x03, { + age: [0,0], + time: [0,1] + }); + client.write(0x2B, { + reason: 3, + gameMode: 1 + }); + }); + client.on([states.PLAY, 0x01], function(data) { + var message = '<'+client.username+'>' + ' ' + data.message; + broadcast(message, client.username); + console.log(message); + }); + client.on('packet', function(packet) { + console.log(packet); + }) +}); + +server.on('error', function(error) { + console.log('Error:', error); +}); + +server.on('listening', function() { + console.log('Server listening on port', server.socketServer.address().port); +}); + +function broadcast(message, username) { + var client, translate; + translate = username ? 'chat.type.announcement' : 'chat.type.text'; + username = username || 'Server'; + for (var clientId in server.clients) { + if (!server.clients.hasOwnProperty(clientId)) continue; + + client = server.clients[clientId]; + var msg = { + translate: translate, + "with": [ + username + ] + }; + if (typeof message === "string") { + msg["with"].push(message); + } else { + Object.keys(message).forEach(function(key) { + if (key === "text") { + msg["with"].push(message[key]); + } else { + msg[key] = message[key]; + } + }); + } + + client.write(0x02, { message: JSON.stringify(msg) }); + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8af217e --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "mc-plain-server", + "description": "", + "version": "0.0.1", + "main": "./mc-plain-server.js", + "author": { + "name": "roblabla", + "email": "robinlambertz.dev@gmail.com" + }, + "keywords": [], + "licenses": { + "type": "mit" + }, + "dependencies": { + "minecraft-protocol": "git://github.com/roblabla/node-minecraft-protocol.git#feature-mc1.7", + "buffers": "0.1.1" + }, + "repository": { + "type": "git", + "url": "git://github.com/YOUR_LOGIN/mc-plain-server.git" + }, + "bugs": { + "url": "http://github.com/YOUR_LOGIN/mc-plain-server/issues" + } +} diff --git a/world.js b/world.js new file mode 100644 index 0000000..6d9d712 --- /dev/null +++ b/world.js @@ -0,0 +1,315 @@ +var util = require('util') + , zlib = require('zlib') + , Buffers = require('buffers'); + +module.exports = World; + +function BiomeData() { + this.data = null; +} + +BiomeData.prototype.fill = function() { + if (!this.data) { + this.data = new Buffer(256); + for (var i = 0;i < 256;i++) { + this.data[i] = 0; + } + } +} + +BiomeData.prototype.unpack = function(buf) { + this.data = buf.slice(0, 256); +} + +BiomeData.prototype.pack = function() { + this.fill(); + return this.data; +} + +BiomeData.prototype.get = function(x, y, z) { + this.fill(); + return this.data[x + ((y * 16) + z) * 16]; +} + +BiomeData.prototype.put = function(x, y, z, data) { + this.fill(); + this.data[x + ((y * 16) + z) * 16] = data; +} + +function ChunkData() { + this.length = 16*16*16; + this.data = null; +} + +ChunkData.prototype.fill = function() { + if (!this.data) { + this.data = new Buffer(this.length); + for (var i = 0;i < this.length;i++) { + this.data[i] = 0; + } + } +} + +ChunkData.prototype.unpack = function(buff) { + this.data = buff.slice(0, this.length); +} + +ChunkData.prototype.pack = function() { + this.fill(); + return this.data; +} + +ChunkData.prototype.get = function(x, y, z) { + this.fill(); + return this.data[x + ((y * 16) + z) * 16]; +} + +ChunkData.prototype.put = function(x, y, z, data) { + this.fill(); + this.data[x + ((y * 16) + z) * 16] = data; +} + +function ChunkDataNibble() { + this.length = 16*16*8; + this.data = null; +} + +util.inherits(ChunkDataNibble, ChunkData); + +ChunkDataNibble.prototype.get = function(x, y, z) { + this.fill(); + var r = x % 2; + x = Math.floor(x/2); + var i = x + ((y * 16) + z) * 16; + + if (r === 0) { + return this.data[i] >>> 4; + } else { + return this.data[i] & 0x0F; + } +} + +ChunkDataNibble.prototype.put = function(x, y, z, data) { + this.fill(); + var r = x % 2; + x = Math.floor(x/2); + var i = x + ((y * 16) + z) * 16; + + if (r === 0) { + this.data[i] = (this.data[i] & 0x0F) | ((data & 0x0F) << 4); + } else { + this.data[i] = (this.data[i] & 0xF0) | (this.data & 0x0F); + } +} + +function Chunk() { + this.block_data = new ChunkData(); + this.block_meta = new ChunkDataNibble(); + this.block_add = new ChunkDataNibble(); + this.light_block = new ChunkDataNibble(); + this.light_sky = new ChunkDataNibble(); +} + +function ChunkColumn() { + this.chunks = new Array(16); + for (var i = 0;i < 16;i++) this.chunks[i] = null; + + this.biome = new BiomeData(); +} + +ChunkColumn.prototype.unpack = function(buff, mask1, mask2, skylight) { + if (typeof skylight == "undefined") skylight = true; + + this.unpack_section(buff, "block_data", mask1); + this.unpack_section(buff, "block_meta", mask1); + this.unpack_section(buff, "light_block", mask1); + if (skylight) + this.unpack_section(buff, "light_sky", mask1); + this.unpack_section(buff, "block_add", mask2); + this.biome.unpack(buff); +} + +ChunkColumn.prototype.unpack_section = function(buff, section, mask) { + for (var i = 0; i < 16; i++) { + if (mask & (1 << i)) { + if (this.chunks[i] === null) { + this.chunks[i] = new Chunk(); + } + this.chunks[i][section].unpack(buff); + } + } +} + +ChunkColumn.prototype.pack = function() { + var bufs = new Buffers(); + var mask1 = 0; + for (var i = 0; i < 16; i++) { + if (this.chunks[i] !== null) { + mask1 |= 1 << i; + } + } + var block_data = this.pack_section("block_data"); + var block_meta = this.pack_section("block_meta"); + var light_block = this.pack_section("light_block"); + var light_sky = this.pack_section("light_sky"); + var mask2 = 0; + bufs.push(block_data, block_meta, light_block, light_sky); + return { + data: bufs.toBuffer(), + mask1: mask1, + mask2: mask2, + skylight: true + }; +} + +ChunkColumn.prototype.pack_section = function(section) { + var bufs = new Buffers(); + for (var i = 0; i < 16; i++) { + if (this.chunks[i] !== null) + bufs.push(this.chunks[i][section].pack()); + } + return bufs.toBuffer(); +} + +function World() { + this.columns = {}; +} + +World.prototype.unpack = function(packetData) { + var data = zlib.inflate(packetData.compressedChunkData); + packetData.meta.forEach(function (meta) { + var key = [meta.x, meta.z]; + var column; + if (key in this.columns) { + column = this.columns[key]; + } else { + column = new ChunkColumn(); + this.columns[key] = column; + } + + column.unpack(data, meta.bitMap, meta.addMap, packetData.skyLightSent); + }); +} + +World.prototype.packMapChunkBulk = function() { + var bufs = new Buffers(); + var metadatas = []; + var cb = arguments[arguments.length - 1]; + + // First pass, get all the metadatas and buffers. + for (var i = 0;i < arguments.length - 1; i++) { + var arg = arguments[i]; + var data = this.columns[arg]; + if (this.columns === null) { + continue; + } + + var packetContent = data.pack(); + bufs.push(packetContent.data); + var metadata = { + x: arg[0], + z: arg[1], + bitMap: packetContent.mask1, + addBitMap: packetContent.mask2 + } + metadatas.push(metadata); + } + + // Deflate/compress the buffers + var deflate = zlib.createDeflate(); + var deflateBuffer = new Buffer(0); + deflate.on('data', function(data) { + deflateBuffer = Buffer.concat([deflateBuffer, data]); + }); + deflate.on('end', function() { + cb(null, { + data: { + skyLightSent: true, + meta: metadatas, + compressedChunkData: deflateBuffer + }}); + }); + deflate.on('error', function(err) { + console.log("error"); + cb(err); + }); + deflate.write(bufs.toBuffer()); + deflate.end(); +} + +World.prototype.get = function(x, y, z, key) { + var rx = x % 16; + x = Math.floor(x / 16); + var ry = y % 16; + y = Math.floor(y / 16); + var rz = z % 16; + z = Math.floor(z / 16); + + if (!([x,z] in this.columns)) { + return 0; + } + + var column = this.columns[[x,z]]; + var chunk = column.chunks[y]; + + if (chunk == null) { + return 0; + } + + return chunk[key].get(rx, ry, rz); +} + +World.prototype.put = function(x, y, z, key, data) { + var rx = x % 16; + x = Math.floor(x / 16); + var ry = y % 16; + y = Math.floor(y / 16); + var rz = z % 16; + z = Math.floor(z / 16); + + var column; + if ([x,z] in this.columns) { + column = this.columns[[x,z]]; + } else { + column = new ChunkColumn(); + this.columns[[x,z]] = column; + } + + var chunk = column.chunks[y]; + if (chunk == null) { + chunk = new Chunk(); + column.chunks[y] = chunk; + } + + chunk[key].put(rx, ry, rz, data); +} + +World.prototype.get_biome = function(x, z) { + var rx = x % 16; + x = Math.floor(x / 16); + var rz = z % 16; + z = Math.floor(z / 16); + + if (!([x,z] in this.columns)) { + return 0; + } + + return this.columns[[x,z]].biome.get(rx, rz); +} + +World.prototype.put_biome = function(x, z, data) { + var rx = x % 16; + x = Math.floor(x / 16); + var rz = z % 16; + z = Math.floor(z / 16); + + var column; + if ([x,z] in this.columns) { + column = this.columns[[x,z]]; + } else { + column = new ChunkColumn(); + this.columns[[x,z]] = column; + } + + return column.biome.put(rx, rz, data); +} \ No newline at end of file