Initial TypeScript commit

This commit is contained in:
Holly Stubbs 2023-04-08 20:52:47 +01:00
parent d730e8f115
commit 3edcbc062f
Signed by: tgpholly
GPG key ID: B8583C4B7D18119E
80 changed files with 4696 additions and 2514 deletions

2
.gitattributes vendored
View file

@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

110
.gitignore vendored
View file

@ -1,109 +1 @@
# Custom stuff node_modules/
old/
#===- Default node gitignore -===#
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port

View file

@ -3,21 +3,20 @@ me trying to understand minecraft beta's protocol
**Implemented:** **Implemented:**
- Basic flat terrain generation - Basic flat terrain generation
**WIP:**
**To Implement:**
- Terrain sending - Terrain sending
- Block placement - Block placement
- Block breaking - Block breaking
- Entities: - Entities:
- Players - Players
**WIP:**
- Terrain generation using perlin noise
- Cross chunk structure generation (trees, buildings, etc...)
**To Implement:**
- Entities:
- Items/Blocks - Items/Blocks
- Animals - Animals
- Mobs - Mobs
- Terrain generation using perlin noise
- Cross chunk structure generation (trees, buildings, etc...)
- Inventories - Inventories
- Terrain saving to disk - Terrain saving to disk
- Sleeping in beds - Sleeping in beds

206
bufferStuff.ts Normal file
View file

@ -0,0 +1,206 @@
export class Reader {
private buffer:Buffer;
private offset:number;
public constructor(buffer:Buffer) {
this.buffer = buffer;
this.offset = 0;
}
public readByte() {
const value = this.buffer.readInt8(this.offset);
this.offset++;
return value;
}
public readUByte() {
const value = this.buffer.readUInt8(this.offset);
this.offset++;
return value;
}
public readBool() {
return Boolean(this.readUByte());
}
public readShort() {
const value = this.buffer.readInt16BE(this.offset);
this.offset += 2
return value;
}
public readInt() {
const value = this.buffer.readInt32BE(this.offset);
this.offset += 4;
return value;
}
public readLong() {
const value = this.buffer.readBigInt64BE(this.offset);
this.offset += 8;
return value;
}
public readFloat() {
const value = this.buffer.readFloatBE(this.offset);
this.offset += 4;
return value;
}
public readDouble() {
const value = this.buffer.readDoubleBE(this.offset);
this.offset += 8;
return value;
}
public readString() {
const length = this.readShort();
let text:string = "";
for (let i = 0; i < length; i++) {
text += String.fromCharCode(this.readShort());
}
return text;
}
}
export class Writer {
private buffer:Buffer;
private offset:number;
private resizable:boolean;
public constructor(size:number = 0) {
this.buffer = Buffer.alloc(size);
this.offset = 0;
this.resizable = size === 0;
}
public toBuffer() {
return this.buffer;
}
public toString() {
return this.buffer.toString();
}
public writeBuffer(buffer:Buffer) {
this.buffer = Buffer.concat([this.buffer, buffer], this.buffer.length + buffer.length);
return this;
}
public writeByte(value:number) {
if (this.resizable) {
const buffer = Buffer.alloc(1);
buffer.writeInt8(value);
this.writeBuffer(buffer);
} else {
this.buffer.writeInt8(value, this.offset);
this.offset++;
}
return this;
}
public writeUByte(value:number) {
if (this.resizable) {
const buffer = Buffer.alloc(1);
buffer.writeUInt8(value);
this.writeBuffer(buffer);
} else {
this.buffer.writeUInt8(value, this.offset);
this.offset++;
}
return this;
}
public writeBool(value:boolean|number) {
if (typeof(value) === "number") {
value = Boolean(value);
}
this.writeUByte(value ? 1 : 0);
return this;
}
public writeShort(value:number) {
if (this.resizable) {
const buffer = Buffer.alloc(2);
buffer.writeInt16BE(value);
this.writeBuffer(buffer);
} else {
this.buffer.writeInt16BE(value, this.offset);
this.offset += 2;
}
return this;
}
public writeInt(value:number) {
if (this.resizable) {
const buffer = Buffer.alloc(4);
buffer.writeInt32BE(value);
this.writeBuffer(buffer);
} else {
this.buffer.writeInt32BE(value, this.offset);
this.offset += 4;
}
return this;
}
public writeLong(value:number|bigint) {
if (typeof(value) !== "bigint") {
value = BigInt(value);
}
if (this.resizable) {
const buffer = Buffer.alloc(8);
buffer.writeBigInt64BE(value);
this.writeBuffer(buffer);
} else {
this.buffer.writeBigInt64BE(value, this.offset);
this.offset += 8;
}
return this;
}
public writeFloat(value:number) {
if (this.resizable) {
const buffer = Buffer.alloc(4);
buffer.writeFloatBE(value);
this.writeBuffer(buffer);
} else {
this.buffer.writeFloatBE(value, this.offset);
this.offset += 4;
}
return this;
}
public writeDouble(value:number) {
if (this.resizable) {
const buffer = Buffer.alloc(8);
buffer.writeDoubleBE(value);
this.writeBuffer(buffer);
} else {
this.buffer.writeDoubleBE(value, this.offset);
this.offset += 8;
}
return this;
}
public writeString(text:string) {
this.writeShort(text.length);
for (let i = 0; i < text.length; i++) {
this.writeShort(text.charCodeAt(i));
}
return this;
}
}

View file

@ -1,4 +1,5 @@
{ {
"port": 25565, "port": 25565,
"worldThreads": 4 "onlineMode": false,
} "maxPlayers": 20
}

5
config.ts Normal file
View file

@ -0,0 +1,5 @@
export interface Config {
port: number,
onlineMode: boolean,
maxPlayers: number
}

67
console.ts Normal file
View file

@ -0,0 +1,67 @@
import chalk from "chalk";
console.clear();
enum LogType {
INFO,
WARN,
ERROR
};
const LogTags = {
INFO: chalk.bgGreen(chalk.black(" INFO ")),
BANCHO: chalk.bgMagenta(chalk.black(" BANCHO ")),
WEBREQ: chalk.bgGreen(chalk.black(" WEBREQ ")),
CHAT: chalk.bgCyan(chalk.black(" CHAT ")),
WARN: chalk.bgYellow(chalk.black(" WARN ")),
ERROR: chalk.bgRed(" ERRR "),
REDIS: chalk.bgRed(chalk.white(" bREDIS ")),
STREAM: chalk.bgBlue(chalk.black(" STREAM "))
} as const;
function correctValue(i:number) : string {
if (i <= 9) return `0${i}`;
else return i.toString();
}
function getTime() : string {
const time = new Date();
return chalk.green(`[${correctValue(time.getHours())}:${correctValue(time.getMinutes())}:${correctValue(time.getSeconds())}]`);
}
function log(tag:string, log:string, logType:LogType = LogType.INFO) : void {
switch (logType) {
case LogType.INFO:
return console.log(`${getTime()} ${tag} ${log}`);
case LogType.WARN:
return console.warn(`${getTime()} ${tag} ${log}`);
case LogType.ERROR:
return console.error(`${getTime()} ${tag} ${log}`);
}
}
export class Console {
public static printWebReq(s:string) : void {
log(LogTags.WEBREQ, s);
}
public static printStream(s:string) : void {
log(LogTags.STREAM, s);
}
public static printInfo(s:string) : void {
log(LogTags.INFO, s);
}
public static printChat(s:string) : void {
log(LogTags.CHAT, s);
}
public static printWarn(s:string) : void {
log(LogTags.WARN, s);
}
public static printError(s:string) : void {
log(LogTags.ERROR, s);
}
}

73
funkyArray.ts Normal file
View file

@ -0,0 +1,73 @@
export class FunkyArray<T, TT> {
private items:Map<T, TT> = new Map<T, TT>();
private itemKeys:Array<T> = new Array<T>();
private _getKeys() : Array<T> {
const keyArray = new Array<T>();
let result:IteratorResult<T, T>;
const iterator = this.items.keys();
while (!(result = iterator.next()).done) {
keyArray.push(result.value);
}
return keyArray;
}
public set(key:T, item:TT, regenerate:boolean = true) : TT {
this.items.set(key, item);
if (regenerate) {
this.itemKeys = this._getKeys();
}
return item;
}
public remove(key:T, regenerate:boolean = true) {
const success = this.items.delete(key);
if (regenerate) {
this.itemKeys = this._getKeys();
}
return success;
}
public removeFirst(regenerate:boolean = true) {
const success = this.items.delete(this.items.keys().next().value);
if (regenerate) {
this.itemKeys = this._getKeys();
}
return success;
}
public first() : TT {
return this.items.values().next().value;
}
public get length() : number {
return this.items.size;
}
public get(key:T) : TT | undefined {
return this.items.get(key);
}
public get keys() : Array<T> {
return this.itemKeys;
}
public forEach(callback: (value:TT) => void) {
return new Promise<boolean>((resolve, reject) => {
if (this.items.size === 0) {
return resolve(true);
}
try {
const iterator = this.items.values();
let result:IteratorResult<TT, TT>;
while (!(result = iterator.next()).done) {
callback(result.value);
}
resolve(true);
} catch (e) {
reject(e);
}
});
}
}

View file

@ -1,15 +0,0 @@
/*
==============- index.js -==============
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const server = new (require('net').Server)();
const config = require("./config.json");
const mcServer = require("./server/server.js");
server.listen(config.port, () => mcServer.init(config));
server.on('connection', mcServer.connection);

6
index.ts Normal file
View file

@ -0,0 +1,6 @@
import { Config } from "./config";
import { readFileSync } from "fs";
import { MinecraftServer } from "./server/MinecraftServer";
const config:Config = JSON.parse(readFileSync("./config.json").toString()) as Config;
const mcServer = new MinecraftServer(config);

3463
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,13 @@
"name": "mc-beta-server", "name": "mc-beta-server",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.ts",
"scripts": {}, "scripts": {
"dev:run": "nodemon --watch './**/*.ts' index.ts",
"pack": "webpack",
"build": "tsc --build",
"_clean": "tsc --build --clean"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/tgpethan/mc-beta-server.git" "url": "git+https://github.com/tgpethan/mc-beta-server.git"
@ -16,6 +21,17 @@
}, },
"homepage": "https://github.com/tgpethan/mc-beta-server#readme", "homepage": "https://github.com/tgpethan/mc-beta-server#readme",
"dependencies": { "dependencies": {
"chalk": "^4.1.0",
"net": "^1.0.2" "net": "^1.0.2"
},
"devDependencies": {
"@types/node": "^18.15.11",
"nodemon": "^2.0.20",
"ts-loader": "^9.4.1",
"ts-node": "^10.9.1",
"typescript": "^4.9.3",
"webpack": "^5.75.0",
"webpack-cli": "^4.10.0",
"webpack-node-externals": "^3.0.0"
} }
} }

View file

@ -1,86 +0,0 @@
class Block {
static blocksList = [];
static blockOpaqueList = [];
static blockOpacity = [];
static blockGrassable = [];
static blockContainer = [];
constructor(blockID = 0) {
this.blockID = blockID;
this.blockHardness = 0;
this.blockResistance = 0;
this.blockName = "";
if (Block.blocksList[blockID] != null) {
throw `[Block] Slot ${blockID} is already occupied by ${Block.blocksList[blockID]} when adding ID:${blockID}`;
} else {
Block.blocksList[blockID] = this;
Block.blockOpaqueList[blockID] = false; // TODO: Block opaque
Block.blockOpacity[blockID] = 0; // TODO: Opacity
Block.blockGrassable[blockID] = false; // TODO: Get if block can be grass'd
Block.blockContainer[blockID] = false; // TODO: Containers
}
}
setHardness(hardness = 0) {
this.blockHardness = hardness;
return this;
}
setBlockUnbreakable() {
this.blockHardness = -1;
return this;
}
setResistance(resistance = 0) {
this.blockHardness = resistance;
return this;
}
setName(name = "") {
this.blockName = name;
return this;
}
static stone = new Block(1).setHardness(1.5).setResistance(10).setName("Stone");
static grass = new Block(2).setHardness(0.6).setName("Grass");
static dirt = new Block(3).setHardness(0.5).setName("Dirt");
static cobblestone = new Block(4).setHardness(2.0).setResistance(10).setName("Cobblestone");
static planks = new Block(5).setHardness(2).setResistance(5).setName("Planks");
static sapling = new Block(6).setName("Sapling");
static bedrock = new Block(7).setBlockUnbreakable().setResistance(6000000).setName("Bedrock");
static waterFlowing = new Block(8).setHardness(100).setName("Flowing Water");
static waterStill = new Block(9).setHardness(100).setName("Still Water");
static lavaMoving = new Block(10).setHardness(1.5).setResistance(10).setName("Flowing Lava");
static lavaStill = new Block(11).setHardness(1.5).setResistance(10).setName("Still Lava");
static sand = new Block(12).setHardness(1.5).setResistance(10).setName("Sand");
static gravel = new Block(13).setHardness(1.5).setResistance(10).setName("Gravel");
static goldOre = new Block(14).setHardness(1.5).setResistance(10).setName("Gold Ore");
static ironOre = new Block(15).setHardness(1.5).setResistance(10).setName("Iron Ore");
static coalOre = new Block(16).setHardness(1.5).setResistance(10).setName("Coal Ore");
static wood = new Block(17).setHardness(1.5).setResistance(10).setName("Wood");
static leaves = new Block(18).setHardness(1.5).setResistance(10).setName("Leaves");
static sponge = new Block(19).setHardness(1.5).setResistance(10).setName("Sponge");
static glass = new Block(20).setHardness(1.5).setResistance(10).setName("Glass");
static lapisOre = new Block(21).setHardness(1.5).setResistance(10).setName("Lapis Ore");
static lapisBlock = new Block(22).setHardness(1.5).setResistance(10).setName("Lapis Block");
static dispenser = new Block(23).setHardness(1.5).setResistance(10).setName("Dispenser");
static sandStone = new Block(24).setHardness(1.5).setResistance(10).setName("Sandstone");
static noteBlock = new Block(25).setHardness(1.5).setResistance(10).setName("Noteblock");
static blockBed = new Block(26).setHardness(1.5).setResistance(10).setName("Bed");
static poweredRail = new Block(27).setHardness(1.5).setResistance(10).setName("Powered Rail");
static detectorRail = new Block(28).setHardness(1.5).setResistance(10).setName("Detector Rail");
static stickyPisonBase = new Block(29).setHardness(1.5).setResistance(10).setName("Sticky Piston Base");
static cobweb = new Block(30).setHardness(4).setName("Cobweb");
static tallGrass = new Block(31).setName("Tall Grass");
static deadBush = new Block(32).setName("Dead Bush");
static pistonBase = new Block(33).setName("Piston Base");
static pistonExtension = new Block(34).setName("Piston Extension");
static wool = new Block(35).setHardness(0.8).setName("Wool");
static pistonMoving = new Block(36).setName("Piston Moving")
static dandilion = new Block(37).setName("Dandilion");
static rose = new Block(38).setName("Rose");
}
module.exports = Block;

36
server/Chunk.ts Normal file
View file

@ -0,0 +1,36 @@
import { Block } from "./blocks/Block";
import { World } from "./World";
export class Chunk {
private readonly MAX_HEIGHT:number = 128;
private readonly world:World;
private readonly x:number;
private readonly z:number;
private blocks:Uint8Array;
public static CreateCoordPair(x:number, z:number) {
return (x >= 0 ? 0 : 2147483648) | (x & 0x7fff) << 16 | (z >= 0 ? 0 : 0x8000) | z & 0x7fff;
}
public constructor(world:World, x:number, z:number) {
this.world = world;
this.x = x;
this.z = z;
this.blocks = new Uint8Array(16 * 16 * this.MAX_HEIGHT);
this.world.generator.generate(this);
}
public setBlock(blockId:number, x:number, y:number, z:number) {
this.blocks[x << 11 | z << 7 | y] = blockId;
}
public getBlockId(x:number, y:number, z:number) {
return this.blocks[x << 11 | z << 7 | y];
}
public getData() {
return this.blocks;
}
}

View file

@ -1,30 +0,0 @@
/*
============- Converter.js -============
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
module.exports.toAbsoluteInt = function(float = 0.0) {
return Math.floor(float * 32.0);
}
module.exports.to360Fraction = function(float = 0.0) {
if (float < 0) {
return Math.abs(Math.max(Math.floor((map(float, 0, -360, 360, 0) / 360) * 256) - 1, 0));
} else if (float > 0) {
return Math.max(Math.floor((float / 360) * 256) - 1, 0);
}
//return Math.max(Math.floor((float / 360) * 256), 0) - 1;
}
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);
}

View file

@ -1,28 +0,0 @@
/*
==============- Entity.js -=============
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
class Entity {
constructor(EID = 0, x = 0, y = 0, z = 0, yaw = 0, pitch = 0) {
this.EID = EID;
this.x = x;
this.y = y;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.motionX = 0;
this.motionY = 0;
this.motionZ = 0;
}
onTick() {
}
}
module.exports = Entity;

View file

@ -1,20 +0,0 @@
/*
============- EntityItem.js -===========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const Entity = require("./Entity.js");
class EntityItem extends Entity {
constructor(itemStack, x = 0, y = 0, z = 0) {
super(global.fromIDPool(), x, y, z);
this.itemStack = itemStack;
this.motionX = (Math.random() * 0.2 - 0.1);
this.motionY = 0.2;
this.motionZ = (Math.random() * 0.2 - 0.1);
}
}

View file

@ -1,22 +0,0 @@
/*
==========- EntityLiving.js -===========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const Entity = require("./Entity.js");
class EntityLiving extends Entity {
constructor(EID = 0, x = 0, y = 0, z = 0) {
super(EID, x, y, z);
}
onTick() {
super.onTick();
}
}
module.exports = EntityLiving;

View file

@ -1,73 +0,0 @@
/*
===========- EntityPlayer.js -==========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const EntityLiving = require("./EntityLiving.js");
const user = require("../user.js");
const Converter = require("../Converter.js");
const PacketMappingTable = require("../PacketMappingTable.js");
const NamedPackets = require("../NamedPackets.js");
class EntityPlayer extends EntityLiving {
constructor(parent = new user, x = 0, y = 0, z = 0) {
super(parent.id, x, y, z);
this.lastX = 0;
this.lastY = 0;
this.lastZ = 0;
this.absX = 0;
this.absY = 0;
this.absZ = 0;
this.absYaw = 0;
this.absPitch = 0;
this.lastYaw = 0;
this.lastPitch = 0;
this.allPacket = new PacketMappingTable[NamedPackets.EntityTeleport](this.EID, this.x, this.y, this.z, this.absYaw, this.absPitch);
this.allPacket.soup();
this.allPacket.writePacket();
this.parentPlayer = parent;
}
onTick() {
super.onTick();
this.absX = this.x.toFixed(2);
this.absY = this.y.toFixed(2);
this.absZ = this.z.toFixed(2);
this.absYaw = Math.floor(this.yaw);
this.absPitch = Math.floor(this.pitch);
if ((this.absX != this.lastX || this.absY != this.lastY || this.absZ != this.lastZ)) {
// all
this.allPacket.writer.offset = 5;
this.allPacket.writer.writeInt(Converter.toAbsoluteInt(this.x));
this.allPacket.writer.writeInt(Converter.toAbsoluteInt(this.y));
this.allPacket.writer.writeInt(Converter.toAbsoluteInt(this.z));
this.allPacket.writer.writeUByte(Converter.to360Fraction(this.absYaw));
this.allPacket.writer.writeUByte(Converter.to360Fraction(this.absPitch));
global.sendToAllPlayersButSelf(this.EID, this.allPacket.toBuffer());
} else if (this.absYaw != this.lastYaw || this.absPitch != this.lastPitch) {
// look only
global.sendToAllPlayersButSelf(this.EID, new PacketMappingTable[NamedPackets.EntityLook](this.EID, this.absYaw, this.absPitch).writePacket());
}
this.lastYaw = this.absYaw;
this.lastPitch = this.absPitch;
this.lastX = this.absX;
this.lastY = this.absY;
this.lastZ = this.absZ;
}
}
module.exports = EntityPlayer;

View file

@ -1,31 +0,0 @@
const Block = require("../Blocks/Block.js");
module.exports = function(x = 0, z = 0) {
// Create chunk
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++) {
chunk[y][x][z] = [0, 0];
}
}
}
for (let y = 0; y < 128; y++) {
for (let x = 0; x < 16; 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][0] = Block.dirt.blockID;
else if (y == 0) chunk[y][x][z][0] = Block.bedrock.blockID;
else if (y < 62) chunk[y][x][z][0] = Block.stone.blockID;
else chunk[y][x][z][0] = 0;
}
}
}
return chunk;
}

View file

@ -1,187 +0,0 @@
const { perlin2D } = require("./perlin.js");
const Block = require("../Blocks/Block.js");
module.exports = function(cx = 0, cz = 0, seed = 0) {
// Create bare chunk
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++) {
chunk[y][x][z] = [0, 0];
}
}
}
let stripTopCoord = {};
// History has shown it's better to alloc all at once
for (let x = 0; x < 16; x++) {
stripTopCoord[x] = {};
for (let z = 0; z < 16; z++) {
stripTopCoord[x][z] = 0;
}
}
// Generate top layer of grass
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
// NOTE: Because of the way this is, it is not random at all. The heightmap is simply offset so uhhh.
// TODO: Figure out a better way of dealing with this :)
const layer1 = (64 + (perlin2D(((cx << 4) + x) / 15, ((cz << 4) + z) / 15) * 10));
const layer2 = (64 + (perlin2D(((cx + (10 + seed) << 4) + x) / 15, ((cz + (4 + seed) << 4) + z) / 15) * 10));
const layer3_1 = (64 + (perlin2D(((cx + (-15 + seed) << 4) + x) / 15, ((cz + (-2 + seed) << 4) + z) / 15) * 23));
const layer3_2 = (64 + (perlin2D(((cx + (25 + seed) << 4) + x) / 15, ((cz + (-17 + seed) << 4) + z) / 15) * 40));
const layer3 = (layer3_1 + layer3_2) / 2;
const average = Math.floor((layer1 + layer2 + layer3) / 3);
stripTopCoord[x][z] = average;
chunk[average][x][z][0] = Block.grass.blockID;
}
}
// Generate down from the top layer
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
// Cache these otherwise we'll be doing more maths 128 times for each column and row
const topM1 = stripTopCoord[x][z] - 1,
topM2 = topM1 - 1;
for (let y = stripTopCoord[x][z]; y != -1; y--) {
if (y == topM1 || y == topM2) chunk[y][x][z][0] = Block.dirt.blockID;
else if (y == 0) chunk[y][x][z][0] = Block.bedrock.blockID;
else if (y < topM2) chunk[y][x][z][0] = Block.stone.blockID;
}
}
}
// 2nd pass
for (let y = 0; y < 128; y++) {
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
if (chunk[y][x][z][0] == 0 && y < 64) chunk[y][x][z][0] = Block.waterStill.blockID;
if (y < 127 && y > 0) if (chunk[y][x][z][0] == Block.waterStill.blockID && chunk[y - 1][x][z][0] == 2) chunk[y - 1][x][z][0] = Block.dirt.blockID;
//if (x == 0 && z == 0) chunk[y][x][z] = 57;
}
}
}
let treeBlocks = [];
const chunkX = cx << 4;
const chunkZ = cz << 4;
let topBlock = 0;
// 3rd pass???
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
topBlock = stripTopCoord[x][z];
if (chunk[topBlock][x][z][0] == Block.grass.blockID) {
if (Math.floor(Math.random() * 5) == 0) {
chunk[topBlock + 1][x][z][0] = Block.tallGrass.blockID;
chunk[topBlock + 1][x][z][1] = 1;
} else if (Math.floor(Math.random() * 150) == 0) {
chunk[topBlock + 1][x][z][0] = Block.rose.blockID;
} else if (Math.floor(Math.random() * 150) == 0) {
chunk[topBlock + 1][x][z][0] = Block.dandilion.blockID;
}
}
// Need a better way of doing this it currently takes a severely long time (gee I wonder why)
if (chunk[topBlock][x][z][0] == 2 && Math.floor(Math.random() * 200) == 0) {
chunk[topBlock][x][z][0] = 3;
// Logs
treeBlocks.push([(chunkX + x), topBlock + 1, (chunkZ + z), Block.wood.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 2, (chunkZ + z), Block.wood.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 3, (chunkZ + z), Block.wood.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 4, (chunkZ + z), Block.wood.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 5, (chunkZ + z), Block.wood.blockID]);
// Leaves
// Layer 1
treeBlocks.push([(chunkX + x) - 1, topBlock + 3, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 2, topBlock + 3, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 3, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 2, topBlock + 3, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 3, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 3, (chunkZ + z) - 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 3, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 3, (chunkZ + z) + 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 3, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 3, (chunkZ + z) - 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 3, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 3, (chunkZ + z) + 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 2, topBlock + 3, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 2, topBlock + 3, (chunkZ + z) - 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 2, topBlock + 3, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 2, topBlock + 3, (chunkZ + z) + 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 1, topBlock + 3, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 1, topBlock + 3, (chunkZ + z) - 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 1, topBlock + 3, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 1, topBlock + 3, (chunkZ + z) + 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 2, topBlock + 3, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 2, topBlock + 3, (chunkZ + z) - 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 2, topBlock + 3, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 2, topBlock + 3, (chunkZ + z) + 2, Block.leaves.blockID]);
// Layer 2
treeBlocks.push([(chunkX + x) - 1, topBlock + 4, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 2, topBlock + 4, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 4, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 2, topBlock + 4, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 4, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 4, (chunkZ + z) - 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 4, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 4, (chunkZ + z) + 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 4, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 4, (chunkZ + z) - 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 4, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 4, (chunkZ + z) + 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 2, topBlock + 4, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 2, topBlock + 4, (chunkZ + z) - 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 2, topBlock + 4, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 2, topBlock + 4, (chunkZ + z) + 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 1, topBlock + 4, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 1, topBlock + 4, (chunkZ + z) - 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 1, topBlock + 4, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 1, topBlock + 4, (chunkZ + z) + 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 2, topBlock + 4, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 2, topBlock + 4, (chunkZ + z) - 2, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 2, topBlock + 4, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 2, topBlock + 4, (chunkZ + z) + 2, Block.leaves.blockID]);
// Layer 3
treeBlocks.push([(chunkX + x) - 1, topBlock + 5, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 5, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 5, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 5, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 1, topBlock + 5, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 5, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) - 1, topBlock + 5, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 5, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 5, (chunkZ + z) - 1, Block.leaves.blockID]);
// Layer 4
treeBlocks.push([(chunkX + x) - 1, topBlock + 6, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x) + 1, topBlock + 6, (chunkZ + z), Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 6, (chunkZ + z) - 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 6, (chunkZ + z) + 1, Block.leaves.blockID]);
treeBlocks.push([(chunkX + x), topBlock + 6, (chunkZ + z), Block.leaves.blockID]);
}
}
}
return [chunk, treeBlocks];
}

View file

@ -1,307 +0,0 @@
/**
* A Perlin Noise library for JavaScript.
*
* Based on implementations by Ken Perlin (Java, C) & Stefan Gustavson (C).
*
* MIT License.
* Copyright (c) 2018 Leonardo de S.L.F.
* http://leodeslf.com/
*/
// Permutation table.
const p = [
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140,
36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234,
75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237,
149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48,
27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105,
92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73,
209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86,
164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38,
147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189,
28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153,
101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224,
232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144,
12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214,
31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150,
254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66,
215, 61, 156, 180
];
const P = [...p, ...p];
// Utility functions.
function fade(t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
function lerp(t, a, b) {
return a + t * (b - a);
}
// Helper functions to find gradients for each vertex.
function gradient1D(hash, x) {
return hash & 1 ? -x : x;
}
function gradient2D(hash, x, y) {
const H = hash & 3;
return (H < 2 ? x : -x) + (H === 0 || H === 2 ? y : -y);
}
function gradient3D(hash, x, y, z) {
const
H = hash & 15,
U = H < 8 ? x : y,
V = H < 4 ? y : H === 12 || H === 14 ? x : z;
return ((H & 1) ? -U : U) + ((H & 2) ? -V : V);
}
function gradient4D(hash, x, y, z, t) {
const
H = hash & 31,
U = H < 24 ? x : y,
V = H < 16 ? y : z,
W = H < 8 ? z : t;
return ((H & 1) ? -U : U) + ((H & 2) ? -V : V) + ((H & 4) ? -W : W);
}
// Perlin Noise functions.
/**
* Returns a one-dimensional noise value between -1 and 1.
* @param {number} x A numeric expression.
* @returns {number} Perlin Noise value.
*/
function perlin1D(x) {
const
FX = Math.floor(x),
X = FX & 255;
x = x - FX;
return lerp(
fade(x),
gradient1D(P[P[X]], x),
gradient1D(P[P[X + 1]], x - 1)
);
}
/**
* Returns a two-dimensional noise value between -1 and 1.
* @param {number} x A numeric expression.
* @param {number} y A numeric expression.
* @returns {number} Perlin Noise value.
*/
function perlin2D(x = 0, y = 0) {
// Hack to allow this to work with integer block coords
x += 0.2;
y += 0.2;
const FX = Math.floor(x),
FY = Math.floor(y),
X = FX & 255,
Y = FY & 255,
A = P[X] + Y,
B = P[X + 1] + Y;
x = x - FX;
y = y - FY;
const
FDX = fade(x),
x1 = x - 1,
y1 = y - 1;
return lerp(
fade(y),
lerp(
FDX,
gradient2D(P[A], x, y),
gradient2D(P[B], x1, y)
),
lerp(
FDX,
gradient2D(P[A + 1], x, y1),
gradient2D(P[B + 1], x1, y1)
)
);
}
/**
* Returns a three-dimensional noise value between -1 and 1.
* @param {number} x A numeric expression.
* @param {number} y A numeric expression.
* @param {number} z A numeric expression.
* @returns {number} Perlin Noise value.
*/
function perlin3D(x, y, z) {
const
FX = Math.floor(x),
FY = Math.floor(y),
FZ = Math.floor(z),
X = FX & 255,
Y = FY & 255,
Z = FZ & 255,
A = P[X] + Y,
B = P[X + 1] + Y,
AA = P[A] + Z,
BA = P[B] + Z,
AB = P[A + 1] + Z,
BB = P[B + 1] + Z;
x = x - FX;
y = y - FY;
z = z - FZ;
const
FDX = fade(x),
FDY = fade(y),
x1 = x - 1,
y1 = y - 1,
z1 = z - 1;
return lerp(
fade(z),
lerp(
FDY,
lerp(
FDX,
gradient3D(P[AA], x, y, z),
gradient3D(P[BA], x1, y, z)
),
lerp(
FDX,
gradient3D(P[AB], x, y1, z),
gradient3D(P[BB], x1, y1, z)
)
),
lerp(
FDY,
lerp(
FDX,
gradient3D(P[AA + 1], x, y, z1),
gradient3D(P[BA + 1], x1, y, z1)
),
lerp(
FDX,
gradient3D(P[AB + 1], x, y1, z1),
gradient3D(P[BB + 1], x1, y1, z1)
)
)
);
}
/**
* Returns a four-dimensional noise value between -1 and 1.
* @param {number} x A numeric expression.
* @param {number} y A numeric expression.
* @param {number} z A numeric expression.
* @param {number} t A numeric expression.
* @returns {number} Perlin Noise value.
*/
function perlin4D(x, y, z, t) {
const
FX = Math.floor(x),
FY = Math.floor(y),
FZ = Math.floor(z),
FT = Math.floor(t),
X = FX & 255,
Y = FY & 255,
Z = FZ & 255,
T = FT & 255,
A = P[X] + Y,
B = P[X + 1] + Y,
AA = P[A] + Z,
BA = P[B] + Z,
AB = P[A + 1] + Z,
BB = P[B + 1] + Z,
AAA = P[AA] + T,
BAA = P[BA] + T,
ABA = P[AB] + T,
BBA = P[BB] + T,
AAB = P[AA + 1] + T,
BAB = P[BA + 1] + T,
ABB = P[AB + 1] + T,
BBB = P[BB + 1] + T;
x = x - FX;
y = y - FY;
z = z - FZ;
t = t - FT;
const
FDX = fade(x),
FDY = fade(y),
FDZ = fade(z),
x1 = x - 1,
y1 = y - 1,
z1 = z - 1,
t1 = t - 1;
return lerp(
fade(t),
lerp(
FDZ,
lerp(
FDY,
lerp(
FDX,
gradient4D(P[AAA], x, y, z, t),
gradient4D(P[BAA], x1, y, z, t)
),
lerp(
FDX,
gradient4D(P[ABA], x, y1, z, t),
gradient4D(P[BBA], x1, y1, z, t)
)
),
lerp(
FDY,
lerp(
FDX,
gradient4D(P[AAB], x, y, z1, t),
gradient4D(P[BAB], x1, y, z1, t)
),
lerp(
FDX,
gradient4D(P[ABB], x, y1, z1, t),
gradient4D(P[BBB], x1, y1, z1, t)
)
)
),
lerp(
FDZ,
lerp(
FDY,
lerp(
FDX,
gradient4D(P[AAA + 1], x, y, z, t1),
gradient4D(P[BAA + 1], x1, y, z, t1)
),
lerp(
FDX,
gradient4D(P[ABA + 1], x, y1, z, t1),
gradient4D(P[BBA + 1], x1, y1, z, t1)
)
),
lerp(
FDY,
lerp(
FDX,
gradient4D(P[AAB + 1], x, y, z1, t1),
gradient4D(P[BAB + 1], x1, y, z1, t1)
),
lerp(
FDX,
gradient4D(P[ABB + 1], x, y1, z1, t1),
gradient4D(P[BBB + 1], x1, y1, z1, t1)
)
)
)
);
}
module.exports = { perlin1D, perlin2D, perlin3D, perlin4D };

View file

@ -1,23 +0,0 @@
const ItemStack = require("../ItemStack.js");
module.exports = class {
constructor() {
this.Slots = {};
}
saveData() {
}
loadData() {
}
getSlot(slot = 0) {
return this.Slots[slot]; // If the slot doesn't exist well sucks to be you I guess! Haha :Þ
}
setSlot(slot = 0, itemStack = new ItemStack) {
this.Slots[slot] = itemStack;
}
}

View file

@ -1,10 +0,0 @@
module.exports = class {
constructor(id, count) {
this.id = id;
this.count = count;
}
updateCount(count) {
this.count += count;
}
}

21
server/MPClient.ts Normal file
View file

@ -0,0 +1,21 @@
import { Socket } from "net";
import { IEntity } from "./entities/IEntity";
import { Writer } from "../bufferStuff";
export class MPClient {
private readonly socket:Socket;
private readonly entity:IEntity;
public constructor(socket:Socket, entity:IEntity) {
this.socket = socket;
this.entity = entity;
}
send(buffer:Buffer|Writer) {
if (buffer instanceof Writer) {
this.socket.write(buffer.toBuffer());
} else {
this.socket.write(buffer);
}
}
}

124
server/MinecraftServer.ts Normal file
View file

@ -0,0 +1,124 @@
import { Config } from "../config";
import { Console } from "../console";
import { Server, Socket } from "net";
import { FunkyArray } from "../funkyArray";
import { World } from "./World";
import { Reader } from "../bufferStuff";
import { Packets } from "./enums/Packets";
import { PacketHandshake } from "./packets/Handshake";
import { MPClient } from "./MPClient";
import { PacketKeepAlive } from "./packets/KeepAlive";
import { PacketLoginRequest } from "./packets/LoginRequest";
import { PacketDisconnectKick } from "./packets/DisconnectKick";
import { Player } from "./entities/Player";
import { PacketTimeUpdate } from "./packets/TimeUpdate";
import { PacketSpawnPosition } from "./packets/SpawnPosition";
import { Chunk } from "./Chunk";
import { PacketMapChunk } from "./packets/MapChunk";
import { PacketPlayerPositionLook } from "./packets/PlayerPositionLook";
import { PacketPreChunk } from "./packets/PreChunk";
export class MinecraftServer {
private static readonly PROTOCOL_VERSION = 14;
private static readonly TICK_RATE = 20;
private static readonly TICK_RATE_MS = 1000 / MinecraftServer.TICK_RATE;
private readonly keepalivePacket = new PacketKeepAlive().writeData();
private totalClients:number = 0;
private config:Config;
private server:Server;
private serverClock:NodeJS.Timer;
private tickCounter:number = 0;
private clients:FunkyArray<number, MPClient>;
private worlds:FunkyArray<number, World>;
public constructor(config:Config) {
this.config = config;
this.clients = new FunkyArray<number, MPClient>();
this.worlds = new FunkyArray<number, World>();
this.worlds.set(0, new World());
this.serverClock = setInterval(() => {
// Every 1 sec
if (this.tickCounter % MinecraftServer.TICK_RATE === 0) {
if (this.clients.length !== 0) {
const timePacket = new PacketTimeUpdate(this.tickCounter).writeData();
this.clients.forEach(client => {
client.send(this.keepalivePacket);
client.send(timePacket);
});
}
}
this.worlds.forEach(world => {
world.tick(this.tickCounter);
});
this.tickCounter++;
}, MinecraftServer.TICK_RATE_MS);
this.server = new Server();
this.server.on("connection", this.onConnection.bind(this));
this.server.listen(config.port, () => Console.printInfo(`Minecraft server started at ${config.port}`));
}
sendToAllClients(buffer:Buffer) {
this.clients.forEach(client => {
client.send(buffer);
});
}
onConnection(socket:Socket) {
socket.on("data", chunk => {
const reader = new Reader(chunk);
const packetId = reader.readUByte();
//console.log(packetId);
switch (packetId) {
// Handle timeouts at some point, idk.
case Packets.KeepAlive:
break;
case Packets.LoginRequest:
const loginPacket = new PacketLoginRequest().readData(reader);
if (loginPacket.protocolVersion !== MinecraftServer.PROTOCOL_VERSION) {
if (loginPacket.protocolVersion > MinecraftServer.PROTOCOL_VERSION) {
socket.write(new PacketDisconnectKick("Outdated server!").writeData());
} else {
socket.write(new PacketDisconnectKick("Outdated or modded client!").writeData());
}
return;
}
const world = this.worlds.get(0);
if (world instanceof World) {
const clientEntity = new Player(this, world, loginPacket.username);
world.addEntity(clientEntity);
socket.write(new PacketLoginRequest(clientEntity.entityId, "", 0, -1).writeData());
socket.write(new PacketSpawnPosition(8, 64, 8).writeData());
socket.write(new PacketPreChunk(0, 0, true).writeData());
const chunk = world.getChunk(0, 0);
if (chunk instanceof Chunk) {
(async () => {
const chunkData = await (new PacketMapChunk(0, 0, 0, 15, 127, 15, chunk).writeData());
socket.write(chunkData);
socket.write(new PacketPlayerPositionLook(8, 66, 66.62, 8, 0, 0, false).writeData());
})();
}
const client = new MPClient(socket, clientEntity);
this.clients.set(this.totalClients++, client);
} else {
socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData());
}
break;
case Packets.Handshake:
const handshakePacket = new PacketHandshake().readData(reader);
socket.write(handshakePacket.writeData());
break;
}
});
}
}

View file

@ -1,69 +0,0 @@
/*
===========- NamedPackets.js -==========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const namedPackets = {
"Disconnect": -1,
"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

@ -1,42 +0,0 @@
/*
========- PacketMappingTable.js -=======
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const Packet0KeepAlive = require("./Packets/Packet0KeepAlive.js"),
Packet1LoginRequest = require("./Packets/Packet1LoginRequest.js"),
Packet2Handshake = require("./Packets/Packet2Handshake.js"),
Packet3Chat = require("./Packets/Packet3Chat.js"),
Packet4TimeUpdate = require("./Packets/Packet4TimeUpdate"),
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"),
Packet32EntityLook = require("./Packets/Packet32EntityLook.js"),
Packet34EntityTeleport = require("./Packets/Packet34EntityTeleport.js"),
Packet50PreChunk = require("./Packets/Packet50PreChunk.js"),
Packet53BlockChange = require("./Packets/Packet53BlockChange.js"),
Packet103SetSlot = require("./Packets/Packet103SetSlot.js");
const mappingTable = {
0x00: Packet0KeepAlive,
0x01: Packet1LoginRequest,
0x02: Packet2Handshake,
0x03: Packet3Chat,
0x04: Packet4TimeUpdate,
0x06: Packet6SpawnPosition,
0x0A: Packet10Player,
0x0D: Packet13PlayerPositionAndLook,
0x12: Packet18Animation,
0x14: Packet20NamedEntitySpawn,
0x20: Packet32EntityLook,
0x22: Packet34EntityTeleport,
0x32: Packet50PreChunk,
0x35: Packet53BlockChange,
0x67: Packet103SetSlot
};
module.exports = mappingTable;

View file

@ -1,30 +0,0 @@
/*
==============- Packet.js -=============
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const bufferStuff = require("../bufferStuff.js");
module.exports = class {
constructor(packetID = 0x00) {
this.id = packetID;
this.packetSize = 0;
this.writer = null;
}
writePacket() {
this.writer = new bufferStuff.Writer(this.packetSize);
this.writer.writeByte(this.id);
return this.writer;
}
toBuffer() {
return this.writer == null ? Buffer.alloc(0) : this.writer.buffer;
}
}

View file

@ -1,18 +0,0 @@
/*
========- Packet0KeepAlive.js -=========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const Packet = require("./Packet.js");
class Packet0KeepAlive extends Packet {
writePacket() {
super.writePacket();
return this.toBuffer();
}
}
module.exports = Packet0KeepAlive;

View file

@ -1,34 +0,0 @@
/*
========- Packet103SetSlot.js -=========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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;

View file

@ -1,26 +0,0 @@
/*
=========- Packet10Player.js -==========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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

@ -1,38 +0,0 @@
/*
==- Packet13PlayerPositionAndLook.js -==
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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

@ -1,28 +0,0 @@
/*
========- Packet18Animation.js -========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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;

View file

@ -1,36 +0,0 @@
/*
=======- Packet1LoginRequest.js -=======
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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

@ -1,41 +0,0 @@
/*
=====- Packet20NamedEntitySpawn.js -====
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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 = Converter.to360Fraction(yaw);
this.packedPitch = Converter.to360Fraction(pitch);
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.writeUByte(this.packedYaw);
this.writer.writeUByte(this.packedPitch);
this.writer.writeShort(this.currentItem);
return this.toBuffer();
}
}
module.exports = Packet20NamedEntitySpawn;

View file

@ -1,26 +0,0 @@
/*
=========- Packet2Handshake.js -========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const Packet = require("./Packet.js");
class Packet2Handshake extends Packet {
constructor(username = "") {
super(0x02);
this.username = username;
}
writePacket() {
super.writePacket();
this.writer.writeString("-"); // "-" == Offline mode
return this.toBuffer();
}
}
module.exports = Packet2Handshake;

View file

@ -1,33 +0,0 @@
/*
=======- Packet32EntityLook.js -========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const Packet = require("./Packet.js");
const Converter = require("../Converter.js");
class Packet32EntityLook extends Packet {
constructor(EID = 0, yaw = 0, pitch = 0) {
super(0x20);
this.packetSize = 7;
this.EID = EID;
this.packedYaw = Converter.to360Fraction(yaw);
this.packedPitch = Converter.to360Fraction(pitch);
}
writePacket() {
super.writePacket();
this.writer.writeInt(this.EID);
this.writer.writeUByte(this.packedYaw);
this.writer.writeUByte(this.packedPitch);
return this.toBuffer();
}
}
module.exports = Packet32EntityLook;

View file

@ -1,41 +0,0 @@
/*
======- Packet34EntityTeleport.js -=====
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const Packet = require("./Packet.js");
const Converter = require("../Converter.js");
class Packet34EntityTeleport extends Packet {
constructor(EID = 0, x = 0, y = 0, z = 0, yaw = 0, pitch = 0) {
super(0x22);
this.packetSize = 19;
this.EID = EID;
this.absX = Converter.toAbsoluteInt(x);
this.absY = Converter.toAbsoluteInt(y);
this.absZ = Converter.toAbsoluteInt(z);
this.packedYaw = Converter.to360Fraction(yaw);
this.packedPitch = Converter.to360Fraction(pitch);
}
soup() {
super.writePacket();
this.writer.writeInt(this.EID);
}
writePacket() {
this.writer.writeInt(this.absX);
this.writer.writeInt(this.absY);
this.writer.writeInt(this.absZ);
this.writer.writeUByte(this.packedYaw);
this.writer.writeUByte(this.packedPitch);
return this.toBuffer();
}
}
module.exports = Packet34EntityTeleport;

View file

@ -1,26 +0,0 @@
/*
===========- Packet3Chat.js -===========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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

@ -1,30 +0,0 @@
/*
========- Packet4TimeUpdate.js -========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const Packet = require("./Packet.js");
class Packet4TimeUpdate extends Packet {
constructor(time = BigInt(0)) {
super(0x04);
this.time = time;
}
readPacket() {
}
writePacket() {
super.writePacket();
this.writer.writeLong(this.time);
return this.toBuffer();
}
}
module.exports = Packet4TimeUpdate;

View file

@ -1,30 +0,0 @@
/*
========- Packet50PreChunk.js -=========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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

@ -1,34 +0,0 @@
/*
=======- Packet53BlockChange.js -=======
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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

@ -1,30 +0,0 @@
/*
======- Packet6SpawnPosition.js -=======
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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;

View file

@ -1,91 +0,0 @@
/*
===========- funkyArray.js -============
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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;
this.index++;
} else {
this.items[id = pRandom()] = item;
}
if (regenerate) {
this.regenerateIterableArray();
this.itemKeys = Object.keys(this.items);
}
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]);
}
this.itemKeys = Object.keys(this.items);
}
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

@ -1,28 +0,0 @@
/*
===========- prettyRandom.js -==========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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

@ -1,82 +0,0 @@
/*
===========- ChunkWorker.js -===========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const { parentPort } = require('worker_threads'),
{ deflateSync } = require("zlib");
const GeneratorFlat = require("../Generators/GeneratorFlat.js");
const GeneratorPerlin = require("../Generators/GeneratorPerlin.js");
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":
const startTime = Date.now();
const chunkData = generateChunk(data[1], data[2], data[4]);
parentPort.postMessage([data[0], chunkData[0], data[1], data[2], data[3], chunkData[1]]);
console.log(`Chunk took ${Date.now() - startTime}ms`);
break;
}
});
function generateChunk(x = 0, z = 0, seed = 0) {
return GeneratorPerlin(x, z, seed);
}
function doChunk(chunk) {
const writer = new bufferStuff.Writer(18);
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
// pre-alloc since doing an alloc 98,304 times takes a while yknow.
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][0]);
if (blockMeta) {
metadata.writeNibble(chunk[2][y - 1][x][z][1], chunk[2][y][x][z][1]); // NOTE: This is sorta jank but it does work
// Light level 15 for 2 blocks (1111 1111)
lighting.writeNibble(15, 15); // TODO: Lighting (Client seems to do it's own (when a block update happens) 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;
}

39
server/World.ts Normal file
View file

@ -0,0 +1,39 @@
import { FunkyArray } from "../funkyArray";
import { Chunk } from "./Chunk";
import { IEntity } from "./entities/IEntity";
import { FlatGenerator } from "./generators/Flat";
import { IGenerator } from "./generators/IGenerator";
export class World {
public chunks:FunkyArray<number, Chunk>;
public entites:FunkyArray<number, IEntity>;
public generator:IGenerator;
public constructor() {
this.chunks = new FunkyArray<number, Chunk>();
this.entites = new FunkyArray<number, IEntity>();
this.generator = new FlatGenerator();
this.chunks.set(Chunk.CreateCoordPair(0, 0), new Chunk(this, 0, 0));
}
public addEntity(entity:IEntity) {
this.entites.set(entity.entityId, entity);
}
public removeEntity(entity:IEntity|number) {
if (typeof(entity) === "number") {
return this.entites.remove(entity);
}
return this.entites.remove(entity.entityId);
}
public getChunk(x:number, z:number) {
return this.chunks.get(Chunk.CreateCoordPair(x, z));
}
public tick(tickCount:number) {
}
}

15
server/blocks/Block.ts Normal file
View file

@ -0,0 +1,15 @@
export class Block {
public readonly blockId:number;
public constructor(blockId:number) {
this.blockId = blockId;
}
static stone = new Block(1);
static grass = new Block(2);
static dirt = new Block(3);
static bedrock = new Block(7);
}

View file

@ -1,225 +0,0 @@
/*
===========- bufferStuff.js -===========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
module.exports.Writer = class {
// bufferStuff supports pre-allocating memory for the buffer
// if you pass in a size of 0 then it will just dynamicly allocate at the
// cost of performance
// NOTE: In pre-allocation mode if you exceed the size of the buffer
// that you set it will cause a crash.
constructor(size = 0) {
this.buffer = Buffer.alloc(size);
this.offset = 0;
this.baseSize = size;
}
reset() {
this.buffer = Buffer.alloc(0);
}
writeBuffer(buff = Buffer.alloc(0)) {
this.buffer = Buffer.concat([this.buffer, buff], this.buffer.length + buff.length);
}
writeBool(data = false) {
this.writeByte(data ? 1 : 0);
}
// NOTE: Currently writing a nibble requires you to write both halves at the same time.
writeNibble(nibble1 = 0, nibble2 = 0) {
this.writeUByte(nibble1 | (nibble2 << 4));
}
writeByte(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;
}
}
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]) {
if (this.baseSize == 0) {
const buff = Buffer.alloc(data.length);
for (let byte of data) {
buff.writeInt8(byte);
}
this.writeBuffer(buff);
} else {
for (let byte of data) {
this.buffer.writeInt8(byte);
this.offset += 1;
}
}
}
writeShort(data = 0) {
if (this.baseSize == 0) {
const buff = Buffer.alloc(2);
buff.writeIntBE(data, 0, 2);
this.writeBuffer(buff);
} else {
this.buffer.writeIntBE(data, this.offset, 2);
this.offset += 2;
}
}
writeShortArray(data = [0]) {
if (this.baseSize == 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);
} else {
for (let short of data) {
this.buffer.writeIntBE(short, this.offset, 2);
this.offset += 2;
}
}
}
writeInt(data = 0) {
if (this.baseSize == 0) {
const buff = Buffer.alloc(4);
buff.writeIntBE(data, 0, 4);
this.writeBuffer(buff);
} else {
this.buffer.writeIntBE(data, this.offset, 4);
this.offset += 4;
}
}
writeLong(data = 0) {
if (this.baseSize == 0) {
const buff = Buffer.alloc(8);
if (data instanceof BigInt) buff.writeBigInt64BE(data, 0);
else buff.writeBigInt64BE(BigInt(data), 0);
this.writeBuffer(buff);
} else {
if (data instanceof BigInt) this.buffer.writeBigInt64BE(data, this.offset);
else this.buffer.writeBigInt64BE(BigInt(data), this.offset);
this.offset += 8;
}
}
writeFloat(data = 0.0) {
if (this.baseSize == 0) {
const buff = Buffer.alloc(4);
buff.writeFloatBE(data, 0);
this.writeBuffer(buff);
} else {
this.buffer.writeFloatBE(data, this.offset);
this.offset += 4;
}
}
writeDouble(data = 0.0) {
if (this.baseSize == 0) {
const buff = Buffer.alloc(8);
buff.writeDoubleBE(data, 0);
this.writeBuffer(buff);
} else {
this.buffer.writeDoubleBE(data, this.offset);
this.offset += 8;
}
}
writeString(string = "") {
this.writeShort(string.length);
for (let i = 0; i < string.length; i++) {
this.writeShort(string.charCodeAt(i));
}
}
}
module.exports.Reader = class {
constructor(buffer = Buffer.alloc(0)) {
this.buffer = buffer;
this.offset = 0;
}
readBool() {
return this.readByte() == 0x01 ? true : false;
}
readByte() {
const data = this.buffer.readInt8(this.offset);
this.offset += 1;
return data;
}
readShort() {
const data = this.buffer.readIntBE(this.offset, 2);
this.offset += 2;
return data;
}
readInt() {
const data = this.buffer.readIntBE(this.offset, 4);
this.offset += 4;
return data;
}
readLong() {
const data = this.buffer.readBigInt64BE(this.offset);
this.offset += 8;
return data;
}
readFloat() {
const data = this.buffer.readFloatBE(this.offset);
this.offset += 4;
return data;
}
readDouble() {
const data = this.buffer.readDoubleBE(this.offset);
this.offset += 8;
return data;
}
readString() {
const length = this.readShort();
let data = "";
for (let i = 0; i < length; i++) {
data += String.fromCharCode(this.readShort());
}
return data;
}
}

View file

@ -1,130 +0,0 @@
/*
===========- chunkManager.js -==========
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
const { Worker } = require('worker_threads');
const FunkyArray = require("./Util/funkyArray.js");
const pRandom = require("./Util/prettyRandom.js");
const config = require("../config.json");
const workerPath = `${__dirname}/Workers/ChunkWorker.js`;
module.exports = class {
constructor() {
this.chunks = {};
this.queuedBlockUpdates = new FunkyArray();
global.generatingChunks = true;
this.threadPool = [];
this.workPool = new FunkyArray();
this.toRemove = [];
// TODO: Figure out a better way of doing this?
this.seed = pRandom(-2147483647, 2147483647) - new Date().getTime() * pRandom(1, 6);
// WoAh!!! Thread pool in js!?!??!???!11!?!?!
for (let i = 0; i < config.worldThreads; i++) {
const worker = new Worker(workerPath);
this.threadPool.push([false, worker]);
const myID = i;
worker.on("message", (data) => {
let user;
switch (data[0]) {
case "chunk":
user = global.getUserByKey(data[2]);
user.chunksToSend.add(Buffer.from(data[1]));
this.toRemove.push(data[1]);
this.threadPool[myID][0] = false;
break;
case "generate":
this.chunks[data[2]][data[3]] = data[1];
this.toRemove.push(data[4]);
const treeBlocksRef = data[5];
treeBlocksRef.forEach((block) => {
this.setBlock(block[0], block[1], block[2], block[3], 0, false);
});
this.queuedBlockUpdates.regenerateIterableArray();
this.threadPool[myID][0] = false;
break;
}
});
}
console.log("Created thread pool with " + this.threadPool.length + " threads");
setInterval(() => {
if (this.workPool.getLength() > 0) {
let limit = Math.min(this.workPool.getLength(), this.threadPool.length);
for (let i = 0; i < limit; i++) {
for (let i1 = 0; i1 < this.threadPool.length; i1++) {
let thread = this.threadPool[i1];
if (!thread[0]) {
const key = this.workPool.itemKeys[i];
const item = this.workPool.getByKey(key);
// Already being processed
if (item == null) break;
if (item[0] == true) {
limit += 1;
break;
}
item[0] = true;
item[1][3] = key;
thread[1].postMessage(item[1]);
thread[0] = true;
break;
}
}
}
for (let item of this.toRemove) {
//console.log("removing item " + this.workPool.getByKey(item)[0]);
this.workPool.remove(item);
}
this.toRemove = [];
}
}, 1000 / 60);
global.generatingChunks = false;
}
// TODO: Store metadata!
createChunk(cx = 0, cz = 0) {
if (this.chunks[cx] == null) this.chunks[cx] = {};
this.workPool.add([false, ["generate", cx, cz, null, this.seed]]);
}
chunkExists(cx = 0, cz = 0) {
if (this.chunks[cx] == null) return false;
if (this.chunks[cx][cz] == null) return false;
// In any other case the chunk exists
return true;
}
multiBlockChunk(chunkX = 0, chunkZ = 0, user) {
this.workPool.add([false, ["chunk", [chunkX, chunkZ, this.chunks[chunkX][chunkZ]], user.id, null]]);
}
setBlock(x = 0, y = 0, z = 0, id = 0, metadata = 0, regenerate = true) {
if (y < 0 || y > 127) return console.error("Tried to set a block outside of the world!");
const chunkX = x >> 4;
const chunkZ = z >> 4;
const blockX = x - (chunkX << 4);
const blockZ = z - (chunkZ << 4);
// Don't queue a block update if that block is already this block (wow those ifs)
if (this.chunks[chunkX] != null)
if (this.chunks[chunkX][chunkZ] != null)
if (this.chunks[chunkX][chunkZ][y][blockX][blockZ] == id) return;
this.queuedBlockUpdates.add([chunkX, chunkZ, y, blockX, blockZ, id, metadata], regenerate);
}
}

23
server/entities/Entity.ts Normal file
View file

@ -0,0 +1,23 @@
import { World } from "../World";
import { IEntity } from "./IEntity";
export class Entity implements IEntity {
public static nextEntityId:number = 0;
public entityId:number;
public world:World;
public x:number;
public y:number;
public z:number;
public lastX:number;
public lastY:number;
public lastZ:number;
public constructor(world:World) {
this.entityId = Entity.nextEntityId++;
this.world = world;
this.x = this.y = this.z = this.lastX = this.lastY = this.lastZ = 0;
}
}

View file

@ -0,0 +1,3 @@
export interface IEntity {
entityId:number
}

18
server/entities/Player.ts Normal file
View file

@ -0,0 +1,18 @@
import { MinecraftServer } from "../MinecraftServer";
import { World } from "../World";
import { Entity } from "./Entity";
export class Player extends Entity {
public username:string;
private server:MinecraftServer;
public constructor(server:MinecraftServer, world:World, username:string) {
super(world);
this.server = server;
this.username = username;
this.x = 8;
this.y = 64;
this.z = 8;
}
}

21
server/enums/Packets.ts Normal file
View file

@ -0,0 +1,21 @@
export enum Packets {
KeepAlive = 0x00,
LoginRequest = 0x01,
Handshake = 0x02,
Chat = 0x03,
TimeUpdate = 0x04,
EntityEquipment = 0x05,
SpawnPosition = 0x06,
UseEntity = 0x07,
UpdateHealth = 0x08,
Respawn = 0x09,
Player = 0x0A,
PlayerPosition = 0x0B,
PlayerLook = 0x0C,
PlayerPositionLook = 0x0D,
PreChunk = 0x32,
MapChunk = 0x33,
DisconnectKick = 255
}

23
server/generators/Flat.ts Normal file
View file

@ -0,0 +1,23 @@
import { Block } from "../blocks/Block";
import { Chunk } from "../Chunk";
import { IGenerator } from "./IGenerator";
export class FlatGenerator implements IGenerator {
public generate(chunk:Chunk) {
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
for (let y = 0; y < 128; y++) {
if (y === 63) {
chunk.setBlock(Block.grass.blockId, x, y, z);
} else if (y === 62 || y === 61) {
chunk.setBlock(Block.dirt.blockId, x, y, z);
} else if (y === 0) {
chunk.setBlock(Block.bedrock.blockId, x, y, z);
} else if (y < 61) {
chunk.setBlock(Block.stone.blockId, x, y, z);
}
}
}
}
}
}

View file

@ -0,0 +1,5 @@
import { Chunk } from "../Chunk";
export interface IGenerator {
generate: (chunk:Chunk) => void
}

22
server/packets/Chat.ts Normal file
View file

@ -0,0 +1,22 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketChat implements IPacket {
public packetId = Packets.Chat;
public message:string;
public constructor(message:string) {
this.message = message;
}
public readData(reader:Reader) {
this.message = reader.readString();
return this;
}
public writeData() {
return new Writer(3 + this.message.length * 2).writeUByte(this.packetId).writeString(this.message).toBuffer();
}
}

View file

@ -0,0 +1,20 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketDisconnectKick implements IPacket {
public packetId = Packets.DisconnectKick;
public reason:string;
public constructor(reason:string) {
this.reason = reason;
}
public readData(reader:Reader) {
return this;
}
public writeData() {
return new Writer(3 + this.reason.length * 2).writeUByte(this.packetId).writeString(this.reason).toBuffer();
}
}

View file

@ -0,0 +1,31 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketEntityEquipment implements IPacket {
public packetId = Packets.EntityEquipment;
public entityId:number;
public slot:number;
public itemId:number;
public damage:number;
public constructor(entityId:number, slot:number, itemId:number, damage:number) {
this.entityId = entityId;
this.slot = slot;
this.itemId = itemId;
this.damage = damage;
}
public readData(reader:Reader) {
this.entityId = reader.readInt();
this.slot = reader.readShort();
this.itemId = reader.readShort();
this.damage = reader.readShort();
return this;
}
public writeData() {
return new Writer(10).writeUByte(this.packetId).writeInt(this.entityId).writeShort(this.slot).writeShort(this.itemId).writeShort(this.damage).toBuffer();
}
}

View file

@ -0,0 +1,26 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketHandshake implements IPacket {
public packetId:Packets = Packets.Handshake;
private username:string;
public constructor(username?:string) {
if (typeof(username) === "string") {
this.username = username;
} else {
this.username = "";
}
}
public readData(reader:Reader) {
this.username = reader.readString();
return this;
}
public writeData() {
return new Writer(5).writeUByte(this.packetId).writeString("-").toBuffer();
}
}

View file

@ -0,0 +1,8 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
export interface IPacket {
packetId: Packets,
readData: (reader:Reader) => IPacket,
writeData: () => Buffer|Promise<Buffer>
}

View file

@ -0,0 +1,16 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketKeepAlive implements IPacket {
public packetId = Packets.KeepAlive;
private static readonly KeepAliveBuffer:Buffer = new Writer(1).writeByte(Packets.KeepAlive).toBuffer();
public readData(reader:Reader) {
return this;
}
public writeData() {
return PacketKeepAlive.KeepAliveBuffer;
}
}

View file

@ -0,0 +1,38 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketLoginRequest implements IPacket {
public packetId = Packets.LoginRequest;
public protocolVersion:number;
public username:string;
public mapSeed:number;
public dimension:number;
public constructor(protocolVersion?:number, username?:string, mapSpeed?:number, dimension?:number) {
if (typeof(protocolVersion) === "number" && typeof(username) === "string" && typeof(mapSpeed) === "number" && typeof(dimension) === "number") {
this.protocolVersion = protocolVersion;
this.username = username;
this.mapSeed = mapSpeed;
this.dimension = dimension;
} else {
this.protocolVersion = Number.MIN_VALUE;
this.username = "";
this.mapSeed = Number.MIN_VALUE;
this.dimension = Number.MIN_VALUE;
}
}
public readData(reader:Reader) {
this.protocolVersion = reader.readInt();
this.username = reader.readString();
this.mapSeed = Number(reader.readLong());
this.dimension = reader.readByte();
return this;
}
public writeData() {
return new Writer(16 + (2 * this.username.length)).writeUByte(this.packetId).writeInt(this.protocolVersion).writeString(this.username).writeLong(this.mapSeed).writeByte(this.dimension).toBuffer();
}
}

View file

@ -0,0 +1,68 @@
import { deflate } from "zlib";
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
import { Chunk } from "../Chunk";
export class PacketMapChunk implements IPacket {
public packetId = Packets.MapChunk;
public x:number;
public y:number;
public z:number;
public sizeX:number;
public sizeY:number;
public sizeZ:number;
public chunk:Chunk;
public constructor(x:number, y:number, z:number, sizeX:number, sizeY:number, sizeZ:number, chunk:Chunk) {
this.x = x;
this.y = y;
this.z = z;
this.sizeX = sizeX;
this.sizeY = sizeY;
this.sizeZ = sizeZ;
this.chunk = chunk;
}
public readData(reader:Reader) {
return this;
}
public writeData() {
return new Promise<Buffer>((resolve, reject) => {
const blocks = new Writer(32768);
const metadata = new Writer(16384);
const lighting = new 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.writeUByte(this.chunk.getBlockId(x, y, z));
if (blockMeta) {
metadata.writeUByte(0);
// Light level 15 for 2 blocks (1111 1111)
lighting.writeUByte(0xff); // TODO: Lighting (Client seems to do it's own (when a block update happens) so it's not top priority)
lighting.writeUByte(0xff);
}
// Hack for nibble stuff
blockMeta = !blockMeta;
}
}
}
// Write meta and lighting data into block buffer for compression
blocks.writeBuffer(metadata.toBuffer()).writeBuffer(lighting.toBuffer());
deflate(blocks.toBuffer(), (err, data) => {
if (err) {
return reject(err);
}
resolve(new Writer(18).writeUByte(this.packetId).writeInt(this.x).writeShort(this.y).writeInt(this.z).writeUByte(this.sizeX).writeUByte(this.sizeY).writeUByte(this.sizeZ).writeInt(data.length).writeBuffer(data).toBuffer());
});
});
}
}

22
server/packets/Player.ts Normal file
View file

@ -0,0 +1,22 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketPlayer implements IPacket {
public packetId = Packets.Player;
public onGround:boolean;
public constructor(onGround:boolean = false) {
this.onGround = onGround;
}
public readData(reader:Reader) {
this.onGround = reader.readBool();
return this;
}
public writeData() {
return new Writer(2).writeUByte(this.packetId).writeBool(this.onGround).toBuffer();
}
}

View file

@ -0,0 +1,28 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketPlayerLook implements IPacket {
public packetId = Packets.Player;
public yaw:number;
public pitch:number;
public onGround:boolean;
public constructor(yaw:number, pitch:number, onGround:boolean = false) {
this.yaw = yaw;
this.pitch = pitch;
this.onGround = onGround;
}
public readData(reader:Reader) {
this.yaw = reader.readFloat();
this.pitch = reader.readFloat();
this.onGround = reader.readBool();
return this;
}
public writeData() {
return new Writer(10).writeUByte(this.packetId).writeFloat(this.yaw).writeFloat(this.pitch).writeBool(this.onGround).toBuffer();
}
}

View file

@ -0,0 +1,34 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketPlayerPosition implements IPacket {
public packetId = Packets.PlayerPosition;
public x:number;
public y:number;
public stance:number;
public z:number;
public onGround:boolean;
public constructor(x:number, y:number, stance:number, z:number, onGround:boolean = false) {
this.x = x;
this.y = y;
this.stance = stance;
this.z = z;
this.onGround = onGround;
}
public readData(reader:Reader) {
this.x = reader.readDouble();
this.y = reader.readDouble();
this.stance = reader.readDouble();
this.z = reader.readDouble();
this.onGround = reader.readBool();
return this;
}
public writeData() {
return new Writer(34).writeUByte(this.packetId).writeDouble(this.x).writeDouble(this.y).writeDouble(this.stance).writeDouble(this.z).writeBool(this.onGround).toBuffer();
}
}

View file

@ -0,0 +1,40 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketPlayerPositionLook implements IPacket {
public packetId = Packets.PlayerPosition;
public x:number;
public y:number;
public stance:number;
public z:number;
public yaw:number;
public pitch:number;
public onGround:boolean;
public constructor(x:number, y:number, stance:number, z:number, yaw:number, pitch:number, onGround:boolean = false) {
this.x = x;
this.y = y;
this.stance = stance;
this.z = z;
this.yaw = yaw;
this.pitch = pitch;
this.onGround = onGround;
}
public readData(reader:Reader) {
this.x = reader.readDouble();
this.y = reader.readDouble();
this.stance = reader.readDouble();
this.z = reader.readDouble();
this.yaw = reader.readFloat();
this.pitch = reader.readFloat();
this.onGround = reader.readBool();
return this;
}
public writeData() {
return new Writer(42).writeUByte(this.packetId).writeDouble(this.x).writeDouble(this.y).writeDouble(this.stance).writeDouble(this.z).writeFloat(this.yaw).writeFloat(this.pitch).writeBool(this.onGround).toBuffer();
}
}

View file

@ -0,0 +1,28 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketPreChunk implements IPacket {
public packetId = Packets.PreChunk;
public x:number;
public z:number;
public mode:boolean;
public constructor(x:number, z:number, mode:boolean) {
this.x = x;
this.z = z;
this.mode = mode;
}
public readData(reader:Reader) {
this.x = reader.readInt();
this.z = reader.readInt();
this.mode = reader.readBool();
return this;
}
public writeData() {
return new Writer(10).writeUByte(this.packetId).writeInt(this.x).writeInt(this.z).writeBool(this.mode).toBuffer();
}
}

22
server/packets/Respawn.ts Normal file
View file

@ -0,0 +1,22 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketRespawn implements IPacket {
public packetId = Packets.Respawn;
public health:number;
public constructor(health:number) {
this.health = health;
}
public readData(reader:Reader) {
this.health = reader.readShort();
return this;
}
public writeData() {
return new Writer(3).writeUByte(this.packetId).writeShort(this.health).toBuffer();
}
}

View file

@ -0,0 +1,28 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketSpawnPosition implements IPacket {
public packetId = Packets.SpawnPosition;
public x:number;
public y:number;
public z:number;
public constructor(x:number, y:number, z:number) {
this.x = x;
this.y = y;
this.z = z;
}
public readData(reader:Reader) {
this.x = reader.readInt();
this.y = reader.readInt();
this.z = reader.readInt();
return this;
}
public writeData() {
return new Writer(13).writeUByte(this.packetId).writeInt(this.x).writeInt(this.y).writeInt(this.z).toBuffer();
}
}

View file

@ -0,0 +1,22 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketTimeUpdate implements IPacket {
public packetId = Packets.TimeUpdate;
public time:number;
public constructor(time:number) {
this.time = time;
}
public readData(reader:Reader) {
this.time = Number(reader.readLong());
return this;
}
public writeData() {
return new Writer(9).writeUByte(this.packetId).writeLong(this.time).toBuffer();
}
}

View file

@ -0,0 +1,22 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketUpdateHealth implements IPacket {
public packetId = Packets.UpdateHealth;
public health:number;
public constructor(health:number) {
this.health = health;
}
public readData(reader:Reader) {
this.health = reader.readShort();
return this;
}
public writeData() {
return new Writer(3).writeUByte(this.packetId).writeShort(this.health).toBuffer();
}
}

View file

@ -0,0 +1,28 @@
import { Reader, Writer } from "../../bufferStuff";
import { Packets } from "../enums/Packets";
import { IPacket } from "./IPacket";
export class PacketUseEntity implements IPacket {
public packetId = Packets.UseEntity;
public userId:number;
public targetId:number;
public leftClick:boolean;
public constructor(userId:number, targetId:number, leftClick:boolean) {
this.userId = userId;
this.targetId = targetId;
this.leftClick = leftClick;
}
public readData(reader:Reader) {
this.userId = reader.readInt();
this.targetId = reader.readInt();
this.leftClick = reader.readBool();
return this;
}
public writeData() {
return new Writer(10).writeUByte(this.packetId).writeInt(this.userId).writeInt(this.targetId).writeBool(this.leftClick).toBuffer();
}
}

View file

@ -1,357 +0,0 @@
/*
==============- server.js -=============
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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 Block = require("./Blocks/Block.js");
const Socket = require("net").Socket;
let idPool = 1;
global.fromIDPool = function() {
const oldVal = idPool;
idPool++;
return oldVal;
}
let netUsers = {},
netUserKeys = Object.keys(netUsers);
global.getUserByKey = function(key) {
return netUsers[key];
}
global.sendToAllPlayers = function(buffer = Buffer.alloc(0)) {
for (let key of netUserKeys) {
const user = netUsers[key];
user.socket.write(buffer);
}
}
global.sendToAllPlayersButSelf = function(id, buffer = Buffer.alloc(0)) {
for (let key of netUserKeys) {
if (key == id) continue;
const user = netUsers[key];
user.socket.write(buffer);
}
}
function addUser(socket) {
let user = new User(global.fromIDPool(), socket);
user.entityRef = new EntityPlayer(user, 8.5, 65.5, 8.5);
netUsers[user.id] = user;
netUserKeys = Object.keys(netUsers);
return user;
}
function removeUser(id) {
delete netUsers[id];
netUserKeys = Object.keys(netUsers);
}
let config = {};
let entities = {};
let entityKeys = {};
global.chunkManager = new ChunkManager();
global.generatingChunks = false;
let tickInterval, tickCounter = BigInt(0), worldTime = 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(() => {
// Runs every sec
if (tickCounter % tickRate == 0) {
for (let key of netUserKeys) {
const user = netUsers[key];
user.socket.write(new PacketMappingTable[NamedPackets.KeepAlive]().writePacket());
if (user.loginFinished) user.socket.write(new PacketMappingTable[NamedPackets.TimeUpdate](BigInt(worldTime)).writePacket());
}
}
// Do chunk updates
// Don't update if chunk is generating
if (global.chunkManager.queuedBlockUpdates.getLength() > 0) {
let itemsToRemove = [];
// Do a max of 128 block 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];
// TODO: Remove this once infinite terrain is in :)
if (chunkUpdate[0] < -3 || chunkUpdate[0] > 3 || chunkUpdate[1] < -3 || chunkUpdate[1] > 3) {
itemsToRemove.push(chunkUpdateKey, false);
continue;
}
// If the chunk just plain doesn't exist (yet) skip this one
if (global.chunkManager.chunks[chunkUpdate[0]] == null) continue;
if (global.chunkManager.chunks[chunkUpdate[0]][chunkUpdate[1]] == null) continue;
global.chunkManager.chunks[chunkUpdate[0]][chunkUpdate[1]][chunkUpdate[2]][chunkUpdate[3]][chunkUpdate[4]][0] = chunkUpdate[5];
global.chunkManager.chunks[chunkUpdate[0]][chunkUpdate[1]][chunkUpdate[2]][chunkUpdate[3]][chunkUpdate[4]][1] = chunkUpdate[6];
const packet = new PacketMappingTable[NamedPackets.BlockChange](chunkUpdate[3] + (chunkUpdate[0] << 4), chunkUpdate[2], chunkUpdate[4] + (chunkUpdate[1] << 4), chunkUpdate[5], chunkUpdate[6]).writePacket();
for (let userKey of netUserKeys) {
const user = netUsers[userKey];
if (user.loginFinished) user.socket.write(packet);
}
itemsToRemove.push(chunkUpdateKey);
}
for (let item of itemsToRemove) {
global.chunkManager.queuedBlockUpdates.remove(item, false);
}
global.chunkManager.queuedBlockUpdates.regenerateIterableArray();
}
// Entity update!
for (let key of netUserKeys) {
const user = netUsers[key];
if (user.loginFinished) user.entityRef.onTick();
}
// Send queued chunks to users
for (let key of netUserKeys) {
const user = netUsers[key];
if (user.loginFinished) {
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++;
worldTime++;
}, 1000 / parseInt(tickRate.toString()));
for (let x = -3; x < 4; x++) {
for (let z = -3; z < 4; z++) {
global.chunkManager.createChunk(x, z);
}
}
}
module.exports.connection = async function(socket = new Socket) {
const thisUser = addUser(socket);
socket.on('data', function(chunk) {
const reader = new bufferStuff.Reader(chunk);
const packetID = reader.readByte();
switch(packetID) {
case NamedPackets.Disconnect:
removeUser(thisUser.id);
break;
case NamedPackets.KeepAlive:
break;
case NamedPackets.LoginRequest:
socket.write(new PacketMappingTable[NamedPackets.LoginRequest](reader.readInt(), reader.readString(), global.chunkManager.seed, 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());
}
}
// Place a layer of glass under the player so they don't fall n' die
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
socket.write(new PacketMappingTable[NamedPackets.BlockChange](x, 64, z, Block.glass.blockID, 0).writePacket());
}
}
socket.write(new PacketMappingTable[NamedPackets.Player](true).writePacket());
const joinMessage = new PacketMappingTable[NamedPackets.ChatMessage](`\u00A7e${thisUser.username} has joined the game`).writePacket();
for (let key of netUserKeys) {
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 user
global.sendToAllPlayersButSelf(thisUser.id, new PacketMappingTable[NamedPackets.NamedEntitySpawn](thisUser.id, thisUser.username, thisUser.entityRef.x, thisUser.entityRef.y, thisUser.entityRef.z, thisUser.entityRef.yaw, thisUser.entityRef.pitch, 0).writePacket());
// send all online users to this user
for (let key of netUserKeys) {
if (key == thisUser.id) continue;
const user = netUsers[key];
socket.write(new PacketMappingTable[NamedPackets.NamedEntitySpawn](user.id, user.username, user.entityRef.x, user.entityRef.y, user.entityRef.z, user.entityRef.yaw, user.entityRef.pitch, 0).writePacket());
}
break;
case NamedPackets.Handshake:
thisUser.username = reader.readString();
socket.write(new PacketMappingTable[NamedPackets.Handshake](thisUser.username).writePacket());
break;
case NamedPackets.ChatMessage:
const message = reader.readString();
// Hacky commands until I made a command system
if (message.startsWith("/")) {
const command = message.substring(1, message.length).split(" ");
console.log(command);
if (command[0] == "time") {
if (command.length < 2) {
} else if (command[1] == "set") {
if (command.length < 3) {
} else {
switch (command[2]) {
case "day":
worldTime = (24000 * (worldTime / 24000));
break;
case "noon":
worldTime = (24000 * (worldTime / 24000)) + 6000;
break;
case "sunset":
worldTime = (24000 * (worldTime / 24000)) + 12000;
break;
case "midnight":
worldTime = (24000 * (worldTime / 24000)) + 18000;
break;
}
}
}
}
} else {
// Send player's message to all players
const cachedPacket = new PacketMappingTable[NamedPackets.ChatMessage](`<${thisUser.username}> ${message}`).writePacket();
for (let key of netUserKeys) {
netUsers[key].socket.write(cachedPacket);
}
}
break;
case NamedPackets.PlayerLook:
thisUser.entityRef.yaw = reader.readFloat() % 360 % -360;
thisUser.entityRef.pitch = reader.readFloat() % 360 % -360;
break;
case NamedPackets.PlayerPosition:
thisUser.entityRef.x = reader.readDouble();
thisUser.entityRef.y = reader.readDouble();
reader.readDouble(); // stance
thisUser.entityRef.z = reader.readDouble();
break;
case NamedPackets.PlayerPositionAndLook:
thisUser.entityRef.x = reader.readDouble();
thisUser.entityRef.y = reader.readDouble();
reader.readDouble(); // stance
thisUser.entityRef.z = reader.readDouble();
thisUser.entityRef.yaw = reader.readFloat() % 360 % -360;
thisUser.entityRef.pitch = reader.readFloat() % 360 % -360;
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(x, y, z, 0);
}
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(x + xOff, y + yOff, z + zOff, block);
break;
case NamedPackets.Player:
break;
default:
console.log(toHexValue(packetID));
break;
}
});
socket.on('end', function() {
console.log("Connection closed");
removeUser(thisUser.id);
});
socket.on('error', function(err) {
console.log("Connection error!");
removeUser(thisUser.id);
});
}
function toHexValue(val = 0x00) {
if (val < 16) return `0x0${val.toString(16).toUpperCase()}`;
else return `0x${val.toString(16).toUpperCase()}`;
}

View file

@ -1,25 +0,0 @@
/*
===============- user.js -==============
Created by Holly (tgpethan) (c) 2021
Licenced under MIT
========================================
*/
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.entityRef = null;
this.chunksToSend = new funkyArray();
}
}

12
tsconfig.json Normal file
View file

@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"target": "es6",
"esModuleInterop": true,
"resolveJsonModule": true,
"rootDir": "./",
"outDir": "./build",
"strict": true
}
}

15
webpack.config.js Normal file
View file

@ -0,0 +1,15 @@
const path = require('path');
const nodeExternals = require('webpack-node-externals');
module.exports = {
target: 'node',
externals: [ nodeExternals() ],
entry: './build/index.js',
output: {
path: path.join(__dirname, 'bundle'),
filename: 'MCBS.js',
},
optimization: {
minimize: true,
},
};