diff --git a/server/Converter.js b/server/Converter.js new file mode 100644 index 0000000..b440130 --- /dev/null +++ b/server/Converter.js @@ -0,0 +1,3 @@ +module.exports.toAbsoluteInt = function(float = 0.0) { + return Math.round(float * 32.0); +} \ No newline at end of file diff --git a/server/Entities/Entity.js b/server/Entities/Entity.js index b891f10..e5abdbe 100644 --- a/server/Entities/Entity.js +++ b/server/Entities/Entity.js @@ -1,6 +1,6 @@ class Entity { - constructor(x = 0, y = 0, z = 0, yaw = 0, pitch = 0) { - this.EID = global.fromIDPool(); + constructor(EID = 0, x = 0, y = 0, z = 0, yaw = 0, pitch = 0) { + this.EID = EID; this.x = x; this.y = y; diff --git a/server/Entities/EntityItem.js b/server/Entities/EntityItem.js index e1f537c..71eef72 100644 --- a/server/Entities/EntityItem.js +++ b/server/Entities/EntityItem.js @@ -2,7 +2,7 @@ const Entity = require("./Entity.js"); class EntityItem extends Entity { constructor(itemID = 0x00, x = 0, y = 0, z = 0) { - super(x, y, z); + super(global.fromIDPool(), x, y, z); this.motionX = (Math.random() * 0.2 - 0.1); this.motionY = 0.2; diff --git a/server/Entities/EntityLiving.js b/server/Entities/EntityLiving.js index 37eae1b..1f370a1 100644 --- a/server/Entities/EntityLiving.js +++ b/server/Entities/EntityLiving.js @@ -1,8 +1,8 @@ const Entity = require("./Entity.js"); -class EntityPlayer extends Entity { - constructor(x = 0, y = 0, z = 0) { - super(x, y, z); +class EntityLiving extends Entity { + constructor(EID = 0, x = 0, y = 0, z = 0) { + super(EID, x, y, z); } @@ -12,4 +12,4 @@ class EntityPlayer extends Entity { } } -module.exports = EntityPlayer; \ No newline at end of file +module.exports = EntityLiving; \ No newline at end of file diff --git a/server/Entities/EntityPlayer.js b/server/Entities/EntityPlayer.js index 1881a21..36fbbe3 100644 --- a/server/Entities/EntityPlayer.js +++ b/server/Entities/EntityPlayer.js @@ -1,12 +1,10 @@ const EntityLiving = require("./EntityLiving.js"); class EntityPlayer extends EntityLiving { - constructor(x = 0, y = 0, z = 0) { - super(x, y, z); + constructor(EID = 0, x = 0, y = 0, z = 0) { + super(EID, x, y, z); - this.motionX = (Math.random() * 0.2 - 0.1); - this.motionY = 0.2; - this.motionZ = (Math.random() * 0.2 - 0.1); + } } diff --git a/server/NamedPackets.js b/server/NamedPackets.js index cedc280..ac0b8ab 100644 --- a/server/NamedPackets.js +++ b/server/NamedPackets.js @@ -1,4 +1,5 @@ const namedPackets = { + "Disconnect": -1, "KeepAlive": 0x00, "LoginRequest": 0x01, "Handshake": 0x02, diff --git a/server/PacketMappingTable.js b/server/PacketMappingTable.js index 97be407..b3b2083 100644 --- a/server/PacketMappingTable.js +++ b/server/PacketMappingTable.js @@ -6,8 +6,11 @@ const Packet0KeepAlive = require("./Packets/Packet0KeepAlive.js"), Packet6SpawnPosition = require("./Packets/Packet6SpawnPosition.js"), Packet10Player = require("./Packets/Packet10Player.js"), Packet13PlayerPositionAndLook = require("./Packets/Packet13PlayerPositionAndLook.js"), + Packet18Animation = require("./Packets/Packet18Animation.js"), + Packet20NamedEntitySpawn = require("./Packets/Packet20NamedEntitySpawn.js"), Packet50PreChunk = require("./Packets/Packet50PreChunk.js"), - Packet53BlockChange = require("./Packets/Packet53BlockChange.js"); + Packet53BlockChange = require("./Packets/Packet53BlockChange.js"), + Packet103SetSlot = require("./Packets/Packet103SetSlot.js"); const mappingTable = { 0x00: Packet0KeepAlive, @@ -18,8 +21,11 @@ const mappingTable = { 0x06: Packet6SpawnPosition, 0x0A: Packet10Player, 0x0D: Packet13PlayerPositionAndLook, + 0x12: Packet18Animation, + 0x14: Packet20NamedEntitySpawn, 0x32: Packet50PreChunk, 0x35: Packet53BlockChange, + 0x67: Packet103SetSlot }; module.exports = mappingTable; \ No newline at end of file diff --git a/server/Packets/Packet103SetSlot.js b/server/Packets/Packet103SetSlot.js new file mode 100644 index 0000000..3ba268d --- /dev/null +++ b/server/Packets/Packet103SetSlot.js @@ -0,0 +1,27 @@ +const Packet = require("./Packet.js"); + +class Packet103SetSlot extends Packet { + constructor(window_id = 0, slot = 0, item_id = -1, item_count = 0, item_uses = 0) { + super(0x67); + + this.window_id = window_id; + this.slot = slot; + this.item_id = item_id; + this.item_count = item_count; + this.item_uses = item_uses; + } + + writePacket() { + super.writePacket(); + + this.writer.writeByte(this.window_id); + this.writer.writeShort(this.slot); + this.writer.writeShort(this.item_id); + this.writer.writeByte(this.item_count); + if (this.item_id != -1) this.writer.writeShort(this.item_uses); + + return this.toBuffer(); + } +} + +module.exports = Packet103SetSlot; \ No newline at end of file diff --git a/server/Packets/Packet18Animation.js b/server/Packets/Packet18Animation.js new file mode 100644 index 0000000..f8e75f9 --- /dev/null +++ b/server/Packets/Packet18Animation.js @@ -0,0 +1,21 @@ +const Packet = require("./Packet.js"); + +class Packet18Animation extends Packet { + constructor(EID = 0, animation = 0) { + super(0x12); + + this.EID = EID; + this.animation = animation; + } + + writePacket() { + super.writePacket(); + + this.writer.writeInt(this.EID); + this.writer.writeByte(this.animation); + + return this.toBuffer(); + } +} + +module.exports = Packet18Animation; \ No newline at end of file diff --git a/server/Packets/Packet20NamedEntitySpawn.js b/server/Packets/Packet20NamedEntitySpawn.js new file mode 100644 index 0000000..963d9ee --- /dev/null +++ b/server/Packets/Packet20NamedEntitySpawn.js @@ -0,0 +1,34 @@ +const Packet = require("./Packet.js"); +const Converter = require("../Converter.js"); + +class Packet20NamedEntitySpawn extends Packet { + constructor(EID = 0, entityName = "", x = 0.0, y = 0.0, z = 0.0, yaw = 0.0, pitch = 0.0, currentItem = 0) { + super(0x14); + + this.EID = EID; + this.entityName = entityName; + this.absX = Converter.toAbsoluteInt(x); + this.absY = Converter.toAbsoluteInt(y); + this.absZ = Converter.toAbsoluteInt(z); + this.packedYaw = 0; // TODO: Add rotation. + this.packedPitch = 0; + this.currentItem = currentItem; + } + + writePacket() { + super.writePacket(); + + this.writer.writeInt(this.EID); + this.writer.writeString(this.entityName); + this.writer.writeInt(this.absX); + this.writer.writeInt(this.absY); + this.writer.writeInt(this.absZ); + this.writer.writeByte(this.packedYaw); + this.writer.writeByte(this.packedPitch); + this.writer.writeShort(this.currentItem); + + return this.toBuffer(); + } +} + +module.exports = Packet20NamedEntitySpawn; \ No newline at end of file diff --git a/server/Packets/Packet2Handshake.js b/server/Packets/Packet2Handshake.js index 6d069b3..936aaff 100644 --- a/server/Packets/Packet2Handshake.js +++ b/server/Packets/Packet2Handshake.js @@ -7,7 +7,7 @@ class Packet2Handshake extends Packet { this.username = username; } - writePacket(EID = 0) { + writePacket() { super.writePacket(); this.writer.writeString("-"); // "-" == Offline mode diff --git a/server/Workers/ChunkPacketGenerator.js b/server/Workers/ChunkPacketGenerator.js deleted file mode 100644 index 7328a08..0000000 --- a/server/Workers/ChunkPacketGenerator.js +++ /dev/null @@ -1,95 +0,0 @@ -const { Worker, MessageChannel, MessagePort, isMainThread, parentPort } = require('worker_threads'); - -const bufferStuff = require("../bufferStuff.js"); - -let chunkY = 0; -let busyInterval = null; - -parentPort.on("message", (data) => { - // This stops the thread from stopping :) - if (busyInterval == null) busyInterval = setInterval(() => {}, 86400000); - - switch (data[0]) { - case "chunk": - chunkY = 0; - - parentPort.postMessage([data[0], doSquareChunk(data[1]), data[2]]); - chunkY += 16; - parentPort.postMessage([data[0], doSquareChunk(data[1]), data[2]]); - chunkY += 16; - parentPort.postMessage([data[0], doSquareChunk(data[1]), data[2]]); - chunkY += 16; - parentPort.postMessage([data[0], doSquareChunk(data[1]), data[2]]); - chunkY += 16; - parentPort.postMessage([data[0], doSquareChunk(data[1]), data[2]]); - chunkY += 16; - parentPort.postMessage([data[0], doSquareChunk(data[1]), data[2]]); - chunkY += 16; - parentPort.postMessage([data[0], doSquareChunk(data[1]), data[2]]); - chunkY += 16; - parentPort.postMessage([data[0], doSquareChunk(data[1]), data[2]]); - - parentPort.postMessage(["remove", data[3]]); - break; - - case "generate": - parentPort.postMessage([data[0], generateChunk(), data[1], data[2], data[3]]); - break; - } -}); - -function generateChunk() { - let chunk = {}; - for (let y = 0; y < 128; y++) { - chunk[y] = {}; - for (let x = 0; x < 16; x++) { - chunk[y][x] = {}; - for (let z = 0; z < 16; z++) { - if (y == 64) { - chunk[y][x][z] = 2; - } - else if (y == 63 || y == 62) chunk[y][x][z] = 3; - else if (y == 0) chunk[y][x][z] = 7; - else if (y < 62) chunk[y][x][z] = 1; - else chunk[y][x][z] = 0; - } - } - } - - return chunk; -} - -function doSquareChunk(chunk) { - let blocksToSend = []; - for (let y = 0; y < 16; y++) { - for (let x = 0; x < 16; x++) { - for (let z = 0; z < 16; z++) { - if (chunk[2][chunkY + y][x][z] == 0) continue; // don't send air lol - blocksToSend.push([chunk[2][chunkY + y][x][z], x & 0xf, z & 0xf, chunkY + y]); - } - } - } - - // I couldn't figure out how to construct a chunk lmao - // Using multi block chunks for now - // TODO: yknow, figure out how to actually chunk. - const writer = new bufferStuff.Writer(); - writer.writeByte(0x34); - writer.writeInt(chunk[0]); - writer.writeInt(chunk[1]); - writer.writeShort(blocksToSend.length); - // Block coords - for (let blocks of blocksToSend) { - writer.writeShort((blocks[1] << 12 | blocks[2] << 8 | blocks[3]) - 32768); - } - // Block types - for (let blocks of blocksToSend) { - writer.writeByte(blocks[0]); - } - // Block metadata - for (let blocks of blocksToSend) { - writer.writeByte(0); - } - - return writer.buffer; -} \ No newline at end of file diff --git a/server/Workers/ChunkWorker.js b/server/Workers/ChunkWorker.js new file mode 100644 index 0000000..8e774eb --- /dev/null +++ b/server/Workers/ChunkWorker.js @@ -0,0 +1,86 @@ +const { parentPort } = require('worker_threads'); + +const { deflateSync } = require("zlib"); + +const bufferStuff = require("../bufferStuff.js"); + +let busyInterval = null; + +parentPort.on("message", (data) => { + // This stops the thread from stopping :) + if (busyInterval == null) busyInterval = setInterval(() => {}, 86400000); // Runs once a day + + switch (data[0]) { + case "chunk": + parentPort.postMessage([data[0], doChunk(data[1]), data[2]]); + break; + + case "generate": + parentPort.postMessage([data[0], generateChunk(), data[1], data[2], data[3]]); + break; + } +}); + +function generateChunk() { + let chunk = {}; + for (let y = 0; y < 128; y++) { + chunk[y] = {}; + for (let x = 0; x < 16; x++) { + chunk[y][x] = {}; + for (let z = 0; z < 16; z++) { + if (y == 64) { + chunk[y][x][z] = 2; + } + else if (y == 63 || y == 62) chunk[y][x][z] = 3; + else if (y == 0) chunk[y][x][z] = 7; + else if (y < 62) chunk[y][x][z] = 1; + else chunk[y][x][z] = 0; + } + } + } + + return chunk; +} + +function doChunk(chunk) { + const writer = new bufferStuff.Writer(); + + writer.writeByte(0x33); // Chunk + writer.writeInt(chunk[0] << 4); // Chunk X + writer.writeShort(0 << 7); // Chunk Y + writer.writeInt(chunk[1] << 4); // Chunk Z + writer.writeByte(15); // Size X + writer.writeByte(127); // Size Y + writer.writeByte(15); // Size Z + + const blocks = new bufferStuff.Writer(32768); + const metadata = new bufferStuff.Writer(32768); + const lighting = new bufferStuff.Writer(32768); + + let blockMeta = false; + for (let x = 0; x < 16; x++) { + for (let z = 0; z < 16; z++) { + for (let y = 0; y < 128; y++) { + blocks.writeByte(chunk[2][y][x][z]); // The block + if (blockMeta) { + metadata.writeByte(0x00); // TODO: Metadata + // Light level 15 for 2 blocks (1111 1111) + lighting.writeUByte(0xFF); // TODO: Lighting (Client seems to do it's own so it's not top priority) + } + // Hack for nibble stuff + blockMeta = !blockMeta; + } + } + } + // These are hacks for the nibbles + blocks.writeBuffer(metadata.buffer); + blocks.writeBuffer(lighting.buffer); // Block lighting + //blocks.writeBuffer(lighting.buffer); // Sky lighting (Looks like this isn't needed???) + + // We're on another thread we don't care if we halt + const deflated = deflateSync(blocks.buffer); + writer.writeInt(deflated.length); // Compressed Size + writer.writeBuffer(deflated); // Compressed chunk data + + return writer.buffer; +} \ No newline at end of file diff --git a/server/bufferStuff.js b/server/bufferStuff.js index 1c9879d..e5272c5 100644 --- a/server/bufferStuff.js +++ b/server/bufferStuff.js @@ -6,8 +6,10 @@ */ module.exports.Writer = class { - constructor() { - this.buffer = Buffer.alloc(0); + constructor(size = 0) { + this.buffer = Buffer.alloc(size); + this.offset = 0; + this.baseSize = size; } reset() { @@ -23,10 +25,27 @@ module.exports.Writer = class { } writeByte(data = 0) { - const buff = Buffer.alloc(1); - buff.writeInt8(data, 0); + if (this.baseSize == 0) { + const buff = Buffer.alloc(1); + buff.writeInt8(data, 0); + + this.writeBuffer(buff); + } else { + this.buffer.writeInt8(data, this.offset); + this.offset += 1; + } + } - this.writeBuffer(buff); + writeUByte(data = 0) { + if (this.baseSize == 0) { + const buff = Buffer.alloc(1); + buff.writeUInt8(data, 0); + + this.writeBuffer(buff); + } else { + this.buffer.writeUInt8(data, this.offset); + this.offset += 1; + } } writeByteArray(data = [0]) { diff --git a/server/chunkManager.js b/server/chunkManager.js index 970c8a1..f4de9f1 100644 --- a/server/chunkManager.js +++ b/server/chunkManager.js @@ -1,11 +1,8 @@ +const { Worker } = require('worker_threads'); const FunkyArray = require("./Util/funkyArray.js"); -const bufferStuff = require("./bufferStuff.js"); - const config = require("../config.json"); -const { Worker } = require('worker_threads'); - -const workerPath = __dirname + "/Workers/ChunkPacketGenerator.js"; +const workerPath = `${__dirname}/Workers/ChunkWorker.js`; module.exports = class { constructor() { @@ -17,7 +14,6 @@ module.exports = class { this.threadPool = []; this.workPool = new FunkyArray(); - this.toRemove = []; // WoAh!!! Thread pool in js!?!??!???!11!?!?! @@ -30,10 +26,6 @@ module.exports = class { case "chunk": const user = global.getUserByKey(data[2]); user.chunksToSend.add(Buffer.from(data[1])); - break; - - // Specific to the chunk task as multiple of them are sent before removal - case "remove": this.toRemove.push(data[1]); this.threadPool[myID][0] = false; break; @@ -102,10 +94,10 @@ module.exports = class { } setBlock(id = 0, x = 0, y = 0, z = 0) { - if (y < 0 || y > 127) throw "Tried to set a block outside of the world!"; + if (y < 0 || y > 127) return console.error("Tried to set a block outside of the world!"); - const chunkX = Math.floor(x / 16); - const chunkZ = Math.floor(z / 16); + const chunkX = Math.floor(x >> 4); + const chunkZ = Math.floor(z >> 4); const blockX = x - (16 * chunkX); const blockZ = z - (16 * chunkZ); diff --git a/server/server.js b/server/server.js index e82babb..5203084 100644 --- a/server/server.js +++ b/server/server.js @@ -1,8 +1,10 @@ const bufferStuff = require("./bufferStuff.js"); const ChunkManager = require("./chunkManager.js"); const User = require("./user.js"); +const EntityPlayer = require("./Entities/EntityPlayer.js"); const PacketMappingTable = require("./PacketMappingTable.js"); const NamedPackets = require("./NamedPackets.js"); +const Converter = require("./Converter.js"); const Socket = require("net").Socket; const uuid = require("uuid").v4; @@ -23,6 +25,7 @@ global.getUserByKey = function(key) { function addUser(socket) { let user = new User(global.fromIDPool(), socket); + user.entityRef = new EntityPlayer(user.id, 8.5, 65.5, 8.5); netUsers[user.id] = user; netUserKeys = Object.keys(netUsers); @@ -59,12 +62,14 @@ module.exports.init = function(config) { } } // Do chunk updates + // Don't update if chunk is generating if (!global.generatingChunks) { let itemsToRemove = []; // Do a max of 128 chunk updates per tick for (let i = 0; i < Math.min(global.chunkManager.queuedBlockUpdates.getLength(), 128); i++) { const chunkUpdateKey = global.chunkManager.queuedBlockUpdates.itemKeys[i]; const chunkUpdate = global.chunkManager.queuedBlockUpdates.items[chunkUpdateKey]; + // Don't update if chunk is nonexistant if (global.chunkManager.chunks[chunkUpdate[1]] == null) continue; if (global.chunkManager.chunks[chunkUpdate[1]][chunkUpdate[2]] == null) continue; itemsToRemove.push(chunkUpdateKey); @@ -73,11 +78,13 @@ module.exports.init = function(config) { global.chunkManager.chunks[chunkUpdate[1]][chunkUpdate[2]][chunkUpdate[3]][chunkUpdate[4]][chunkUpdate[5]] = chunkUpdate[0]; const packet = new PacketMappingTable[NamedPackets.BlockChange](chunkUpdate[4] + (16 * chunkUpdate[1]), chunkUpdate[3], chunkUpdate[5] + (16 * chunkUpdate[2]), chunkUpdate[0]).writePacket(); - for (let userKey in netUserKeys) { + for (let userKey of netUserKeys) { const user = netUsers[userKey]; if (user.loginFinished) user.socket.write(packet); } - } catch (e) {} + } catch (e) { + console.error(e); + } } for (let item of itemsToRemove) { @@ -89,7 +96,7 @@ module.exports.init = function(config) { for (let key of netUserKeys) { const user = netUsers[key]; - + //user.entityRef.onTick(); } // Send queued chunks to users @@ -126,8 +133,12 @@ module.exports.connection = async function(socket = new Socket) { const packetID = reader.readByte(); switch(packetID) { + case NamedPackets.Disconnect: + removeUser(thisUser.id); + break; + case NamedPackets.KeepAlive: - socket.write(new PacketMappingTable[NamedPackets.KeepAlive]().writePacket()); + break; case NamedPackets.LoginRequest: @@ -154,15 +165,21 @@ module.exports.connection = async function(socket = new Socket) { netUsers[key].socket.write(joinMessage); } + socket.write(new PacketMappingTable[NamedPackets.SetSlot](0, 36, 3, 64, 0).writePacket()); + socket.write(new PacketMappingTable[NamedPackets.PlayerPositionAndLook](8.5, 65 + 1.6200000047683716, 65, 8.5, 0, 0, false).writePacket()); thisUser.loginFinished = true; + // Send chunks for (let x = -3; x < 4; x++) { for (let z = -3; z < 4; z++) { global.chunkManager.multiBlockChunk(x, z, thisUser); } } + + // Send this user to other online users + break; case NamedPackets.Handshake: @@ -211,6 +228,44 @@ module.exports.connection = async function(socket = new Socket) { } break; + case NamedPackets.Animation: + const EID = reader.readInt(); + const cachedPacket = new PacketMappingTable[NamedPackets.Animation](thisUser.id, reader.readByte()).writePacket(); + for (let key of netUserKeys) { + if (netUsers[key].id !== thisUser.id) netUsers[key].socket.write(cachedPacket); + } + break; + + case NamedPackets.PlayerDigging: + const status = reader.readByte(); + + if (status == 2) { + const x = reader.readInt(); + const y = reader.readByte(); + const z = reader.readInt(); + + global.chunkManager.setBlock(0, x, y, z); + } + break; + + case NamedPackets.PlayerBlockPlacement: + const x = reader.readInt(); + const y = reader.readByte(); + const z = reader.readInt(); + let xOff = 0, yOff = 0, zOff = 0; + switch (reader.readByte()) { // direction + case 0: yOff = -1; break; + case 1: yOff = 1; break; + case 2: zOff = -1; break; + case 3: zOff = 1; break; + case 4: xOff = -1; break; + case 5: xOff = 1; break; + } + const block = reader.readShort(); + + global.chunkManager.setBlock(block, x + xOff, y + yOff, z + zOff); + break; + case NamedPackets.Player: break;