This commit is contained in:
Holly Stubbs 2021-08-12 04:58:56 +01:00
parent c50328818f
commit 1c775cfd60
21 changed files with 690 additions and 17 deletions

41
package-lock.json generated Normal file
View File

@ -0,0 +1,41 @@
{
"name": "mc-beta-server",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"net": "^1.0.2",
"uuid": "^8.3.2"
}
},
"node_modules/net": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz",
"integrity": "sha1-0XV+yaf7I3HYPPR1XOPifhCCk4g="
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
}
},
"dependencies": {
"net": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz",
"integrity": "sha1-0XV+yaf7I3HYPPR1XOPifhCCk4g="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
}
}

22
package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "mc-beta-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {},
"repository": {
"type": "git",
"url": "git+https://github.com/tgpethan/mc-beta-server.git"
},
"keywords": [],
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/tgpethan/mc-beta-server/issues"
},
"homepage": "https://github.com/tgpethan/mc-beta-server#readme",
"dependencies": {
"net": "^1.0.2",
"uuid": "^8.3.2"
}
}

61
server/NamedPackets.js Normal file
View File

@ -0,0 +1,61 @@
const namedPackets = {
"KeepAlive": 0x00,
"LoginRequest": 0x01,
"Handshake": 0x02,
"ChatMessage": 0x03,
"TimeUpdate": 0x04,
"EntityEquipment": 0x05,
"SpawnPosition": 0x06,
"UseEntity": 0x07,
"UpdateHealth": 0x08,
"Respawn": 0x09,
"Player": 0x0A,
"PlayerPosition": 0x0B,
"PlayerLook": 0x0C,
"PlayerPositionAndLook": 0x0D,
"PlayerDigging": 0x0E,
"PlayerBlockPlacement": 0x0F,
"HoldingChange": 0x10,
"UseBed": 0x11,
"Animation": 0x12,
"EntityAction": 0x13,
"NamedEntitySpawn": 0x14,
"PickupSpawn": 0x15,
"CollectItem": 0x16,
"AddObjectOrVehicle": 0x17,
"MobSpawn": 0x18,
"EntityPainting": 0x19,
"StanceUpdate": 0x1B,
"EntityVelocity": 0x1C,
"DestroyEntity": 0x1D,
"Entity": 0x1E,
"EntityRelativeMove": 0x1F,
"EntityLook": 0x20,
"EntityLookAndRelativeMove": 0x21,
"EntityTeleport": 0x22,
"EntityStatus": 0x26,
"AttachEntity": 0x27,
"EntityMetadata": 0x28,
"PreChunk": 0x32,
"MapChunk": 0x33,
"MultiBlockChange": 0x34,
"BlockChange": 0x35,
"BlockAction": 0x36,
"Explosion": 0x3C,
"SoundEffect": 0x3D,
"NewOrInvalidState": 0x46,
"Thunderbolt": 0x47,
"OpenWindow": 0x64,
"CloseWindow": 0x65,
"WindowClick": 0x66,
"SetSlot": 0x67,
"WindowItems": 0x68,
"UpdateProgressBar": 0x69,
"Transaction": 0x6A,
"UpdateSign": 0x82,
"ItemData": 0x83,
"IncrementStatistic": 0xC8,
"DisconnectOrKick": 0xFF
};
module.exports = namedPackets;

View File

@ -0,0 +1,23 @@
const Packet0KeepAlive = require("./Packets/Packet0KeepAlive.js"),
Packet1LoginRequest = require("./Packets/Packet1LoginRequest.js"),
Packet2Handshake = require("./Packets/Packet2Handshake.js"),
Packet3Chat = require("./Packets/Packet3Chat.js"),
Packet6SpawnPosition = require("./Packets/Packet6SpawnPosition.js"),
Packet10Player = require("./Packets/Packet10Player.js"),
Packet13PlayerPositionAndLook = require("./Packets/Packet13PlayerPositionAndLook.js"),
Packet50PreChunk = require("./Packets/Packet50PreChunk.js"),
Packet53BlockChange = require("./Packets/Packet53BlockChange.js");
const mappingTable = {
0x00: Packet0KeepAlive,
0x01: Packet1LoginRequest,
0x02: Packet2Handshake,
0x03: Packet3Chat,
0x06: Packet6SpawnPosition,
0x0A: Packet10Player,
0x0D: Packet13PlayerPositionAndLook,
0x32: Packet50PreChunk,
0x35: Packet53BlockChange,
};
module.exports = mappingTable;

View File

@ -1,13 +1,21 @@
const bufferStuff = require("../bufferStuff.js");
module.exports = class {
constructor() {
this.id = 0;
}
readPacket() {
constructor(packetID = 0x00) {
this.id = packetID;
this.writer = null;
}
writePacket() {
this.writer = new bufferStuff.Writer();
this.writer.writeByte(this.id);
return this.writer;
}
toBuffer() {
return this.writer == null ? Buffer.alloc(0) : this.writer.buffer;
}
}

View File

@ -0,0 +1,11 @@
const Packet = require("./Packet.js");
class Packet0KeepAlive extends Packet {
writePacket() {
super.writePacket();
return this.toBuffer();
}
}
module.exports = Packet0KeepAlive;

View File

@ -0,0 +1,19 @@
const Packet = require("./Packet.js");
class Packet10Player extends Packet {
constructor(onGround = true) {
super(0x0A);
this.onGround = onGround;
}
writePacket() {
super.writePacket();
this.writer.writeBool(this.onGround);
return this.toBuffer();
}
}
module.exports = Packet10Player;

View File

@ -0,0 +1,31 @@
const Packet = require("./Packet.js");
class Packet13PlayerPositionAndLook extends Packet {
constructor(x = 0, y = 65, stance = 67, z = 0, yaw = 0.0, pitch = 0.0, onGround = true) {
super(0x0D);
this.x = x;
this.y = y;
this.stance = stance;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.onGround = onGround;
}
writePacket() {
super.writePacket();
this.writer.writeDouble(this.x);
this.writer.writeDouble(this.y);
this.writer.writeDouble(this.stance);
this.writer.writeDouble(this.z);
this.writer.writeFloat(this.yaw);
this.writer.writeFloat(this.pitch);
this.writer.writeBool(this.onGround);
return this.toBuffer();
}
}
module.exports = Packet13PlayerPositionAndLook;

View File

@ -0,0 +1,29 @@
const Packet = require("./Packet.js");
class Packet1LoginRequest extends Packet {
constructor(protocol_version = 0, username = "", map_seed = BigInt(0), dimension = 0) {
super(0x01);
this.protocol_version = protocol_version;
this.username = username;
this.map_seed = map_seed;
this.dimension = dimension;
}
readPacket() {
}
writePacket(EID = 0) {
super.writePacket();
this.writer.writeInt(EID);
this.writer.writeString("");
this.writer.writeLong(971768181197178410);
this.writer.writeByte(0);
return this.toBuffer();
}
}
module.exports = Packet1LoginRequest;

View File

@ -0,0 +1,19 @@
const Packet = require("./Packet.js");
class Packet2Handshake extends Packet {
constructor(username = "") {
super(0x02);
this.username = username;
}
writePacket(EID = 0) {
super.writePacket();
this.writer.writeString("-"); // "-" == Offline mode
return this.toBuffer();
}
}
module.exports = Packet2Handshake;

View File

@ -0,0 +1,19 @@
const Packet = require("./Packet.js");
class Packet3Chat extends Packet {
constructor(message = "") {
super(0x03);
this.message = message;
}
writePacket() {
super.writePacket();
this.writer.writeString(this.message);
return this.toBuffer();
}
}
module.exports = Packet3Chat;

View File

@ -0,0 +1,23 @@
const Packet = require("./Packet.js");
class Packet50PreChunk extends Packet {
constructor(x = 0, z = 0, mode = true) {
super(0x32);
this.x = x;
this.z = z;
this.mode = mode;
}
writePacket() {
super.writePacket();
this.writer.writeInt(this.x);
this.writer.writeInt(this.z);
this.writer.writeBool(this.mode);
return this.toBuffer();
}
}
module.exports = Packet50PreChunk;

View File

@ -0,0 +1,27 @@
const Packet = require("./Packet.js");
class Packet53BlockChange extends Packet {
constructor(x = 0, y = 0, z = 0, block_type = 0, block_metadata = 0) {
super(0x35);
this.x = x;
this.y = y;
this.z = z;
this.block_type = block_type;
this.block_metadata = block_metadata;
}
writePacket() {
super.writePacket();
this.writer.writeInt(this.x);
this.writer.writeByte(this.y);
this.writer.writeInt(this.z);
this.writer.writeByte(this.block_type);
this.writer.writeByte(this.block_metadata);
return this.toBuffer();
}
}
module.exports = Packet53BlockChange;

View File

@ -0,0 +1,23 @@
const Packet = require("./Packet.js");
class Packet6SpawnPosition extends Packet {
constructor(x = 8.5, y = 65.5, z = 8.5) {
super(0x06);
this.x = x;
this.y = y;
this.z = z;
}
writePacket() {
super.writePacket();
this.writer.writeInt(this.x);
this.writer.writeInt(this.y);
this.writer.writeInt(this.z);
return this.toBuffer();
}
}
module.exports = Packet6SpawnPosition;

80
server/Util/funkyArray.js Normal file
View File

@ -0,0 +1,80 @@
const pRandom = require("./prettyRandom.js");
module.exports = class {
constructor(indexingMode = false) {
this.items = {};
this.itemKeys = Object.keys(this.items);
this.indexingMode = indexingMode;
this.index = 0;
this.iterableArray = [];
}
add(item, regenerate = true) {
let id;
if (this.indexingMode) {
this.items[id = this.index] = item;
} else {
this.items[id = pRandom()] = item;
}
if (regenerate) this.regenerateIterableArray();
this.itemKeys = Object.keys(this.items);
this.index++;
return this.items[id];
}
remove(id, regenerate = true) {
delete this.items[id];
if (regenerate) this.regenerateIterableArray();
this.itemKeys = Object.keys(this.items);
}
removeFirstItem(regenerate = true) {
delete this.items[this.itemKeys[0]];
if (regenerate) this.regenerateIterableArray();
this.itemKeys = Object.keys(this.items);
}
regenerateIterableArray() {
this.iterableArray = new Array();
for (let itemKey of this.itemKeys) {
this.iterableArray.push(this.items[itemKey]);
}
}
getFirstItem() {
return this.items[this.itemKeys[0]];
}
getLength() {
return this.itemKeys.length;
}
getKeyById(id) {
return this.itemKeys[id];
}
getById(id) {
return this.items[this.itemKeys[id]];
}
getByKey(key) {
return this.items[key];
}
getKeys() {
return this.itemKeys;
}
getItems() {
return this.items;
}
getIterableItems() {
return this.iterableArray;
}
}

View File

@ -0,0 +1,21 @@
let lastRandom = -1;
function pRandom(from = 0, to = 2147483647) {
let thisRandom = Math.floor(map(Math.random(), 0, 1, from, to));
if (thisRandom == lastRandom) thisRandom = pRandom(from, to);
return thisRandom;
}
function map(input, inputMin, inputMax, outputMin, outputMax) {
const newv = (input - inputMin) / (inputMax - inputMin) * (outputMax - outputMin) + outputMin;
if (outputMin < outputMax) return constrain(newv, outputMin, outputMax);
else return constrain(newv, outputMax, outputMin);
}
function constrain(input, low, high) {
return Math.max(Math.min(input, high), low);
}
module.exports = pRandom;

View File

View File

@ -22,6 +22,16 @@ module.exports.Writer = class {
this.writeBuffer(buff);
}
writeByteArray(data = [0]) {
const buff = Buffer.alloc(data.length);
for (let byte of data) {
buff.writeInt8(byte);
}
this.writeBuffer(buff);
}
writeShort(data = 0) {
const buff = Buffer.alloc(2);
buff.writeIntBE(data, 0, 2);
@ -29,6 +39,18 @@ module.exports.Writer = class {
this.writeBuffer(buff);
}
writeShortArray(data = [0]) {
const buff = Buffer.alloc(data.length * 2);
let offset = 0;
for (let short of data) {
buff.writeIntBE(short, offset, 2);
offset += 2;
}
this.writeBuffer(buff);
}
writeInt(data = 0) {
const buff = Buffer.alloc(4);
buff.writeIntBE(data, 0, 4);

View File

@ -1,25 +1,110 @@
const FunkyArray = require("./Util/funkyArray.js");
const bufferStuff = require("./bufferStuff.js");
module.exports = class {
constructor() {
this.chunks = {};
this.createChunk(0, 0);
this.queuedBlockUpdates = new FunkyArray();
for (let x = -3; x < 4; x++) {
for (let z = -3; z < 4; z++) {
this.createChunk(x, z);
}
}
}
createChunk(x = 0, z = 0) {
this.chunks[x] = {};
this.chunks[x][z] = {};
// TODO: Store metadata!
createChunk(cx = 0, cz = 0) {
if (this.chunks[cx] == null) this.chunks[cx] = {};
this.chunks[cx][cz] = {};
let chunkQueuedBlocks = [];
const chunk = this.chunks[x][z];
for (let y = 0; y < 128; y++) {
chunk[y] = {};
this.chunks[cx][cz][y] = {};
for (let x = 0; x < 16; x++) {
chunk[y][x] = {};
this.chunks[cx][cz][y][x] = [];
for (let z = 0; z < 16; z++) {
chunk[y][x][z] = 0;
if (y == 64) {
this.chunks[cx][cz][y][x].push(2);
// Make a tree :)
if (Math.random() <= 0.01) {
const newX = x + (16 * cx), newZ = z + (16 * cz);
// trunk
this.setBlock(17, newX, y + 1, newZ);
this.setBlock(17, newX, y + 2, newZ);
this.setBlock(17, newX, y + 3, newZ);
this.setBlock(17, newX, y + 4, newZ);
// leaves
this.setBlock(18, newX + 2, y + 3, newZ + 2);
this.setBlock(18, newX + 1, y + 3, newZ + 2);
this.setBlock(18, newX, y + 3, newZ + 2);
this.setBlock(18, newX - 1, y + 3, newZ + 2);
this.setBlock(18, newX - 2, y + 3, newZ + 2);
}
}
else if (y == 63 || y == 62) this.chunks[cx][cz][y][x].push(3);
else if (y == 0) this.chunks[cx][cz][y][x].push(7);
else if (y < 62) this.chunks[cx][cz][y][x].push(1);
else this.chunks[cx][cz][y][x].push(0);
}
}
}
}
console.log(chunk);
multiBlockChunk(chunkX = 0, chunkZ = 0, user) {
const writer = new bufferStuff.Writer();
// I couldn't figure out how to construct a chunk lmao
// __ima just send each block individually__
// Using multi block chunks now!
// TODO: yknow, figure out how to chunk.
let blocksToSend = [];
for (let y = 0; y < 128; y++) {
blocksToSend = [];
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
if (this.chunks[chunkX][chunkZ][y][x][z] == 0) continue; // don't send air lol
blocksToSend.push([this.chunks[chunkX][chunkZ][y][x][z], x & 0xf, z & 0xf]);
}
}
if (blocksToSend.length > 0) {
writer.reset();
writer.writeByte(0x34);
writer.writeInt(chunkX);
writer.writeInt(chunkZ);
writer.writeShort(blocksToSend.length);
// Block coords
for (let blocks of blocksToSend) {
writer.writeShort((blocks[1] << 12 | blocks[2] << 8 | y) - 32768);
}
// Block types
for (let blocks of blocksToSend) {
writer.writeByte(blocks[0]);
}
// Block metadata
for (let blocks of blocksToSend) {
writer.writeByte(0);
}
user.chunksToSend.add(writer.buffer) // so we don't flood the client queue these
}
}
}
setBlock(id = 0, x = 0, y = 0, z = 0) {
if (y < 0 || y > 127) throw "Tried to set a block outside of the world!";
const chunkX = Math.floor(x / 16);
const chunkZ = Math.floor(z / 16);
const blockX = x - (16 * chunkX);
const blockZ = z - (16 * chunkZ);
// Don't queue a block update if that block is already this block
//if (this.chunks[chunkX][chunkZ][y][blockX][blockZ] == id) return;
this.queuedBlockUpdates.add([id, chunkX, chunkZ, y, blockX, blockZ]);
}
}

View File

@ -1,14 +1,107 @@
const bufferStuff = require("./bufferStuff.js")
const bufferStuff = require("./bufferStuff.js");
const ChunkManager = require("./chunkManager.js");
const User = require("./user.js");
const PacketMappingTable = require("./PacketMappingTable.js");
const NamedPackets = require("./NamedPackets.js");
const Socket = require("net").Socket;
const uuid = require("uuid").v4;
let idPool = 1;
global.fromIDPool = function() {
const oldVal = idPool;
idPool++;
return oldVal;
}
let netUsers = {},
netUserKeys = Object.keys(netUsers);
function addUser(socket) {
let user = new User(global.fromIDPool(), socket);
netUsers[user.id] = user;
netUserKeys = Object.keys(netUsers);
return user;
}
function removeUser() {
}
let config = {};
let entities = {};
global.chunkManager = new ChunkManager();
let tickInterval, tickCounter = BigInt(0);
let tickRate = BigInt(20);
module.exports.init = function(config) {
config = config;
console.log(`Up! Running at 0.0.0.0:${config.port}`);
tickInterval = setInterval(() => {
for (let key of netUserKeys) {
const user = netUsers[netUserKeys];
let itemsToRemove = [];
for (let i = 0; i < Math.min(user.chunksToSend.getLength(), 128); i++) {
const chunkKey = user.chunksToSend.itemKeys[i];
itemsToRemove.push(chunkKey);
user.socket.write(user.chunksToSend.items[chunkKey]);
}
for (let item of itemsToRemove) {
user.chunksToSend.remove(item, false);
}
user.chunksToSend.regenerateIterableArray();
}
tickCounter++;
}, 1000 / parseInt(tickRate.toString()));
}
module.exports.connection = function(socket = new Socket) {
const thisUser = addUser(socket);
socket.on('data', function(chunk) {
console.log(chunk);
const reader = new bufferStuff.Reader(chunk);
switch(reader.readByte()) {
case NamedPackets.KeepAlive:
socket.write(new PacketMappingTable[NamedPackets.KeepAlive]().writePacket());
break;
case NamedPackets.LoginRequest:
socket.write(new PacketMappingTable[NamedPackets.LoginRequest](reader.readInt(), reader.readString(), reader.readLong(), reader.readByte()).writePacket(thisUser.id));
socket.write(new PacketMappingTable[NamedPackets.SpawnPosition]().writePacket());
for (let x = -3; x < 4; x++) {
for (let z = -3; z < 4; z++) {
socket.write(new PacketMappingTable[NamedPackets.PreChunk](x, z, true).writePacket());
global.chunkManager.multiBlockChunk(x, z, thisUser);
}
}
socket.write(new PacketMappingTable[NamedPackets.BlockChange](8, 64, 8, 20, 0).writePacket());
socket.write(new PacketMappingTable[NamedPackets.Player](true).writePacket());
socket.write(new PacketMappingTable[NamedPackets.ChatMessage](`\u00A7e${thisUser.username} has joined the game`).writePacket());
socket.write(new PacketMappingTable[NamedPackets.PlayerPositionAndLook](8.5, 65 + 1.6200000047683716, 65, 8.5, 0, 0, false).writePacket());
thisUser.loginFinished = true;
break;
case NamedPackets.Handshake:
thisUser.username = reader.readString();
socket.write(new PacketMappingTable[NamedPackets.Handshake](thisUser.username).writePacket());
break;
}
});
socket.on('end', function() {

16
server/user.js Normal file
View File

@ -0,0 +1,16 @@
const funkyArray = require("./Util/funkyArray.js");
const Socket = require("net").Socket;
module.exports = class {
constructor(id = 1, socket = new Socket) {
this.id = id;
this.socket = socket;
this.username = "UNNAMED";
this.loginFinished = false;
this.chunksToSend = new funkyArray();
}
}