Compare commits

..

No commits in common. "typescript" and "master" have entirely different histories.

150 changed files with 2535 additions and 9398 deletions

2
.gitattributes vendored Normal file
View file

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

View file

@ -1,37 +0,0 @@
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
name: Node.js Build
on:
push:
branches: [ "typescript" ]
pull_request:
branches: [ "typescript" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
# Don't run updateCheck for now
#- run: npm run dev:updateCheck
- run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v3
with:
name: 'mc-beta-server'
path: 'build/index.min.js'

114
.gitignore vendored
View file

@ -1,7 +1,109 @@
# Custom stuff
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/
build/
bundle/
world/
logs/
combined.ts
config.json
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

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 Holly Stubbs
Copyright (c) 2021 holly
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -1,77 +1,28 @@
# mc-beta-server [![CodeFactor](https://www.codefactor.io/repository/github/tgpholly/mc-beta-server/badge/typescript)](https://www.codefactor.io/repository/github/tgpholly/mc-beta-server/overview/typescript) [![Node.js CI](https://github.com/tgpholly/mc-beta-server/actions/workflows/node.js.yml/badge.svg?branch=typescript)](https://github.com/tgpholly/mc-beta-server/actions/workflows/node.js.yml)
# mc-beta-server [![CodeFactor](https://www.codefactor.io/repository/github/tgpethan/mc-beta-server/badge)](https://www.codefactor.io/repository/github/tgpethan/mc-beta-server)
me trying to understand minecraft beta's protocol
- [Running the server](#running-the-server)
- ~~[Running from a release](#running-from-a-release)~~
- [Building yourself](#building-yourself)
- [Setting up for development](#setting-up-for-development)
<hr>
<img src="https://eusv.net/images/mc-beta-server-readme-1-overworld.webp" alt="Minecraft landscape with trees, caves, flowers, shrubs and an ocean.">
<img src="https://eusv.net/images/mc-beta-server-readme-1-nether.webp" alt="WIP Minecraft Nether with very simple terrain.">
**Goals:**
- Make this as fast as possible on a single thread.
- Only use thread pools if absolutely neccesary.
- Build the server in a way where it should be easy to multithread it.
**Implemented:**
- Basic flat terrain generation
- Basic (top to bottom, no spread) Lighting
- "Decent" terrain generation using OpenSimplex Noise
- Multi-World support
- Chunk management (loading / unloading)
- Cross chunk structure generation (trees, buildings, etc...)
- World/Chunk saving to disk
- Terrain sending
- Block placement
- Block breaking
- Player Inventory
**WIP:**
- Entities:
- Players
- Dimensions:
- Nether
- Inventories (containers, etc...)
**WIP:**
- Terrain generation using perlin noise
- Cross chunk structure generation (trees, buildings, etc...)
**To Implement:**
- Block placement
- Entities:
- Items
- Items/Blocks
- Animals
- Mobs
- Saving inventories
- Inventories
- Terrain saving to disk
- Sleeping in beds
- Tile entities
- Probably a bunch more things that i'm forgetting
<hr>
## Running the server
**Heads Up!**
This server is under pretty heavy development with most likley breaking changes, I will try my best to keep compatibility in the save formats (or provide migration paths) with updates but cannot guarentee it.
To use the server either grab a [release](https://github.com/tgpholly/mc-beta-server/releases/latest) or build it yourself.
### Running from a release
~~When using a release all you need to do is run the executable you get from the download.~~
### Building yourself
To build the server yourself clone the repo and run
```
npm i
npm run build
```
This will automatically build the server and pack it into a single js file, you'll find your resulting js file in the `./build` folder.
## Setting up for development
To run the server simply clone the repo, and run:
```
npm i
```
Copy the [`config.example.json`](https://github.com/tgpholly/mc-beta-server/blob/typescript/config.example.json) to `config.json` and edit how you want it, then run:
```
npm run dev:run
```
You are now running a server locally!
When running the server with `npm run dev:run` it is running with nodemon, this means that the server will auto restart as you edit.
**Long Term:**
- Optimise the shit out of this

View file

@ -1,6 +0,0 @@
{
"port": 25565,
"maxPlayers": 20,
"saveCompression": "DEFLATE",
"worldName": "world"
}

4
config.json Normal file
View file

@ -0,0 +1,4 @@
{
"port": 25565,
"worldThreads": 4
}

View file

@ -1,9 +0,0 @@
import SaveCompressionType from "./server/enums/SaveCompressionType";
export default interface Config {
port: number,
maxPlayers: number,
seed: number|string,
worldName: string,
saveCompression: SaveCompressionType
}

View file

@ -1,195 +0,0 @@
// This is free and unencumbered software released into the public domain
import shuffleSeed from "./shuffle_seed";
const NORM_2D = 1.0 / 47.0;
const SQUISH_2D = (Math.sqrt(2 + 1) - 1) / 2;
const STRETCH_2D = (1 / Math.sqrt(2 + 1) - 1) / 2;
export type Noise2D = (x: number, y: number) => number;
interface Contribution2D {
dx: number;
dy: number;
next?: Contribution2D;
xsb: number;
ysb: number;
}
function contribution2D(
multiplier: number,
xsb: number,
ysb: number,
): Contribution2D {
return {
dx: -xsb - multiplier * SQUISH_2D,
dy: -ysb - multiplier * SQUISH_2D,
xsb,
ysb,
};
}
export function makeNoise2D(clientSeed: number): Noise2D {
const contributions: Contribution2D[] = [];
for (let i = 0; i < p2D.length; i += 4) {
const baseSet = base2D[p2D[i]];
let previous: Contribution2D | null = null;
let current: Contribution2D | null = null;
for (let k = 0; k < baseSet.length; k += 3) {
current = contribution2D(baseSet[k], baseSet[k + 1], baseSet[k + 2]);
if (previous === null) contributions[i / 4] = current;
else previous.next = current;
previous = current;
}
current!.next = contribution2D(p2D[i + 1], p2D[i + 2], p2D[i + 3]);
}
const lookup: Contribution2D[] = [];
for (let i = 0; i < lookupPairs2D.length; i += 2) {
lookup[lookupPairs2D[i]] = contributions[lookupPairs2D[i + 1]];
}
const perm = new Uint8Array(256);
const perm2D = new Uint8Array(256);
const source = new Uint8Array(256);
for (let i = 0; i < 256; i++) source[i] = i;
let seed = new Uint32Array(1);
seed[0] = clientSeed;
seed = shuffleSeed(shuffleSeed(shuffleSeed(seed)));
for (let i = 255; i >= 0; i--) {
seed = shuffleSeed(seed);
const r = new Uint32Array(1);
r[0] = (seed[0] + 31) % (i + 1);
if (r[0] < 0) r[0] += i + 1;
perm[i] = source[r[0]];
perm2D[i] = perm[i] & 0x0e;
source[r[0]] = source[i];
}
return (x: number, y: number): number => {
const stretchOffset = (x + y) * STRETCH_2D;
const xs = x + stretchOffset;
const ys = y + stretchOffset;
const xsb = Math.floor(xs);
const ysb = Math.floor(ys);
const squishOffset = (xsb + ysb) * SQUISH_2D;
const dx0 = x - (xsb + squishOffset);
const dy0 = y - (ysb + squishOffset);
const xins = xs - xsb;
const yins = ys - ysb;
const inSum = xins + yins;
const hash = (xins - yins + 1) |
(inSum << 1) |
((inSum + yins) << 2) |
((inSum + xins) << 4);
let value = 0;
for (
let c: Contribution2D | undefined = lookup[hash];
c !== undefined;
c = c.next
) {
const dx = dx0 + c.dx;
const dy = dy0 + c.dy;
const attn = 2 - dx * dx - dy * dy;
if (attn > 0) {
const px = xsb + c.xsb;
const py = ysb + c.ysb;
const indexPartA = perm[px & 0xff];
const index = perm2D[(indexPartA + py) & 0xff];
const valuePart = gradients2D[index] * dx + gradients2D[index + 1] * dy;
value += attn * attn * attn * attn * valuePart;
}
}
return value * NORM_2D;
};
}
const base2D = [
[1, 1, 0, 1, 0, 1, 0, 0, 0],
[1, 1, 0, 1, 0, 1, 2, 1, 1],
];
const gradients2D = [
5,
2,
2,
5,
-5,
2,
-2,
5,
5,
-2,
2,
-5,
-5,
-2,
-2,
-5,
];
const lookupPairs2D = [
0,
1,
1,
0,
4,
1,
17,
0,
20,
2,
21,
2,
22,
5,
23,
5,
26,
4,
39,
3,
42,
4,
43,
3,
];
const p2D = [
0,
0,
1,
-1,
0,
0,
-1,
1,
0,
2,
1,
1,
1,
2,
2,
0,
1,
2,
0,
2,
1,
0,
0,
0,
];

View file

@ -1,596 +0,0 @@
// This is free and unencumbered software released into the public domain
import shuffleSeed from "./shuffle_seed";
const NORM_3D = 1.0 / 103.0;
const SQUISH_3D = (Math.sqrt(3 + 1) - 1) / 3;
const STRETCH_3D = (1 / Math.sqrt(3 + 1) - 1) / 3;
export type Noise3D = (x: number, y: number, z: number) => number;
interface Contribution3D {
dx: number;
dy: number;
dz: number;
next?: Contribution3D;
xsb: number;
ysb: number;
zsb: number;
}
function contribution3D(
multiplier: number,
xsb: number,
ysb: number,
zsb: number,
): Contribution3D {
return {
dx: -xsb - multiplier * SQUISH_3D,
dy: -ysb - multiplier * SQUISH_3D,
dz: -zsb - multiplier * SQUISH_3D,
xsb,
ysb,
zsb,
};
}
export function makeNoise3D(clientSeed: number): Noise3D {
const contributions: Contribution3D[] = [];
for (let i = 0; i < p3D.length; i += 9) {
const baseSet = base3D[p3D[i]];
let previous: Contribution3D | null = null;
let current: Contribution3D | null = null;
for (let k = 0; k < baseSet.length; k += 4) {
current = contribution3D(
baseSet[k],
baseSet[k + 1],
baseSet[k + 2],
baseSet[k + 3],
);
if (previous === null) contributions[i / 9] = current;
else previous.next = current;
previous = current;
}
current!.next = contribution3D(
p3D[i + 1],
p3D[i + 2],
p3D[i + 3],
p3D[i + 4],
);
current!.next.next = contribution3D(
p3D[i + 5],
p3D[i + 6],
p3D[i + 7],
p3D[i + 8],
);
}
const lookup: Contribution3D[] = [];
for (let i = 0; i < lookupPairs3D.length; i += 2) {
lookup[lookupPairs3D[i]] = contributions[lookupPairs3D[i + 1]];
}
const perm = new Uint8Array(256);
const perm3D = new Uint8Array(256);
const source = new Uint8Array(256);
for (let i = 0; i < 256; i++) source[i] = i;
let seed = new Uint32Array(1);
seed[0] = clientSeed;
seed = shuffleSeed(shuffleSeed(shuffleSeed(seed)));
for (let i = 255; i >= 0; i--) {
seed = shuffleSeed(seed);
const r = new Uint32Array(1);
r[0] = (seed[0] + 31) % (i + 1);
if (r[0] < 0) r[0] += i + 1;
perm[i] = source[r[0]];
perm3D[i] = (perm[i] % 24) * 3;
source[r[0]] = source[i];
}
return (x: number, y: number, z: number): number => {
const stretchOffset = (x + y + z) * STRETCH_3D;
const xs = x + stretchOffset;
const ys = y + stretchOffset;
const zs = z + stretchOffset;
const xsb = Math.floor(xs);
const ysb = Math.floor(ys);
const zsb = Math.floor(zs);
const squishOffset = (xsb + ysb + zsb) * SQUISH_3D;
const dx0 = x - (xsb + squishOffset);
const dy0 = y - (ysb + squishOffset);
const dz0 = z - (zsb + squishOffset);
const xins = xs - xsb;
const yins = ys - ysb;
const zins = zs - zsb;
const inSum = xins + yins + zins;
const hash = (yins - zins + 1) |
((xins - yins + 1) << 1) |
((xins - zins + 1) << 2) |
(inSum << 3) |
((inSum + zins) << 5) |
((inSum + yins) << 7) |
((inSum + xins) << 9);
let value = 0;
for (
let c: Contribution3D | undefined = lookup[hash];
c !== undefined;
c = c.next
) {
const dx = dx0 + c.dx;
const dy = dy0 + c.dy;
const dz = dz0 + c.dz;
const attn = 2 - dx * dx - dy * dy - dz * dz;
if (attn > 0) {
const px = xsb + c.xsb;
const py = ysb + c.ysb;
const pz = zsb + c.zsb;
const indexPartA = perm[px & 0xff];
const indexPartB = perm[(indexPartA + py) & 0xff];
const index = perm3D[(indexPartB + pz) & 0xff];
const valuePart = gradients3D[index] * dx +
gradients3D[index + 1] * dy +
gradients3D[index + 2] * dz;
value += attn * attn * attn * attn * valuePart;
}
}
return value * NORM_3D;
};
}
const base3D = [
[0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1],
[2, 1, 1, 0, 2, 1, 0, 1, 2, 0, 1, 1, 3, 1, 1, 1],
[1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 2, 1, 1, 0, 2, 1, 0, 1, 2, 0, 1, 1],
];
const gradients3D = [
-11,
4,
4,
-4,
11,
4,
-4,
4,
11,
11,
4,
4,
4,
11,
4,
4,
4,
11,
-11,
-4,
4,
-4,
-11,
4,
-4,
-4,
11,
11,
-4,
4,
4,
-11,
4,
4,
-4,
11,
-11,
4,
-4,
-4,
11,
-4,
-4,
4,
-11,
11,
4,
-4,
4,
11,
-4,
4,
4,
-11,
-11,
-4,
-4,
-4,
-11,
-4,
-4,
-4,
-11,
11,
-4,
-4,
4,
-11,
-4,
4,
-4,
-11,
];
const lookupPairs3D = [
0,
2,
1,
1,
2,
2,
5,
1,
6,
0,
7,
0,
32,
2,
34,
2,
129,
1,
133,
1,
160,
5,
161,
5,
518,
0,
519,
0,
546,
4,
550,
4,
645,
3,
647,
3,
672,
5,
673,
5,
674,
4,
677,
3,
678,
4,
679,
3,
680,
13,
681,
13,
682,
12,
685,
14,
686,
12,
687,
14,
712,
20,
714,
18,
809,
21,
813,
23,
840,
20,
841,
21,
1198,
19,
1199,
22,
1226,
18,
1230,
19,
1325,
23,
1327,
22,
1352,
15,
1353,
17,
1354,
15,
1357,
17,
1358,
16,
1359,
16,
1360,
11,
1361,
10,
1362,
11,
1365,
10,
1366,
9,
1367,
9,
1392,
11,
1394,
11,
1489,
10,
1493,
10,
1520,
8,
1521,
8,
1878,
9,
1879,
9,
1906,
7,
1910,
7,
2005,
6,
2007,
6,
2032,
8,
2033,
8,
2034,
7,
2037,
6,
2038,
7,
2039,
6,
];
const p3D = [
0,
0,
1,
-1,
0,
0,
1,
0,
-1,
0,
0,
-1,
1,
0,
0,
0,
1,
-1,
0,
0,
-1,
0,
1,
0,
0,
-1,
1,
0,
2,
1,
1,
0,
1,
1,
1,
-1,
0,
2,
1,
0,
1,
1,
1,
-1,
1,
0,
2,
0,
1,
1,
1,
-1,
1,
1,
1,
3,
2,
1,
0,
3,
1,
2,
0,
1,
3,
2,
0,
1,
3,
1,
0,
2,
1,
3,
0,
2,
1,
3,
0,
1,
2,
1,
1,
1,
0,
0,
2,
2,
0,
0,
1,
1,
0,
1,
0,
2,
0,
2,
0,
1,
1,
0,
0,
1,
2,
0,
0,
2,
2,
0,
0,
0,
0,
1,
1,
-1,
1,
2,
0,
0,
0,
0,
1,
-1,
1,
1,
2,
0,
0,
0,
0,
1,
1,
1,
-1,
2,
3,
1,
1,
1,
2,
0,
0,
2,
2,
3,
1,
1,
1,
2,
2,
0,
0,
2,
3,
1,
1,
1,
2,
0,
2,
0,
2,
1,
1,
-1,
1,
2,
0,
0,
2,
2,
1,
1,
-1,
1,
2,
2,
0,
0,
2,
1,
-1,
1,
1,
2,
0,
0,
2,
2,
1,
-1,
1,
1,
2,
0,
2,
0,
2,
1,
1,
1,
-1,
2,
2,
0,
0,
2,
1,
1,
1,
-1,
2,
0,
2,
0,
];

View file

@ -1,8 +0,0 @@
// This is free and unencumbered software released into the public domain
export default function shuffleSeed(seed: Uint32Array): Uint32Array {
const newSeed = new Uint32Array(1);
newSeed[0] = seed[0] * 1664525 + 1013904223;
return newSeed;
}

15
index.js Normal file
View file

@ -0,0 +1,15 @@
/*
==============- 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);

View file

@ -1,13 +0,0 @@
import { readFileSync } from "fs";
import { Console } from "hsconsole";
import Config from "./config";
import MinecraftServer from "./server/MinecraftServer";
import SaveCompressionType from "./server/enums/SaveCompressionType";
const tempConfig = JSON.parse(readFileSync("./config.json").toString());
tempConfig.saveCompression = SaveCompressionType[tempConfig.saveCompression];
const config:Config = tempConfig as Config;
Console.customHeader(`MC Beta Server started at ${new Date()}`);
new MinecraftServer(config);

View file

@ -1,35 +0,0 @@
export default class NibbleArray {
private array:Uint8Array;
public constructor(size:number|ArrayBuffer|Uint8Array) {
if (size instanceof ArrayBuffer) {
this.array = new Uint8Array(size);
} else if (size instanceof Uint8Array) {
this.array = new Uint8Array(size);
} else {
this.array = new Uint8Array(size >> 1);
}
}
public get(index:number) {
const arrayIndex = index >> 1;
if ((index & 1) === 0) {
return this.array[arrayIndex] & 0xf;
} else {
return this.array[arrayIndex] >> 4 & 0xf;
}
}
public set(index:number, value:number) {
const arrayIndex = index >> 1;
if ((index & 1) === 0) {
this.array[arrayIndex] = this.array[arrayIndex] & 0xf0 | value & 0xf;
} else {
this.array[arrayIndex] = this.array[arrayIndex] & 0xf | (value & 0xf) << 4;
}
}
public toBuffer() {
return Buffer.from(this.array);
}
}

2773
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,41 +2,20 @@
"name": "mc-beta-server",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"dev:updateCheck": "check-outdated",
"dev:run": "nodemon --watch './**/*.ts' index.ts",
"build": "npm-run-all build:*",
"build:build": "ncc build index.ts -o build",
"build:mangle": "ts-node ./tooling/mangle.ts",
"build:cleanup": "ts-node ./tooling/cleanup.ts",
"_clean": "tsc --build --clean"
},
"main": "index.js",
"scripts": {},
"repository": {
"type": "git",
"url": "git+https://github.com/tgpholly/mc-beta-server.git"
"url": "git+https://github.com/tgpethan/mc-beta-server.git"
},
"keywords": [],
"author": "tgpholly",
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/tgpholly/mc-beta-server/issues"
"url": "https://github.com/tgpethan/mc-beta-server/issues"
},
"homepage": "https://github.com/tgpholly/mc-beta-server#readme",
"homepage": "https://github.com/tgpethan/mc-beta-server#readme",
"dependencies": {
"bufferstuff": "^1.4.2",
"funky-array": "^1.0.0",
"hsconsole": "^1.0.2"
},
"devDependencies": {
"@types/node": "^20.8.10",
"@vercel/ncc": "^0.38.1",
"check-outdated": "^2.12.0",
"nodemon": "^3.0.1",
"npm-run-all": "^4.1.5",
"terser": "^5.24.0",
"ts-loader": "^9.5.0",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
"net": "^1.0.2"
}
}

View file

@ -1,177 +0,0 @@
import FunkyArray from "funky-array";
import Vec3 from "./Vec3";
// Based on this MDN article:
// https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection
export default class AABB {
private static readonly aabbPool:FunkyArray<string, AABB> = new FunkyArray<string, AABB>();
public readonly aabbPoolString:string;
public readonly pooled:boolean;
public initMin:Vec3;
public initMax:Vec3;
public prevMin:Vec3;
public prevMax:Vec3;
public min:Vec3;
public max:Vec3;
public constructor(minXOrMin:Vec3 | number, minYOrMax:Vec3 | number, minZ?:number, maxX?:number, maxY?:number, maxZ?:number, pooled:boolean = false) {
if (minXOrMin instanceof Vec3 && minYOrMax instanceof Vec3) {
this.min = minXOrMin;
this.max = minYOrMax;
} else if (typeof(minXOrMin) === "number" && typeof(minYOrMax) === "number" && typeof(minZ) === "number" && typeof(maxX) === "number" && typeof(maxY) === "number" && typeof(maxZ) === "number") {
this.min = new Vec3(minXOrMin, minYOrMax, minZ);
this.max = new Vec3(maxX, maxY, maxZ);
} else {
throw new Error("Invalid input parameters: AABB must be supplied with either two Vec3 with the min and max bounds or the raw bounds.");
}
this.initMin = new Vec3(this.min);
this.initMax = new Vec3(this.max);
this.prevMin = new Vec3(this.min);
this.prevMax = new Vec3(this.min);
this.pooled = pooled;
this.aabbPoolString = AABB.createAABBPoolString(this.min.x, this.min.y, this.min.z, this.max.x, this.max.y, this.max.z);
if (!AABB.aabbPool.has(this.aabbPoolString)) {
AABB.aabbPool.set(this.aabbPoolString, this);
}
}
public static createAABBPoolString(minX:number, minY:number, minZ:number, maxX:number, maxY:number, maxZ:number) {
return `m${minX}c${minY}a${minZ}a${maxX}b${maxY}b${maxZ}`;
}
public static getAABB(minX:number, minY:number, minZ:number, maxX:number, maxY:number, maxZ:number) {
const aabbPoolString = this.createAABBPoolString(minX, minY, minZ, maxX, maxY, maxZ);
if (AABB.aabbPool.has(aabbPoolString)) {
const aabb = AABB.aabbPool.get(aabbPoolString);
if (aabb === undefined) {
throw new Error(`Pooled AABB was ${typeof(aabb)}! This should be impossible.`);
}
return aabb;
}
return new AABB(minX, minY, minZ, maxX, maxY, maxZ);
}
public static intersects(a:AABB, b:AABB) {
return a.min.x <= b.max.x && a.max.x >= b.min.x && a.min.y <= b.max.y && a.max.y >= b.min.y && a.min.z <= b.max.z && a.max.z >= b.min.z;
}
public intersects(aabb:AABB) {
return this.min.x <= aabb.max.x && this.max.x >= aabb.min.x && this.min.y <= aabb.max.y && this.max.y >= aabb.min.y && this.min.z <= aabb.max.z && this.max.z >= aabb.min.z;
}
public static intersectionY(a: AABB, b: AABB) {
const minY = Math.max(a.min.y, b.min.y);
const maxY = Math.min(a.max.y, b.max.y);
return minY <= maxY ? maxY - minY : 0;
}
public intersectionY(aabb: AABB) {
const minY = Math.max(this.min.y, aabb.min.y);
const maxY = Math.min(this.max.y, aabb.max.y);
return minY <= maxY ? maxY - minY : 0;
}
public static intersectionX(a: AABB, b: AABB) {
const minX = Math.max(a.min.x, b.min.x);
const maxX = Math.min(a.max.x, b.max.x);
return minX <= maxX ? maxX - minX : 0;
}
public intersectionX(aabb: AABB) {
const minX = Math.max(this.min.x, aabb.min.x);
const maxX = Math.min(this.max.x, aabb.max.x);
return minX <= maxX ? maxX - minX : 0;
}
public static intersectionZ(a: AABB, b: AABB) {
const minZ = Math.max(a.min.z, b.min.z);
const maxZ = Math.min(a.max.z, b.max.z);
return minZ <= maxZ ? maxZ - minZ : 0;
}
public intersectionZ(aabb: AABB) {
const minZ = Math.max(this.min.z, aabb.min.z);
const maxZ = Math.min(this.max.z, aabb.max.z);
return minZ <= maxZ ? maxZ - minZ : 0;
}
public static copy(aabb:AABB) {
const newAABB = new AABB(new Vec3(aabb.min), new Vec3(aabb.max));
newAABB.min.set(aabb.min);
newAABB.max.set(aabb.max);
return newAABB;
}
public copy() {
const newAABB = new AABB(new Vec3(this.min), new Vec3(this.max));
newAABB.min.set(this.min);
newAABB.max.set(this.max);
return newAABB;
}
// Can only revert one step
public revert() {
this.min.set(this.prevMin);
this.max.set(this.prevMax);
}
public set(aabb:AABB) {
this.prevMin.set(this.min);
this.prevMax.set(this.max);
this.min.set(aabb.min);
this.max.set(aabb.min);
}
public move(xOrVec3:Vec3 | number, y?:number, z?:number) {
if (this.pooled) {
throw new Error(`Attempted to move a pooled AABB. This is not allowed!`);
}
this.prevMin.set(this.min);
this.prevMax.set(this.max);
this.min.set(this.initMin);
this.max.set(this.initMax);
if (xOrVec3 instanceof Vec3) {
//this.pos.set(xOrVec3);
this.min.add(xOrVec3);
this.max.add(xOrVec3);
} else if (typeof(xOrVec3) === "number" && typeof(y) === "number" && typeof(z) === "number") {
//this.pos.set(xOrVec3, y, z);
this.min.add(xOrVec3, y, z);
this.max.add(xOrVec3, y, z);
}
}
public offset(xOrVec3:Vec3 | number, y?:number, z?:number) {
if (this.pooled) {
throw new Error(`Attempted to offset a pooled AABB. This is not allowed!`);
}
this.prevMin.set(this.min);
this.prevMax.set(this.max);
if (xOrVec3 instanceof Vec3) {
this.min.add(xOrVec3);
this.max.add(xOrVec3);
} else if (typeof(xOrVec3) === "number" && typeof(y) === "number" && typeof(z) === "number") {
this.min.add(xOrVec3, y, z);
this.max.add(xOrVec3, y, z);
}
}
}

86
server/Blocks/Block.js Normal file
View file

@ -0,0 +1,86 @@
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;

View file

@ -1,190 +0,0 @@
import Block from "./blocks/Block";
import FunkyArray from "funky-array";
import NibbleArray from "../nibbleArray";
import Player from "./entities/Player";
import QueuedBlockUpdate from "./queuedUpdateTypes/BlockUpdate";
import World from "./World";
export default class Chunk {
private readonly MAX_HEIGHT:number = 128;
private readonly FULLBRIGHT = false;
public readonly world:World;
public readonly x:number;
public readonly z:number;
public readonly playersInChunk:FunkyArray<number, Player>;
public savingToDisk:boolean = false;
public forceLoaded:boolean = false;
private blocks:Uint8Array;
private metadata:NibbleArray;
public skyLight:NibbleArray;
public blockLight:NibbleArray;
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, generateOrBlockData?:boolean|Uint8Array, metadata?:Uint8Array, blockLight?:Uint8Array, skyLight?:Uint8Array) {
this.world = world;
this.x = x;
this.z = z;
this.playersInChunk = new FunkyArray<number, Player>();
if (generateOrBlockData instanceof Uint8Array && metadata instanceof Uint8Array && blockLight instanceof Uint8Array && skyLight instanceof Uint8Array) {
this.blocks = new Uint8Array(generateOrBlockData);
this.metadata = new NibbleArray(metadata);
this.skyLight = new NibbleArray(blockLight);
this.blockLight = new NibbleArray(skyLight);
} else if (generateOrBlockData instanceof Uint8Array && metadata instanceof Uint8Array && !(blockLight instanceof Uint8Array) && !(skyLight instanceof Uint8Array)) {
this.blocks = new Uint8Array(generateOrBlockData);
this.metadata = new NibbleArray(metadata);
this.skyLight = new NibbleArray(16 * 16 * this.MAX_HEIGHT);
this.blockLight = new NibbleArray(16 * 16 * this.MAX_HEIGHT);
this.calculateLighting();
} else {
this.blocks = new Uint8Array(16 * 16 * this.MAX_HEIGHT);
this.metadata = new NibbleArray(16 * 16 * this.MAX_HEIGHT);
this.skyLight = new NibbleArray(16 * 16 * this.MAX_HEIGHT);
this.blockLight = new NibbleArray(16 * 16 * this.MAX_HEIGHT);
if (typeof(generateOrBlockData) === "boolean" && generateOrBlockData) {
this.world.generator.generate(this);
this.calculateLighting();
}
}
}
public getTopBlockY(x:number, z:number) {
let castY = this.MAX_HEIGHT;
while (castY-- > 0) {
if (this.getBlockId(x >>> 0, castY, z >>> 0) !== 0) {
break;
}
}
return castY;
}
public calculateLighting() {
if (this.FULLBRIGHT) {
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
for (let y = this.MAX_HEIGHT - 1; y > 0; y--) {
this.setBlockLight(15, x, y, z);
this.setSkyLight(15, x, y, z);
}
}
}
return;
}
let blockId = 0;
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
let colLight = 255;
for (let y = this.MAX_HEIGHT - 1; y > 0; y--) {
blockId = this.getBlockId(x, y, z);
if (blockId == 0) {
if (colLight <= 0) {
this.setBlockLight(0, x, y, z);
this.setSkyLight(0, x, y, z);
} else {
this.setBlockLight(Math.round((colLight / 255) * 15), x, y, z);
this.setSkyLight(Math.round((colLight / 255) * 15), x, y, z);
}
} else {
if (colLight <= 0) {
this.setBlockLight(0, x, y, z);
} else {
this.setBlockLight(Math.round((colLight / 255) * 15), x, y, z);
colLight -= (255 - Block.blocks[blockId].lightPassage);
}
}
}
}
}
}
public queueBlockUpdateForOuterChunkBlock(blockId:number, metadata:number, x:number, y:number, z:number) {
const cPair = Chunk.CreateCoordPair(this.x + (x >> 4), this.z + (z >> 4));
if (this.world.chunks.keys.includes(cPair)) {
this.world.queuedUpdates.push(new QueuedBlockUpdate(cPair, x & 0xf, y, z & 0xf, blockId, metadata));
} else {
this.world.queuedChunkBlocks.push(new QueuedBlockUpdate(cPair, x & 0xf, y, z & 0xf, blockId, metadata));
}
}
public setBlock(blockId:number, x:number, y:number, z:number) {
if (x < 0 || x > 15 || y < 0 || y > 127 || z < 0 || z > 15) {
this.queueBlockUpdateForOuterChunkBlock(blockId, 0, x, y, z);
return;
}
this.blocks[x << 11 | z << 7 | y] = blockId;
}
public setBlockMetadata(metadata:number, x:number, y:number, z:number) {
const index = x << 11 | z << 7 | y;
if (x < 0 || x > 15 || y < 0 || y > 127 || z < 0 || z > 15) {
this.queueBlockUpdateForOuterChunkBlock(this.blocks[index], metadata, x, y, z);
return;
}
this.metadata.set(index, metadata);
}
public setBlockWithMetadata(blockId:number, metadata:number, x:number, y:number, z:number) {
if (x < 0 || x > 15 || y < 0 || y > 127 || z < 0 || z > 15) {
this.queueBlockUpdateForOuterChunkBlock(blockId, metadata, x, y, z);
return;
}
x = x << 11 | z << 7 | y;
this.blocks[x] = blockId;
this.metadata.set(x, metadata);
}
public getBlockId(x:number, y:number, z:number) {
return this.blocks[x << 11 | z << 7 | y];
}
public getBlockMetadata(x:number, y:number, z:number) {
return this.metadata.get(x << 11 | z << 7 | y);
}
public getBlockLight(x:number, y:number, z:number) {
return this.blockLight.get(x << 11 | z << 7 | y);
}
public setBlockLight(value:number, x:number, y:number, z:number) {
return this.blockLight.set(x << 11 | z << 7 | y, value);
}
public getSkyLight(x:number, y:number, z:number) {
return this.skyLight.get(x << 11 | z << 7 | y);
}
public setSkyLight(value:number, x:number, y:number, z:number) {
return this.skyLight.set(x << 11 | z << 7 | y, value);
}
public getBlockBuffer() {
return Buffer.from(this.blocks);
}
public getMetadataBuffer() {
return this.metadata.toBuffer();
}
public getBlockLightBuffer() {
return this.blockLight.toBuffer();
}
public getSkyLightBuffer() {
return this.skyLight.toBuffer();
}
public getBlockData() {
return this.blocks;
}
}

30
server/Converter.js Normal file
View file

@ -0,0 +1,30 @@
/*
============- 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);
}

28
server/Entities/Entity.js Normal file
View file

@ -0,0 +1,28 @@
/*
==============- 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

@ -0,0 +1,20 @@
/*
============- 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

@ -0,0 +1,22 @@
/*
==========- 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

@ -0,0 +1,73 @@
/*
===========- 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,26 +0,0 @@
import { MetadataEntry, MetadataWriter } from "./MetadataWriter";
import MetadataFieldType from "./enums/MetadataFieldType";
export default class EntityMetadata {
public onFire:boolean = false;
public crouched:boolean = false;
public ridingEntity:boolean = false;
private finalValue:number = 0;
private static readonly ENTITY_ON_FIRE = 1 << 0;
private static readonly ENTITY_CROUCHING = 1 << 1;
private static readonly ENTITY_RIDING = 1 << 2;
writeMetadata() {
const metadataWriter = new MetadataWriter();
this.finalValue =
(this.onFire ? EntityMetadata.ENTITY_ON_FIRE : 0) | // On Fire
(this.crouched ? EntityMetadata.ENTITY_CROUCHING : 0) | // Crouching
(this.ridingEntity ? EntityMetadata.ENTITY_CROUCHING : 0); // Riding entity
metadataWriter.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, this.finalValue));
return metadataWriter.writeBuffer();
}
}

View file

@ -0,0 +1,31 @@
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

@ -0,0 +1,187 @@
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];
}

307
server/Generators/perlin.js Normal file
View file

@ -0,0 +1,307 @@
/**
* 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

@ -0,0 +1,23 @@
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;
}
}

10
server/ItemStack.js Normal file
View file

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

View file

@ -1,336 +0,0 @@
import { IReader } from "bufferstuff";
import { Console } from "hsconsole";
import { Socket } from "net";
import AABB from "./AABB";
import Block from "./blocks/Block";
import EntityLiving from "./entities/EntityLiving";
import EntityItem from "./entities/EntityItem";
import ItemStack from "./inventories/ItemStack";
import MinecraftServer from "./MinecraftServer";
import Packet from "./enums/Packet";
import PacketAnimation from "./packets/Animation";
import PacketChat from "./packets/Chat"
import PacketEntityAction from "./packets/EntityAction";
import PacketPlayer from "./packets/Player";
import PacketPlayerPosition from "./packets/PlayerPosition";
import PacketPlayerLook from "./packets/PlayerLook";
import PacketPlayerPositionLook from "./packets/PlayerPositionLook";
import PacketPlayerDigging from "./packets/PlayerDigging";
import PacketRespawn from "./packets/Respawn";
import PacketPlayerBlockPlacement from "./packets/PlayerBlockPlacement";
import PacketHoldingChange from "./packets/HoldingChange";
import PacketDisconnectKick from "./packets/DisconnectKick";
import PacketSoundEffect from "./packets/SoundEffect";
import PacketUseEntity from "./packets/UseEntity";
import Player from "./entities/Player";
import PlayerInventory from "./inventories/PlayerInventory";
import SoundEffects from "./enums/SoundEffects";
import Vec3 from "./Vec3";
export default class MPClient {
private readonly mcServer:MinecraftServer;
private readonly socket:Socket;
public entity:Player;
private inventory:PlayerInventory;
private dimension:number;
private holdingIndex:number = 36; // First hotbar slot.
private diggingAt:Vec3;
public constructor(mcServer:MinecraftServer, socket:Socket, entity:Player) {
this.mcServer = mcServer;
this.socket = socket;
this.entity = entity;
this.inventory = entity.inventory;
this.dimension = 0;
this.diggingAt = new Vec3();
}
private mapCoordsFromFace(pos:Vec3, face:number) {
switch (face) {
case 0:
pos.y--;
return pos;
case 1:
pos.y++;
return pos;
case 2:
pos.z--;
return pos;
case 3:
pos.z++;
return pos;
case 4:
pos.x--;
return pos;
case 5:
pos.x++;
return pos;
}
}
public handlePacket(reader:IReader) {
const packetId = reader.readUByte();
switch (packetId) {
case Packet.Chat: this.handleChat(new PacketChat().readData(reader)); break;
case Packet.UseEntity: this.handleUseEntity(new PacketUseEntity().readData(reader)); break;
case Packet.Respawn: this.handlePacketRespawn(new PacketRespawn().readData(reader)); break;
case Packet.Player: this.handlePacketPlayer(new PacketPlayer().readData(reader)); break;
case Packet.PlayerPosition: this.handlePacketPlayerPosition(new PacketPlayerPosition().readData(reader)); break;
case Packet.PlayerLook: this.handlePacketPlayerLook(new PacketPlayerLook().readData(reader)); break;
case Packet.PlayerPositionLook: this.handlePacketPlayerPositionLook(new PacketPlayerPositionLook().readData(reader)); break;
case Packet.PlayerDigging: this.handlePacketPlayerDigging(new PacketPlayerDigging().readData(reader)); break;
case Packet.PlayerBlockPlacement: this.handlePacketBlockPlacement(new PacketPlayerBlockPlacement().readData(reader)); break;
case Packet.HoldingChange: this.handlePacketHoldingChange(new PacketHoldingChange().readData(reader)); break;
//case Packets.UseBed: break;
case Packet.Animation: this.handlePacketAnimation(new PacketAnimation().readData(reader)); break;
case Packet.EntityAction: this.handlePacketEntityAction(new PacketEntityAction().readData(reader)); break;
case Packet.DisconnectKick: this.handleDisconnectKick(); break;
default: return Console.printWarn(`UNIMPLEMENTED PACKET: ${Packet[packetId]}`);
}
if (reader.readOffset < reader.length - 1) {
this.handlePacket(reader);
}
}
private handleUseEntity(packet:PacketUseEntity) {
const attacker = this.entity.world.entites.get(packet.userId);
const target = this.entity.world.entites.get(packet.targetId);
if (attacker && target && target instanceof EntityLiving) {
if (packet.leftClick) {
target.damageFrom(2, attacker);
}
}
}
private handleChat(packet:PacketChat) {
const message = packet.message.split(" ");
if (message[0].startsWith("/")) {
packet.message = "";
if (message[0] === "/tp") {
const x = this.entity.position.x = parseFloat(message[1]);
const y = this.entity.position.y = parseFloat(message[2]);
const z = this.entity.position.z = parseFloat(message[3]);
this.send(new PacketPlayerPositionLook(x, y, y + 0.62, z, 0, 0, false).writeData());
Console.printInfo(packet.message = `Teleported ${this.entity.username} to ${message[1]} ${message[2]} ${message[3]}`);
} else if (message[0] === "/csay") {
this.mcServer.sendChatMessage(`[CONSOLE] ${message.slice(1, message.length).join(" ")}`);
} else if (message[0] === "/top") {
packet.message = `Woosh!`;
const topBlock = this.entity.chunk.getTopBlockY(this.entity.position.x & 0xf, this.entity.position.z & 0xf);
this.send(new PacketPlayerPosition(this.entity.position.x, topBlock + 3, topBlock + 3.62, this.entity.position.z, false).writeData());
} else if (message[0] === "/tpx") {
const dimension = parseInt(message[1]);
if (this.mcServer.worlds.has(dimension)) {
packet.message = "\u00a76Switching dimensions...";
this.switchDimension(dimension);
} else {
packet.message = `\u00a7cNo dimension by id "${dimension}" exists!`;
}
}
if (packet.message !== "") {
this.send(packet.writeData());
}
return;
}
packet.message = `<${this.entity.username}> ${packet.message}`;
Console.printInfo(`[CHAT] ${packet.message}`);
this.mcServer.sendToAllClients(packet.writeData());
}
private handlePacketRespawn(packet:PacketRespawn) {
if (!this.entity.isDead && packet.dimension === this.entity.world.dimension) {
return;
}
const world = this.mcServer.worlds.get(this.entity.world.dimension);
if (world == undefined) {
return;
}
this.entity.world.removeEntity(this.entity);
const oldPlayerEntity = this.entity;
this.entity = new Player(this.mcServer, world, oldPlayerEntity.username);
this.entity.position.set(8, 70, 8);
world.addEntity(this.entity);
this.send(new PacketRespawn(world.dimension).writeData());
//this.send(new PacketSpawnPosition(8, 64, 8).writeData());
this.entity.position.set(this.entity.position.x, this.entity.position.y, this.entity.position.z);
this.send(new PacketPlayerPositionLook(this.entity.position.x, this.entity.position.y, this.entity.position.y + 0.62, this.entity.position.z, 0, 0, false).writeData());
this.entity.forceUpdatePlayerChunks();
}
private handlePacketPlayer(packet:PacketPlayer) {
this.entity.onGround = packet.onGround;
}
private handlePacketPlayerPosition(packet:PacketPlayerPosition) {
this.entity.onGround = packet.onGround;
this.entity.position.set(packet.x, packet.y, packet.z);
}
private handlePacketPlayerLook(packet:PacketPlayerLook) {
this.entity.onGround = packet.onGround;
this.entity.rotation.set(packet.yaw, packet.pitch);
}
private handlePacketPlayerPositionLook(packet:PacketPlayerPositionLook) {
this.entity.onGround = packet.onGround;
this.entity.position.set(packet.x, packet.y, packet.z);
this.entity.rotation.set(packet.yaw, packet.pitch);
}
private breakBlock(brokenBlockId:number, x:number, y:number, z:number) {
const metadata = this.entity.world.getBlockMetadata(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z);
this.entity.world.setBlockWithNotify(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, 0);
//this.inventory.addItemStack(new ItemStack(Block.blockBehaviours[brokenBlockId].droppedItem(brokenBlockId), 1, metadata));
//this.send(new PacketWindowItems(0, this.inventory.getInventorySize(), this.inventory.constructInventoryPayload()).writeData());
const blockBehaviour = Block.blockBehaviours[brokenBlockId];
const itemId = blockBehaviour.droppedItem(brokenBlockId);
if (itemId !== -1) {
const itemCount = blockBehaviour.droppedCount(brokenBlockId);
this.entity.sendToNearby(new PacketSoundEffect(SoundEffects.BLOCK_BREAK, x, y, z, brokenBlockId).writeData());
for (let i = 0; i < ((itemCount - 1) >> 6) + 1; i++) {
const itemEntity = new EntityItem(this.entity.world, new ItemStack(itemId, Math.min(itemCount - 64 * i, 64), metadata));
itemEntity.position.set(x + 0.5, y + 0.5, z + 0.5);
itemEntity.pickupDelay = 10;
this.entity.world.addEntity(itemEntity);
}
}
}
// TODO: Cap how far away a player is able to break blocks
private handlePacketPlayerDigging(packet:PacketPlayerDigging) {
// Special drop item case
if (packet.status === 4) {
const itemStack = this.getHeldItemStack();
if (itemStack !== null && itemStack.size > 0) {
itemStack.size--;
const itemEntity = new EntityItem(this.entity.world, new ItemStack(itemStack.itemID, 1, itemStack.damage));
itemEntity.pickupDelay = 10;
itemEntity.position.set(this.entity.position.x, this.entity.position.y + 1.50, this.entity.position.z);
itemEntity.motion.set(
-Math.sin((this.entity.rotation.yaw / 180) * Math.PI) * Math.cos((this.entity.rotation.pitch / 180) * Math.PI) * 0.3,
-Math.sin((this.entity.rotation.pitch / 180) * Math.PI) * 0.3 + 0.1,
Math.cos((this.entity.rotation.yaw / 180) * Math.PI) * Math.cos((this.entity.rotation.pitch / 180) * Math.PI) * 0.3
);
// Add random motion vector
const twoPIRandomised = Math.random() * Math.PI * 2;
const rngMult = 0.02 * Math.random();
itemEntity.motion.add(
Math.cos(twoPIRandomised) * rngMult,
(Math.random() - Math.random()) * 0.1,
Math.sin(twoPIRandomised) * rngMult
);
this.entity.world.addEntity(itemEntity);
this.inventory.dropEmptyItemStacks();
this.inventory.sendUpdatedStacks([this.holdingIndex]);
}
return;
}
this.diggingAt.set(packet.x, packet.y, packet.z);
let brokenBlockId:number;
if (packet.status === 0) {
// Started digging
if ((brokenBlockId = this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z)) != 0 && Block.blocks[brokenBlockId].blockStrength() >= 1) {
this.breakBlock(brokenBlockId, this.diggingAt.x, this.diggingAt.y, this.diggingAt.z);
}
} else if (packet.status === 2) {
if ((brokenBlockId = this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z)) != 0) {
this.breakBlock(brokenBlockId, this.diggingAt.x, this.diggingAt.y, this.diggingAt.z);
}
}
}
public getHeldItemStack() {
return this.inventory.getSlotItemStack(this.holdingIndex);
}
private handlePacketBlockPlacement(packet:PacketPlayerBlockPlacement) {
this.diggingAt.set(packet.x, packet.y, packet.z);
this.mapCoordsFromFace(this.diggingAt, packet.face);
if (this.entity.entityAABB.intersects(AABB.getAABB(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, this.diggingAt.x + 1, this.diggingAt.y + 1, this.diggingAt.z + 1))) {
return;
}
const itemStack = this.getHeldItemStack();
if (itemStack == null || itemStack.size == 0) {
return;
}
if (itemStack.isBlock && Block.blocks[itemStack.itemID].behaviour.canPlaceBlockAt(this.entity.world, this.diggingAt.x, this.diggingAt.y, this.diggingAt.z)) {
if (this.entity.world.getBlockId(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z) === 0) {
itemStack.size--;
this.entity.world.setBlockAndMetadataWithNotify(this.diggingAt.x, this.diggingAt.y, this.diggingAt.z, itemStack.itemID, itemStack.damage);
this.inventory.dropEmptyItemStacks();
}
} else {
// TODO: Handle item usage
}
}
private handlePacketHoldingChange(packet:PacketHoldingChange) {
if (packet.slotId < 0 || packet.slotId > 8) {
this.send(new PacketDisconnectKick("Out of Bounds Holding Index!").writeData());
this.socket.end();
return;
}
this.holdingIndex = 36 + packet.slotId;
}
// Animation start
private handlePacketAnimation(packet:PacketAnimation) {
// Forward this packet to all nearby clients
this.entity.world.sendToNearbyClients(this.entity, packet.writeData());
}
private handlePacketEntityAction(packet:PacketEntityAction) {
// Forward this packet to all nearby clients
switch (packet.action) {
case 1: this.entity.crouching = true; break;
case 2: this.entity.crouching = false; break;
case 3: break; // TODO: Leave Bed
}
}
private switchDimension(dimension:number) {
const world = this.mcServer.worlds.get(dimension);
if (world == undefined) {
return;
}
this.entity.world.removeEntity(this.entity);
this.entity.world = world;
world.addEntity(this.entity);
this.send(new PacketRespawn(dimension).writeData());
//this.send(new PacketSpawnPosition(8, 64, 8).writeData());
this.entity.position.set(8, 60, 8);
this.send(new PacketPlayerPositionLook(8, 70, 70.62, 8, 0, 0, false).writeData());
this.entity.forceUpdatePlayerChunks();
}
private handleDisconnectKick() {
this.socket.end();
}
public send(buffer:Buffer) {
this.socket.write(buffer);
}
}

View file

@ -1,90 +0,0 @@
import { createWriter, Endian } from "bufferstuff";
import FunkyArray from "funky-array";
import ItemStack from "./inventories/ItemStack";
import MetadataFieldType from "./enums/MetadataFieldType";
import Vec3 from "./Vec3";
export class MetadataEntry {
public type:MetadataFieldType;
public value:number|string|Vec3|ItemStack;
public constructor(type:MetadataFieldType, value:number|string|Vec3|ItemStack) {
this.type = type;
this.value = value;
}
}
export class MetadataWriter {
// https://wiki.vg/index.php?title=Protocol&oldid=488#Entity_Metadata_.280x28.29
private entries:FunkyArray<number, MetadataEntry>; // TODO: Extend with Item and Vector types
public constructor() {
this.entries = new FunkyArray<number, MetadataEntry>();
}
public addMetadataEntry(identifier:number, entry:MetadataEntry) {
this.entries.set(identifier, entry);
}
private calculateBufferSize() {
let size = this.entries.length + 1; // Type/Identifiers + Stream end magic
this.entries.forEach(entry => {
switch (entry.type) {
case MetadataFieldType.Byte: size += 1; break;
case MetadataFieldType.Short: size += 2; break;
case MetadataFieldType.Int: size += 4; break;
case MetadataFieldType.Float: size += 4; break;
case MetadataFieldType.String:
if (typeof(entry.value) === "string") {
size += 2 + entry.value.length * 2; break;
} else {
throw "Non-string value assigned to a String MetadataEntry";
}
case MetadataFieldType.Item:
if (entry.value instanceof ItemStack) {
size += 5;
} else {
throw "Non-ItemStack value assigned to an ItemStack MetadataEntry";
}
case MetadataFieldType.Vector:
if (entry.value instanceof Vec3) {
size += 12;
} else {
throw "Non-Vec3 value assigned to an Vec3 MetadataEntry";
}
}
})
return size;
}
public writeBuffer() {
const writer = createWriter(Endian.BE, this.calculateBufferSize());
for (const key of this.entries.keys) {
const entry = this.entries.get(key);
if (entry instanceof MetadataEntry) {
writer.writeByte((entry.type << 5 | key & 0x1f) & 0xff);
if (typeof(entry.value) === "number") {
switch (entry.type) {
case MetadataFieldType.Byte: writer.writeByte(entry.value); break;
case MetadataFieldType.Short: writer.writeShort(entry.value); break;
case MetadataFieldType.Int: writer.writeInt(entry.value); break;
case MetadataFieldType.Float: writer.writeFloat(entry.value); break;
}
} else if (typeof(entry.value) === "string") {
writer.writeString16(entry.value);
} else if (entry.value instanceof ItemStack) {
writer.writeShort(entry.value.itemID).writeByte(entry.value.size).writeShort(entry.value.damage);
} else if (entry.value instanceof Vec3) {
const absVec = entry.value.toAbs();
writer.writeInt(absVec.x).writeInt(absVec.y).writeInt(absVec.z);
}
}
}
// Metadata end magic
writer.writeByte(0x7F);
return writer.toBuffer();
}
}

View file

@ -1,296 +0,0 @@
import { createReader, IReader, Endian } from "bufferstuff";
import { getRandomValues } from "crypto";
import { Console } from "hsconsole";
import { Server, Socket } from "net";
import Config from "../config";
import Chunk from "./Chunk";
import FunkyArray from "funky-array";
import HillyGenerator from "./generators/terrain/Hilly";
import MPClient from "./MPClient";
import NetherGenerator from "./generators/terrain/Nether";
import Packet from "./enums/Packet";
import PacketKeepAlive from "./packets/KeepAlive";
import PacketHandshake from "./packets/Handshake";
import PacketLoginRequest from "./packets/LoginRequest";
import PacketChat from "./packets/Chat";
import PacketSpawnPosition from "./packets/SpawnPosition";
import PacketPlayerPositionLook from "./packets/PlayerPositionLook";
import PacketNamedEntitySpawn from "./packets/NamedEntitySpawn";
import PacketDisconnectKick from "./packets/DisconnectKick";
import PacketTimeUpdate from "./packets/TimeUpdate";
import PacketWindowItems from "./packets/WindowItems";
import Player from "./entities/Player";
import SaveCompressionType from "./enums/SaveCompressionType";
import World from "./World";
import WorldSaveManager from "./WorldSaveManager";
const chunkFrom = -15;
const chunkTo = 15;
const chunkCount = Math.abs(chunkFrom * 2) * (chunkTo * 2);
function getRandomSeed() {
const arr = new Uint32Array(1);
return getRandomValues(arr)[0];
}
export default 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 config:Config;
private server:Server;
private readonly serverClock:NodeJS.Timeout;
private tickCounter:number = 0;
private clients:FunkyArray<string, MPClient>;
public worlds:FunkyArray<number, World>;
public saveManager:WorldSaveManager;
// https://stackoverflow.com/a/7616484
// Good enough for the world seed.
private hashCode(string:string) : number {
let hash = 0, i, chr;
if (string.length === 0) {
return hash;
}
for (i = 0; i < string.length; i++) {
chr = string.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0;
}
return hash;
}
public constructor(config:Config) {
this.config = config;
let shuttingDown = false;
process.on("SIGINT", async (signal) => {
if (shuttingDown) {
return;
}
shuttingDown = true;
Console.printInfo("Shutting down...");
// Stop the server timer
clearInterval(this.serverClock);
// Disconnect all players
const kickPacket = new PacketDisconnectKick("Server shutting down.").writeData();
this.sendToAllClients(kickPacket);
// Save chunks
Console.printInfo("Saving worlds...");
let savedWorldCount = 0;
let savedChunkCount = 0;
const worldsSaveStartTime = Date.now();
await this.worlds.forEach(async (world) => {
const worldSaveStartTime = Date.now();
if (world.chunks.length !== 0) {
await world.chunks.forEach(async (chunk) => {
await world.unloadChunk(Chunk.CreateCoordPair(chunk.x, chunk.z));
savedChunkCount++;
});
}
Console.printInfo(`Saved DIM${world.dimension} to disk. Took ${Date.now() - worldSaveStartTime}ms`);
savedWorldCount++;
});
Console.printInfo(`Saved ${savedChunkCount} chunks from ${savedWorldCount} world(s). Took ${Date.now() - worldsSaveStartTime}ms`);
// Flush final console log to disk and close all writers
Console.cleanup();
// hsconsole is gone now so we have to use built in.
console.log("Goodbye");
// Shut down the tcp server
this.server.close();
});
if (this.config.saveCompression === SaveCompressionType.NONE) {
Console.printWarn("=============- WARNING -=============");
Console.printWarn(" Chunk compression is disabled. This");
Console.printWarn(" will lead to large file sizes!");
Console.printWarn("=====================================");
}
this.clients = new FunkyArray<string, MPClient>();
// Convert seed if needed
let worldSeed = typeof(this.config.seed) === "string" ? this.hashCode(this.config.seed) : typeof(this.config.seed) === "number" ? this.config.seed : getRandomSeed();
// Init save manager and load seed from it if possible
this.saveManager = new WorldSaveManager(this.config, [0, -1], worldSeed);
if (this.saveManager.worldSeed !== Number.MIN_VALUE) {
worldSeed = this.saveManager.worldSeed;
}
this.worlds = new FunkyArray<number, World>();
//this.worlds.set(0, new World(this.saveManager, 0, worldSeed, new NewOverworld(worldSeed)));
this.worlds.set(0, new World(this.saveManager, 0, worldSeed, new HillyGenerator(worldSeed)));
this.worlds.set(-1, new World(this.saveManager, -1, worldSeed, new NetherGenerator(worldSeed)));
(async () => {
const generateStartTime = Date.now();
let timer = Date.now();
await this.worlds.forEach(async world => {
timer = Date.now();
let chunksGenerated = 0;
let chunksPerSecond = 0;
Console.printInfo(`Generating spawn area for DIM${world.dimension}...`);
for (let x = chunkFrom; x < chunkTo; x++) {
for (let z = chunkFrom; z < chunkTo; z++) {
const chunk = await world.getChunkSafe(x, z);
chunk.forceLoaded = true;
chunksGenerated++;
chunksPerSecond++;
if (Date.now() - timer >= 1000) {
Console.printInfo(`DIM${world.dimension} Progress [${chunksGenerated}/${chunkCount}] (${chunksPerSecond} chunks/s) ${((chunksGenerated / chunkCount) * 100).toFixed(2)}%`);
timer = Date.now();
chunksPerSecond = 0;
}
}
}
});
Console.printInfo(`Done! Took ${Date.now() - generateStartTime}ms`);
this.initServer();
}).bind(this)();
const timeUpdate = new PacketTimeUpdate(BigInt(this.tickCounter));
this.serverClock = setInterval(() => {
// Every 1 sec
if (this.tickCounter % MinecraftServer.TICK_RATE === 0) {
if (this.clients.length !== 0) {
timeUpdate.time = BigInt(this.tickCounter);
const timePacket = timeUpdate.writeData();
this.clients.forEach(client => {
// Keep the client happy
client.send(this.keepalivePacket);
client.send(timePacket);
});
}
// const memoryUsage = process.memoryUsage();
// console.log(`Memory Usage: ${(memoryUsage.heapUsed / 1024 / 1024).toFixed(1)}MB / ${(memoryUsage.heapTotal / 1024 / 1024).toFixed(1)}MB ArrayBuffers: ${(memoryUsage.arrayBuffers / 1024 / 1024).toFixed(1)}MB`);
}
this.worlds.forEach(world => {
world.tick();
});
this.tickCounter++;
}, MinecraftServer.TICK_RATE_MS);
this.server = new Server();
this.server.on("connection", this.onConnection.bind(this));
}
initServer() {
this.server.listen(this.config.port, () => Console.printInfo(`Minecraft server started at ${this.config.port}`));
}
sendToAllClients(buffer:Buffer) {
this.clients.forEach(client => {
client.send(buffer);
});
}
sendChatMessage(text:string) {
this.sendToAllClients(new PacketChat(text).writeData());
Console.printInfo(`[CHAT] ${text}`);
}
async handleLoginRequest(reader:IReader, socket:Socket, setMPClient:(mpclient:MPClient) => void) {
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 dimension = 0;
const world = this.worlds.get(dimension);
if (world instanceof World) {
const clientEntity = new Player(this, world, loginPacket.username);
if (this.saveManager.playerDataOnDisk.includes(clientEntity.username)) {
clientEntity.fromSave(await this.saveManager.readPlayerDataFromDisk(clientEntity.username));
} else {
clientEntity.position.set(8, 70, 8);
}
world.addEntity(clientEntity);
const client = new MPClient(this, socket, clientEntity);
setMPClient(client);
clientEntity.mpClient = client;
this.clients.set(loginPacket.username, client);
this.sendChatMessage(`\u00a7e${loginPacket.username} joined the game`);
socket.write(new PacketLoginRequest(clientEntity.entityId, "", 0, dimension).writeData());
socket.write(new PacketSpawnPosition(8, 64, 8).writeData());
const thisPlayerSpawn = new PacketNamedEntitySpawn(clientEntity.entityId, clientEntity.username, clientEntity.absPosition.x, clientEntity.absPosition.y, clientEntity.absPosition.z, clientEntity.absRotation.yaw, clientEntity.absRotation.pitch, clientEntity.mpClient?.getHeldItemStack()?.itemID).writeData();
world.players.forEach(player => {
if (player.entityId !== clientEntity.entityId && clientEntity.distanceTo(player) < World.ENTITY_MAX_SEND_DISTANCE) {
// Inform the joining player of the players around them
socket.write(new PacketNamedEntitySpawn(player.entityId, player.username, player.absPosition.x, player.absPosition.y, player.absPosition.z, player.absRotation.yaw, player.absRotation.pitch, player.mpClient?.getHeldItemStack()?.itemID).writeData());
player.sendPlayerEquipment(clientEntity);
// Inform players around the joining player of the joined player
player.mpClient?.send(thisPlayerSpawn);
clientEntity.sendPlayerEquipment(player);
}
});
socket.write(new PacketPlayerPositionLook(clientEntity.position.x, clientEntity.position.y, clientEntity.position.y + 0.62, clientEntity.position.z, 0, 0, false).writeData());
const playerInventory = clientEntity.inventory;
socket.write(new PacketWindowItems(0, playerInventory.getInventorySize(), playerInventory.constructInventoryPayload()).writeData());
} else {
socket.write(new PacketDisconnectKick("Failed to find world to put player in.").writeData());
}
}
handleHandshake(reader:IReader, socket:Socket) {
const handshakePacket = new PacketHandshake().readData(reader);
socket.write(handshakePacket.writeData());
}
onConnection(socket:Socket) {
let mpClient:MPClient;
const setMPClient = (mpclient:MPClient) => {
mpClient = mpclient;
}
const playerDisconnect = (err:Error) => {
mpClient.entity.world.removeEntity(mpClient.entity);
this.clients.remove(mpClient.entity.username);
this.sendChatMessage(`\u00a7e${mpClient.entity.username} left the game`);
if (typeof(err) !== "boolean") {
Console.printError(`Client disconnected with error: ${err.message}`);
}
}
socket.on("close", playerDisconnect.bind(this));
socket.on("error", playerDisconnect.bind(this));
socket.on("data", chunk => {
const reader = createReader(Endian.BE, chunk);
// Let mpClient take over if it exists
if (mpClient instanceof MPClient) {
mpClient.handlePacket(reader);
return;
}
const packetId = reader.readUByte();
switch (packetId) {
// TODO: Handle timeouts at some point, idk.
case Packet.KeepAlive: break;
case Packet.LoginRequest: this.handleLoginRequest(reader, socket, setMPClient.bind(this)); break;
case Packet.Handshake: this.handleHandshake(reader, socket); break;
}
});
}
}

69
server/NamedPackets.js Normal file
View file

@ -0,0 +1,69 @@
/*
===========- 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

@ -0,0 +1,42 @@
/*
========- 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;

30
server/Packets/Packet.js Normal file
View file

@ -0,0 +1,30 @@
/*
==============- 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

@ -0,0 +1,18 @@
/*
========- 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

@ -0,0 +1,34 @@
/*
========- 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

@ -0,0 +1,26 @@
/*
=========- 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

@ -0,0 +1,38 @@
/*
==- 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

@ -0,0 +1,28 @@
/*
========- 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

@ -0,0 +1,36 @@
/*
=======- 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

@ -0,0 +1,41 @@
/*
=====- 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

@ -0,0 +1,26 @@
/*
=========- 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

@ -0,0 +1,33 @@
/*
=======- 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

@ -0,0 +1,41 @@
/*
======- 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

@ -0,0 +1,26 @@
/*
===========- 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

@ -0,0 +1,30 @@
/*
========- 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

@ -0,0 +1,30 @@
/*
========- 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

@ -0,0 +1,34 @@
/*
=======- 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

@ -0,0 +1,30 @@
/*
======- 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,17 +0,0 @@
import mulberry32 from "./mulberry32";
export default class Random {
private readonly mulberry32;
public constructor(seed:number = Date.now()) {
this.mulberry32 = mulberry32(seed);
}
public nextInt(bound = Number.MAX_SAFE_INTEGER) {
return Math.floor(this.mulberry32() * bound);
}
public nextFloat() {
return this.mulberry32();
}
}

View file

@ -1,19 +0,0 @@
import Vec2 from "./Vec2";
export default class Rotation extends Vec2 {
public get yaw() {
return this.x;
}
public set yaw(value:number) {
this.x = value;
}
public get pitch() {
return this.y;
}
public set pitch(value:number) {
this.y = value;
}
}

View file

@ -1,23 +0,0 @@
export default class TrackedProperty<T> {
private trackedValue?:T;
private updateCallback?:(value:T) => void;
constructor(initialValue?:T) {
this.trackedValue = initialValue;
}
public set OnChange(value:() => void) {
this.updateCallback = value;
}
public get Value() {
return this.trackedValue;
}
public set Value(value) {
this.trackedValue = value;
if (this.updateCallback && value) {
this.updateCallback(value);
}
}
}

91
server/Util/funkyArray.js Normal file
View file

@ -0,0 +1,91 @@
/*
===========- 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

@ -0,0 +1,28 @@
/*
===========- 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,35 +0,0 @@
export default class Vec2 {
public x:number;
public y:number;
public constructor(x?:Vec2 | number, y?:number) {
if (typeof(x) === "number" && typeof(y) === "number") {
this.x = x;
this.y = y;
} else if (typeof(x) === "number" && typeof(y) !== "number") {
this.x = x;
this.y = x;
} else if (x instanceof Vec2) {
this.x = x.x;
this.y = x.y;
} else {
this.x = this.y = 0;
}
}
public get isZero() {
return this.x === 0 && this.y === 0;
}
public set(x?:Vec2 | number, y?:number) {
if (x instanceof Vec2) {
this.x = x.x;
this.y = x.y;
} else if (typeof(x) === "number" && typeof(y) === "number") {
this.x = x;
this.y = y;
} else {
this.x = this.y = 0;
}
}
}

View file

@ -1,75 +0,0 @@
export default class Vec3 {
public x:number;
public y:number;
public z:number;
public constructor(x?:Vec3 | number, y?:number, z?:number) {
if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") {
this.x = x;
this.y = y;
this.z = z;
} else if (typeof(x) === "number" && typeof(y) !== "number" && typeof(z) !== "number") {
this.x = x;
this.y = x;
this.z = x;
} else if (x instanceof Vec3) {
this.x = x.x;
this.y = x.y;
this.z = x.z;
} else {
this.x = this.y = this.z = 0;
}
}
public get isZero() {
return this.x === 0 && this.y === 0 && this.z === 0;
}
public set(x?:Vec3 | number, y?:number, z?:number) {
if (x instanceof Vec3) {
this.x = x.x;
this.y = x.y;
this.z = x.z;
} else if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") {
this.x = x;
this.y = y;
this.z = z;
} else {
throw new Error(`Invalid arguments for Vec3.set : ${typeof(x)}, ${typeof(y)}, ${typeof(z)}`);
}
}
add(x:Vec3 | number, y?:number, z?:number) {
if (x instanceof Vec3) {
this.set(this.x + x.x, this.y + x.y, this.z + x.z);
} else if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") {
this.set(this.x + x, this.y + y, this.z + z);
} else {
throw new Error(`Invalid arguments for Vec3.add : ${typeof(x)}, ${typeof(y)}, ${typeof(z)}`);
}
}
sub(x:Vec3 | number, y?:number, z?:number) {
if (x instanceof Vec3) {
this.set(this.x - x.x, this.y - x.y, this.z - x.z);
} else if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") {
this.set(this.x - x, this.y - y, this.z - z);
} else {
throw new Error(`Invalid arguments for Vec3.add : ${typeof(x)}, ${typeof(y)}, ${typeof(z)}`);
}
}
mult(x:Vec3 | number, y?:number, z?:number) {
if (x instanceof Vec3) {
this.set(this.x * x.x, this.y * x.y, this.z * x.z);
} else if (typeof(x) === "number" && typeof(y) === "number" && typeof(z) === "number") {
this.set(this.x * x, this.y * y, this.z * z);
} else {
throw new Error(`Invalid arguments for Vec3.mult : ${typeof(x)}, ${typeof(y)}, ${typeof(z)}`);
}
}
toAbs() {
return new Vec3(Math.round(this.x * 32), Math.round(this.y * 32), Math.round(this.z * 32));
}
}

View file

@ -0,0 +1,82 @@
/*
===========- 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;
}

View file

@ -1,333 +0,0 @@
import { Endian, createWriter } from "bufferstuff";
import AABB from "./AABB";
import Block from "./blocks/Block";
import Chunk from "./Chunk";
import EntityItem from "./entities/EntityItem";
import FunkyArray from "funky-array";
import IEntity from "./entities/IEntity";
import IGenerator from "./generators/IGenerator";
import IQueuedUpdate from "./queuedUpdateTypes/IQueuedUpdate";
import PacketBlockChange from "./packets/BlockChange";
import PacketDestroyEntity from "./packets/DestroyEntity";
import PacketPickupSpawn from "./packets/PickupSpawn";
import Player from "./entities/Player";
import QueuedBlockUpdate from "./queuedUpdateTypes/BlockUpdate";
import Random from "./Random";
import WorldSaveManager from "./WorldSaveManager";
export default class World {
public static ENTITY_MAX_SEND_DISTANCE = 50;
private static READ_CHUNKS_FROM_DISK = true;
private readonly saveManager;
private readonly chunksOnDisk:Array<number>;
public chunks:FunkyArray<number, Chunk>;
public entites:FunkyArray<number, IEntity>;
public players:FunkyArray<number, Player>;
public playerHitboxes:FunkyArray<number, AABB>;
public queuedChunkBlocks:Array<IQueuedUpdate>;
public queuedUpdates:Array<IQueuedUpdate>;
public generator:IGenerator;
public random:Random = new Random();
public readonly dimension:number;
public constructor(saveManager:WorldSaveManager, dimension:number, seed:number, generator:IGenerator) {
this.dimension = dimension;
this.saveManager = saveManager;
this.chunksOnDisk = this.saveManager.chunksOnDisk.get(dimension) ?? new Array<number>;
this.chunks = new FunkyArray<number, Chunk>();
this.entites = new FunkyArray<number, IEntity>();
this.players = new FunkyArray<number, Player>();
this.playerHitboxes = new FunkyArray<number, AABB>();
this.queuedChunkBlocks = new Array<IQueuedUpdate>();
this.queuedUpdates = new Array<IQueuedUpdate>();
this.generator = generator;
}
public addEntity(entity:IEntity) {
this.entites.set(entity.entityId, entity);
this.playerHitboxes.set(entity.entityId, entity.entityAABB);
if (entity instanceof Player) {
this.players.set(entity.entityId, entity);
} else if (entity instanceof EntityItem) {
const packet = new PacketPickupSpawn(entity.entityId, entity.itemStack.itemID, entity.itemStack.size, entity.itemStack.damage, Math.round(entity.position.x * 32), Math.round(entity.position.y * 32), Math.round(entity.position.z * 32), 0, 0, 0).writeData();
entity.sendToNearby(packet);
}
}
// TODO: getChunkByCoordPair failed in here during removeEntity, figure out why.
public removeEntity(entity:IEntity) {
if (entity instanceof Player) {
for (const coordPair of entity.loadedChunks) {
if (this.chunkExists(coordPair)) {
const chunk = this.getChunkByCoordPair(coordPair);
chunk.playersInChunk.remove(entity.entityId);
if (!chunk.forceLoaded && chunk.playersInChunk.length === 0) {
this.unloadChunk(coordPair);
}
}
}
// Clear player chunk list (they may be switching dimensions)
entity.loadedChunks = new Array<number>();
entity.justUnloaded = new Array<number>();
this.playerHitboxes.remove(entity.entityId);
this.players.remove(entity.entityId);
if (!entity.isDead) {
const writer = createWriter(Endian.BE);
entity.toSave(writer);
this.saveManager.writePlayerSaveToDisk(entity.username, writer);
}
}
this.entites.remove(entity.entityId);
this.sendToNearbyClients(entity, new PacketDestroyEntity(entity.entityId).writeData());
}
public chunkExists(coordPairOrX:number, chunkZ?:number) {
if (typeof(coordPairOrX) === "number" && typeof(chunkZ) === "number") {
return this.chunks.has(Chunk.CreateCoordPair(coordPairOrX, chunkZ));
}
return this.chunks.has(coordPairOrX);
}
public getChunk(x:number, z:number) {
const coordPair = Chunk.CreateCoordPair(x, z);
const existingChunk = this.chunks.get(coordPair);
if (!(existingChunk instanceof Chunk)) {
throw new Error(`BADLOOKUP: Chunk [${x}, ${z}] does not exist.`);
}
return existingChunk;
}
public getChunkSafe(x:number, z:number) {
return new Promise<Chunk>((resolve) => {
const coordPair = Chunk.CreateCoordPair(x, z);
const existingChunk = this.chunks.get(coordPair);
if (!(existingChunk instanceof Chunk)) {
if (World.READ_CHUNKS_FROM_DISK && this.chunksOnDisk.includes(coordPair)) {
return this.saveManager.readChunkFromDisk(this, x, z)
.then(chunk => resolve(this.chunks.set(coordPair, chunk)));
} else {
resolve(this.chunks.set(coordPair, new Chunk(this, x, z, true)));
if (World.READ_CHUNKS_FROM_DISK) {
this.saveManager.writeChunkToDisk(this.getChunk(x, z));
}
return;
}
}
resolve(existingChunk);
});
}
public getChunkByCoordPair(coordPair:number) {
const existingChunk = this.chunks.get(coordPair);
if (!(existingChunk instanceof Chunk)) {
throw new Error(`BADLOOKUP: Chunk ${coordPair} does not exist.`);
}
return existingChunk;
}
public getBlockId(x:number, y:number, z:number) {
const chunkX = x >> 4,
chunkZ = z >> 4;
return this.getChunk(chunkX, chunkZ).getBlockId(x & 0xf, y, z & 0xf);
}
public getChunkBlockId(chunk:Chunk, x:number, y:number, z:number) {
return chunk.getBlockId(x & 0xf, y, z & 0xf);
}
public getBlockMetadata(x:number, y:number, z:number) {
const chunkX = x >> 4,
chunkZ = z >> 4;
return this.getChunk(chunkX, chunkZ).getBlockMetadata(x & 0xf, y, z & 0xf);
}
public getBlockLight(x:number, y:number, z:number) {
const chunkX = x >> 4,
chunkZ = z >> 4;
return this.getChunk(chunkX, chunkZ).getBlockLight(x & 0xf, y, z & 0xf);
}
public getSkyLight(x:number, y:number, z:number) {
const chunkX = x >> 4,
chunkZ = z >> 4;
return this.getChunk(chunkX, chunkZ).getSkyLight(x & 0xf, y, z & 0xf);
}
public setBlock(blockId:number, x:number, y:number, z:number, doBlockUpdate?:boolean) {
const chunkX = x >> 4,
chunkZ = z >> 4;
const chunk = this.getChunk(chunkX, chunkZ);
chunk.setBlockWithMetadata(blockId, 0, x & 0xf, y, z & 0xf);
if (doBlockUpdate) {
const blockUpdatePacket = new PacketBlockChange(x, y, z, blockId, 0).writeData();
// Send block update to all players that have this chunk loaded
chunk.playersInChunk.forEach(player => {
player.mpClient?.send(blockUpdatePacket);
});
}
}
public setBlockWithMetadata(blockId:number, metadata:number, x:number, y:number, z:number, doBlockUpdate?:boolean) {
const chunkX = x >> 4,
chunkZ = z >> 4;
const chunk = this.getChunk(chunkX, chunkZ);
chunk.setBlockWithMetadata(blockId, metadata, x & 0xf, y, z & 0xf);
if (doBlockUpdate) {
const blockUpdatePacket = new PacketBlockChange(x, y, z, blockId, metadata).writeData();
// Send block update to all players that have this chunk loaded
chunk.playersInChunk.forEach(player => {
player.mpClient?.send(blockUpdatePacket);
});
}
}
public setBlockMetadata(x:number, y:number, z:number, metadata:number, doBlockUpdate?:boolean) {
const chunkX = x >> 4, chunkZ = z >> 4;
const chunk = this.getChunk(chunkX, chunkZ);
const xc = x & 0xf, zc = z & 0xf;
chunk.setBlockMetadata(metadata, xc, y, zc);
if (doBlockUpdate) {
const blockId = chunk.getBlockId(xc, y, zc);
const blockUpdatePacket = new PacketBlockChange(x, y, z, blockId, metadata).writeData();
// Send block update to all players that have this chunk loaded
chunk.playersInChunk.forEach(player => {
player.mpClient?.send(blockUpdatePacket);
});
}
}
public setBlockWithNotify(x:number, y:number, z:number, blockId:number) {
this.setBlock(blockId, x, y, z, true);
this.notifyNeighborBlocksOfChange(x, y, z, blockId);
}
public setBlockMetadataWithNotify(x:number, y:number, z:number, metadata:number) {
this.setBlockMetadata(x, y, z, metadata, true);
this.notifyNeighborBlocksOfChange(x, y, z, this.getBlockId(x, y, z));
}
public setBlockAndMetadataWithNotify(x:number, y:number, z:number, blockId:number, metadata:number) {
this.setBlockWithMetadata(blockId, metadata, x, y, z, true);
this.notifyNeighborBlocksOfChange(x, y, z, blockId);
}
public notifyNeighborBlocksOfChange(x:number, y:number, z:number, blockId:number) {
this.notifyNeighborBlockOfChange(x - 1, y, z, blockId);
this.notifyNeighborBlockOfChange(x + 1, y, z, blockId);
this.notifyNeighborBlockOfChange(x, y - 1, z, blockId);
this.notifyNeighborBlockOfChange(x, y + 1, z, blockId);
this.notifyNeighborBlockOfChange(x, y, z - 1, blockId);
this.notifyNeighborBlockOfChange(x, y, z + 1, blockId);
}
private notifyNeighborBlockOfChange(x:number, y:number, z:number, blockId:number) {
const block = Block.blocks[this.getBlockId(x, y, z)];
if (block != null && block.blockId !== 0) {
block.neighborBlockChange(this, x, y, z, block.blockId);
}
}
public sendToNearbyClients(sentFrom:IEntity, buffer:Buffer) {
this.players.forEach(player => {
if (sentFrom.entityId !== player.entityId && Math.abs(sentFrom.distanceTo(player)) < World.ENTITY_MAX_SEND_DISTANCE) {
player.mpClient?.send(buffer);
}
});
}
public sendToNearbyAllNearbyClients(sentFrom:IEntity, buffer:Buffer) {
this.players.forEach(player => {
if (Math.abs(sentFrom.distanceTo(player)) < World.ENTITY_MAX_SEND_DISTANCE) {
player.mpClient?.send(buffer);
}
});
}
public async unloadChunk(coordPair:number) {
const chunk = this.getChunkByCoordPair(coordPair);
if (!chunk.savingToDisk) {
chunk.savingToDisk = true;
await this.saveManager.writeChunkToDisk(chunk);
if (chunk.playersInChunk.length === 0) {
this.chunks.remove(coordPair);
return;
}
// A player loaded the chunk while we were, flushing to disk.
// Keep it loaded.
chunk.savingToDisk = false;
}
}
public tick() {
if (this.queuedUpdates.length > 0) {
for (let i = this.queuedUpdates.length - 1; i >= 0; i--) {
const update = this.queuedUpdates[i];
if (update instanceof QueuedBlockUpdate) {
if (this.chunks.keys.includes(update.coordPair)) {
this.queuedUpdates.splice(i, 1);
const thatChunk = this.getChunkByCoordPair(update.coordPair);
thatChunk.setBlockWithMetadata(update.blockId, update.metadata, update.x, update.y, update.z);
if (thatChunk.playersInChunk.length > 0) {
const blockUpdate = new PacketBlockChange((thatChunk.x << 4) + update.x, update.y, (thatChunk.z << 4) + update.z, update.blockId, update.metadata).writeData()
thatChunk.playersInChunk.forEach(player => {
player.mpClient?.send(blockUpdate);
});
}
}
}
}
}
this.entites.forEach(entity => {
entity.onTick();
if (entity instanceof Player) {
if (entity.justUnloaded.length > 0) {
for (const coordPair of entity.justUnloaded) {
if (this.chunks.get(coordPair) != undefined) {
const chunkToUnload = this.getChunkByCoordPair(coordPair);
chunkToUnload.playersInChunk.remove(entity.entityId);
if (!chunkToUnload.forceLoaded && chunkToUnload.playersInChunk.length === 0) {
this.unloadChunk(coordPair);
}
}
}
entity.justUnloaded = new Array<number>();
}
}
if (entity.markedForDisposal) {
this.removeEntity(entity);
}
})
}
}

View file

@ -1,299 +0,0 @@
import { createWriter, createReader, Endian, IWriter, IReader } from "bufferstuff";
import { readFileSync, readFile, writeFile, existsSync, mkdirSync, writeFileSync, readdirSync, renameSync } from "fs";
import { Console } from "hsconsole";
import { deflate, inflate } from "zlib";
import Chunk from "./Chunk";
import Config from "../config";
import FunkyArray from "funky-array";
import SaveCompressionType from "./enums/SaveCompressionType";
import World from "./World";
enum FileMagic {
Chunk = 0xFC,
Info = 0xFD,
Player = 0xFE
}
export default class WorldSaveManager {
private readonly worldFolderPath;
private readonly globalDataPath;
private readonly worldPlayerDataFolderPath;
private readonly infoFilePath;
private readonly config:Config;
public worldCreationDate = new Date();
public worldLastLoadDate = new Date();
public worldSeed = Number.MIN_VALUE;
public chunksOnDisk:FunkyArray<number, Array<number>>;
public playerDataOnDisk:Array<string>;
public constructor(config:Config, dimensions:Array<number>, numericalSeed:number) {
this.chunksOnDisk = new FunkyArray<number, Array<number>>();
this.playerDataOnDisk = new Array<string>();
this.worldFolderPath = `./${config.worldName}`;
this.worldPlayerDataFolderPath = `${this.worldFolderPath}/playerdata`;
this.globalDataPath = `${this.worldFolderPath}/data`;
this.infoFilePath = `${this.worldFolderPath}/info.hwd`;
this.config = config;
// Create world folder if it doesn't exist
if (!existsSync(this.worldFolderPath)) {
mkdirSync(this.worldFolderPath);
mkdirSync(this.globalDataPath);
}
if (existsSync(this.infoFilePath)) {
this.readInfoFile();
} else {
// World info file does not exist
this.worldSeed = numericalSeed;
this.createInfoFile(numericalSeed);
}
for (const dimension of dimensions) {
const chunksArray = new Array<number>();
this.chunksOnDisk.set(dimension, chunksArray);
const dimensionFolderPath = `${this.worldFolderPath}/DIM${dimension}`
if (!existsSync(dimensionFolderPath)) {
mkdirSync(dimensionFolderPath);
mkdirSync(`${dimensionFolderPath}/chunks`);
mkdirSync(`${dimensionFolderPath}/data`);
} else {
const chunkFiles = readdirSync(`${dimensionFolderPath}/chunks`);
for (const file of chunkFiles) {
if (file.endsWith(".hwc")) {
const name = file.split(".")[0];
chunksArray.push(parseInt(name.startsWith("-") ? name.replace("-", "-0x") : `0x${name}`));
}
}
}
}
if (!existsSync(this.worldPlayerDataFolderPath)) {
mkdirSync(this.worldPlayerDataFolderPath);
}
const playerDataFiles = readdirSync(this.worldPlayerDataFolderPath);
for (const dataFile of playerDataFiles) {
if (dataFile.endsWith(".hpd")) {
this.playerDataOnDisk.push(dataFile.replace(".hpd", ""));
}
}
}
private createInfoFile(numericalSeed:number) {
const infoFileWriter = createWriter(Endian.BE, 26);
infoFileWriter.writeUByte(FileMagic.Info); // Info File Magic
infoFileWriter.writeUByte(2); // File Version
infoFileWriter.writeLong(this.worldCreationDate.getTime()); // World creation date
infoFileWriter.writeLong(this.worldLastLoadDate.getTime()); // Last load date
infoFileWriter.writeLong(numericalSeed);
writeFileSync(this.infoFilePath, infoFileWriter.toBuffer());
}
private readInfoFile() {
const infoFileReader = createReader(Endian.BE, readFileSync(this.infoFilePath));
const fileMagic = infoFileReader.readUByte();
if (fileMagic !== FileMagic.Info) {
throw new Error("World info file is invalid");
}
const fileVersion = infoFileReader.readByte();
// v0, v1 and v2 all contain the same data apart from version numbers
// All that changed between them was the folder format.
if (fileVersion === 0 || fileVersion === 1 || fileVersion === 2) {
this.worldCreationDate = new Date(Number(infoFileReader.readLong()));
infoFileReader.readLong(); // Last load time is currently ignored
this.worldSeed = Number(infoFileReader.readLong());
// Upgrade v0 to v1
if (fileVersion === 0) {
Console.printInfo("Upgrading world to format v1 from v0");
renameSync(`${this.worldFolderPath}/chunks`, `${this.worldFolderPath}/DIM0`);
this.createInfoFile(this.worldSeed);
}
// Upgrade v1 to v2
if (fileVersion === 1) {
Console.printInfo("Upgrading world to format v2 from v1");
const files = readdirSync(`${this.worldFolderPath}/`);
for (const file of files) {
if (file.startsWith("DIM")) {
renameSync(`${this.worldFolderPath}/${file}`, `${this.worldFolderPath}/OLD${file}`);
mkdirSync(`${this.worldFolderPath}/${file}`);
mkdirSync(`${this.worldFolderPath}/${file}/data`);
renameSync(`${this.worldFolderPath}/OLD${file}`, `${this.worldFolderPath}/${file}/chunks`);
}
}
this.createInfoFile(this.worldSeed);
}
}
}
public writeChunkToDisk(chunk:Chunk) {
return new Promise<boolean>((resolve, reject) => {
const saveType = this.config.saveCompression;
const chunkFileWriter = createWriter(Endian.BE, 10);
chunkFileWriter.writeUByte(FileMagic.Chunk); // Chunk File Magic
// TODO: Change to 1 when lighting actually works
chunkFileWriter.writeUByte(1); // File Version
chunkFileWriter.writeUByte(saveType); // Save compression type
chunkFileWriter.writeUByte(16); // Chunk X
chunkFileWriter.writeUByte(128); // Chunk Y
chunkFileWriter.writeUByte(16); // Chunk Z
const chunkData = createWriter(Endian.BE)
.writeBuffer(Buffer.from(chunk.getBlockData()))
.writeBuffer(chunk.getMetadataBuffer())
.writeBuffer(chunk.getBlockLightBuffer())
.writeBuffer(chunk.getSkyLightBuffer()).toBuffer();
const codArr = this.chunksOnDisk.get(chunk.world.dimension);
if (saveType === SaveCompressionType.NONE) {
chunkFileWriter.writeInt(chunkData.length); // Data length
chunkFileWriter.writeBuffer(chunkData); // Chunk data
writeFile(`${this.worldFolderPath}/DIM${chunk.world.dimension}/chunks/${Chunk.CreateCoordPair(chunk.x, chunk.z).toString(16)}.hwc`, chunkFileWriter.toBuffer(), () => {
const cPair = Chunk.CreateCoordPair(chunk.x, chunk.z);
if (!codArr?.includes(cPair)) {
codArr?.push(cPair);
}
resolve(true);
});
} else if (saveType === SaveCompressionType.DEFLATE) {
deflate(chunkData, (err, data) => {
if (err) {
return reject(err);
}
chunkFileWriter.writeInt(data.length);
chunkFileWriter.writeBuffer(data);
writeFile(`${this.worldFolderPath}/DIM${chunk.world.dimension}/chunks/${Chunk.CreateCoordPair(chunk.x, chunk.z).toString(16)}.hwc`, chunkFileWriter.toBuffer(), () => {
const cPair = Chunk.CreateCoordPair(chunk.x, chunk.z);
if (!codArr?.includes(cPair)) {
codArr?.push(cPair);
}
//console.log(`Wrote ${chunk.x},${chunk.z} to disk`);
resolve(true);
});
})
} else if (saveType === SaveCompressionType.XZ) {
// TODO: Implement XZ chunk saving
}
});
}
readChunkFromDisk(world:World, x:number, z:number) {
return new Promise<Chunk>((resolve, reject) => {
readFile(`${this.worldFolderPath}/DIM${world.dimension}/chunks/${Chunk.CreateCoordPair(x, z).toString(16)}.hwc`, (err, data) => {
if (err) {
return reject(err);
}
const chunkFileReader = createReader(Endian.BE, data);
// Check file validity
if (chunkFileReader.readUByte() !== FileMagic.Chunk) {
return reject(new Error("Chunk file is invalid"));
}
const fileVersion = chunkFileReader.readUByte();
if (fileVersion === 0) {
const saveCompressionType:SaveCompressionType = chunkFileReader.readUByte();
const chunkX = chunkFileReader.readUByte();
const chunkY = chunkFileReader.readUByte();
const chunkZ = chunkFileReader.readUByte();
const totalByteSize = chunkX * chunkZ * chunkY;
const contentLength = chunkFileReader.readInt();
if (saveCompressionType === SaveCompressionType.NONE) {
const chunkData = createReader(Endian.BE, chunkFileReader.readBuffer(contentLength));
const chunk = new Chunk(world, x, z, chunkData.readUint8Array(totalByteSize), chunkData.readUint8Array(totalByteSize / 2));
resolve(chunk);
} else if (saveCompressionType === SaveCompressionType.DEFLATE) {
inflate(chunkFileReader.readBuffer(contentLength), (err, data) => {
if (err) {
return reject(err);
}
const chunkData = createReader(Endian.BE, data);
const chunk = new Chunk(world, x, z, chunkData.readUint8Array(totalByteSize), chunkData.readUint8Array(totalByteSize / 2));
resolve(chunk);
});
}
} else if (fileVersion === 1) {
const saveCompressionType:SaveCompressionType = chunkFileReader.readUByte();
const chunkX = chunkFileReader.readUByte();
const chunkY = chunkFileReader.readUByte();
const chunkZ = chunkFileReader.readUByte();
const totalByteSize = chunkX * chunkZ * chunkY;
const contentLength = chunkFileReader.readInt();
if (saveCompressionType === SaveCompressionType.NONE) {
const chunkData = createReader(Endian.BE, chunkFileReader.readBuffer(contentLength));
const chunk = new Chunk(world, x, z, chunkData.readUint8Array(totalByteSize), chunkData.readUint8Array(totalByteSize / 2), chunkData.readUint8Array(totalByteSize / 2), chunkData.readUint8Array(totalByteSize / 2));
resolve(chunk);
} else if (saveCompressionType === SaveCompressionType.DEFLATE) {
inflate(chunkFileReader.readBuffer(contentLength), (err, data) => {
if (err) {
return reject(err);
}
const chunkData = createReader(Endian.BE, data);
const chunk = new Chunk(world, x, z, chunkData.readUint8Array(totalByteSize), chunkData.readUint8Array(totalByteSize / 2), chunkData.readUint8Array(totalByteSize / 2), chunkData.readUint8Array(totalByteSize / 2));
resolve(chunk);
});
}
}
});
});
}
writePlayerSaveToDisk(username:string, playerData:IWriter) {
return new Promise<boolean>((resolve, reject) => {
const playerDataWriter = createWriter(Endian.BE);
playerDataWriter.writeUByte(FileMagic.Player); // File magic
playerDataWriter.writeUByte(0); // File version
playerDataWriter.writeBuffer(playerData.toBuffer()); // Player data
writeFile(`${this.worldPlayerDataFolderPath}/${username}.hpd`, playerDataWriter.toBuffer(), (err) => {
if (err) {
return reject(err);
}
if (!this.playerDataOnDisk.includes(username)) {
this.playerDataOnDisk.push(username);
}
resolve(true);
})
});
}
readPlayerDataFromDisk(username:string) {
return new Promise<IReader>((resolve, reject) => {
readFile(`${this.worldPlayerDataFolderPath}/${username}.hpd`, (err, data) => {
if (err) {
return reject(err);
}
const reader = createReader(Endian.BE, data);
if (reader.readUByte() !== FileMagic.Player) {
return reject(new Error("Player data file is invalid"));
}
const fileVersion = reader.readUByte();
if (fileVersion === 0) {
resolve(reader);
}
});
});
}
}

View file

@ -1,256 +0,0 @@
import AABB from "../AABB";
import BlockBehaviour from "./BlockBehaviour";
import BlockBehaviourSapling from "./BlockBehaviorSapling";
import BlockBehaviourSugarcane from "./BlockBehaviorSugarcane";
import BlockBehaviourClay from "./BlockBehaviourClay";
import BlockBehaviourFlower from "./BlockBehaviourFlower";
import BlockBehaviourGrass from "./BlockBehaviourGrass";
import BlockBehaviourOre from "./BlockBehaviourOre";
import BlockBehaviourRedstoneOre from "./BlockBehaviourRedstoneOre";
import BlockBehaviourStone from "./BlockBehaviourStone";
import BlockBehaviourTallGrass from "./BlockBehaviourTallGrass";
import IBlockBehaviour from "./IBlockBehaviour";
import World from "../World";
abstract class Behaviour {
public static base = new BlockBehaviour();
public static stone = new BlockBehaviourStone();
public static grass = new BlockBehaviourGrass();
public static sapling = new BlockBehaviourSapling();
public static ore = new BlockBehaviourOre();
public static tallGrass = new BlockBehaviourTallGrass();
public static flower = new BlockBehaviourFlower();
public static redstoneOre = new BlockBehaviourRedstoneOre();
public static clay = new BlockBehaviourClay();
public static sugarcane = new BlockBehaviourSugarcane();
}
export default class Block {
public readonly blockId:number;
public static readonly blocks:Array<Block> = new Array<Block>();
public static readonly lightPassage:Array<number> = new Array<number>();
public static readonly lightEmission:Array<number> = new Array<number>();
public static readonly hardness:Array<number> = new Array<number>();
public static readonly blockAABBs:Array<AABB> = new Array<AABB>();
public static readonly blockBehaviours:Array<IBlockBehaviour> = new Array<IBlockBehaviour>();
public static readonly blockNames:Array<string> = new Array<string>();
public constructor(blockId:number) {
Block.blocks[blockId] = this;
Block.lightPassage[blockId] = 0;
Block.lightEmission[blockId] = 0;
Block.blockNames[blockId] = "";
Block.blockBehaviours[blockId] = Behaviour.base;
this.blockId = blockId;
}
public get lightPassage() {
return Block.lightPassage[this.blockId];
}
public set lightPassage(value:number) {
Block.lightPassage[this.blockId] = value;
}
public get lightEmission() {
return Block.lightEmission[this.blockId];
}
public set lightEmission(value:number) {
Block.lightEmission[this.blockId] = value;
}
public get hardness() {
return Block.hardness[this.blockId];
}
public set hardness(value:number) {
Block.hardness[this.blockId] = value;
}
private get blockAABB() {
return Block.blockAABBs[this.blockId];
}
private set blockAABB(value:AABB) {
Block.blockAABBs[this.blockId] = value;
}
public get blockName() {
return Block.blockNames[this.blockId];
}
public set blockName(value:string) {
Block.blockNames[this.blockId] = value;
}
public get behaviour() {
return Block.blockBehaviours[this.blockId];
}
public set behaviour(value:IBlockBehaviour) {
Block.blockBehaviours[this.blockId] = value;
}
public setBehaviour(value:IBlockBehaviour) {
value.block = this;
this.behaviour = value;
return this;
}
public setLightPassage(value:number) {
this.lightPassage = value;
return this;
}
public setLightEmission(value:number) {
this.lightEmission = value;
return this;
}
public setBlockName(value:string) {
this.blockName = value;
return this;
}
public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {
this.behaviour.neighborBlockChange(world, x, y, z, blockId);
}
public droppedItem(blockId:number) {
this.behaviour.droppedItem(blockId);
}
public droppedCount(blockId:number) {
this.behaviour.droppedCount(blockId);
}
public getHardness() {
return this.hardness;
}
public setHardness(value:number) {
this.hardness = value;
return this;
}
public setUnbreakable() {
return this.setHardness(-1);
}
public blockStrength() {
if (this.hardness < 0) {
return 0;
}
// TODO: Check if we can actually harvest a block with current tool
// TODO: Have the 1 be based on current tool ig
return 1 / this.hardness / 100;
}
public getBoundingBox(x:number, y:number, z:number) {
return this.behaviour.getBoundingBox(x, y, z);
}
// Define statics here
static readonly stone = new Block(1).setHardness(1.5).setBehaviour(Behaviour.stone).setBlockName("Stone");
static readonly grass = new Block(2).setHardness(0.6).setBehaviour(Behaviour.grass).setBlockName("Grass");
static readonly dirt = new Block(3).setHardness(0.5).setBlockName("Dirt");
static readonly cobblestone = new Block(4).setHardness(2).setBlockName("Cobblestone");
static readonly planks = new Block(5).setHardness(2).setBlockName("Planks");
static readonly sapling = new Block(6).setHardness(0).setBehaviour(Behaviour.sapling).setBlockName("Sapling");
static readonly bedrock = new Block(7).setUnbreakable().setBlockName("Bedrock");
static readonly waterFlowing = new Block(8).setHardness(100).setLightPassage(128).setBlockName("Flowing Water"); // TODO: Behavior script
static readonly waterStill = new Block(9).setHardness(100).setLightPassage(255).setBlockName("Still Water"); // TODO: Behavior script
static readonly lavaFlowing = new Block(10).setHardness(0).setLightPassage(255).setBlockName("Flowing Lava"); // TODO: Behavior script
static readonly lavaStill = new Block(11).setHardness(100).setBlockName("Still Lava"); // TODO: Behavior script
static readonly sand = new Block(12).setHardness(0.5).setBlockName("Sand");
static readonly gravel = new Block(13).setHardness(0.6).setBlockName("Gravel"); // TODO: Behavior script
static readonly goldOre = new Block(14).setHardness(3).setBehaviour(Behaviour.ore).setBlockName("Gold Ore"); // TODO: Behavior script
static readonly ironOre = new Block(15).setHardness(3).setBehaviour(Behaviour.ore).setBlockName("Iron Ore"); // TODO: Behavior script
static readonly coalOre = new Block(16).setHardness(3).setBehaviour(Behaviour.ore).setBlockName("Coal Ore"); // TODO: Behavior script
static readonly wood = new Block(17).setHardness(2).setBlockName("Wood");
static readonly leaves = new Block(18).setHardness(0.2).setLightPassage(240).setBlockName("Leaves");
static readonly sponge = new Block(19).setHardness(0.6).setBlockName("Sponge");
static readonly glass = new Block(20).setHardness(0.3).setLightPassage(255).setBlockName("Glass"); // TODO: Behavior script
static readonly lapisOre = new Block(21).setHardness(3).setBehaviour(Behaviour.ore).setBlockName("Lapis Ore"); // TODO: Behavior script
static readonly lapisBlock = new Block(22).setHardness(3).setBlockName("Lapis Block");
static readonly dispenser = new Block(23).setHardness(3.5).setBlockName("Dispenser");
static readonly sandStone = new Block(24).setHardness(0.8).setBlockName("Sand Stone");
static readonly noteblock = new Block(25).setHardness(0.8).setBlockName("Noteblock");
static readonly bed = new Block(26).setHardness(0.2).setBlockName("Bed"); // TODO: Behavior script
static readonly poweredRail = new Block(27).setHardness(0.7).setBlockName("Powered Rail"); // TODO: Behavior script
static readonly detectorRail = new Block(28).setHardness(0.7).setBlockName("Detector Rail"); // TODO: Behavior script
static readonly stickyPistonBase = new Block(29).setBlockName("Sticky Piston Base"); // TODO: Behavior script
static readonly web = new Block(30).setHardness(4).setLightPassage(255).setBlockName("Web"); // TODO: Behavior script
static readonly tallGrass = new Block(31).setHardness(0).setLightPassage(255).setBehaviour(Behaviour.tallGrass).setBlockName("Tall Grass");
static readonly deadBush = new Block(32).setHardness(0).setLightPassage(255).setBehaviour(Behaviour.tallGrass).setBlockName("Dead Bush"); // TODO: Give it's own behavior script
static readonly pistonBase = new Block(33).setBlockName("Piston Base"); // TODO: Behavior script
static readonly pistonExtension = new Block(34).setBlockName("Piston Extension"); // TODO: Behavior script?
static readonly wool = new Block(35).setHardness(0.8).setBlockName("Wool"); // TODO: Behavior script?
static readonly pistonMoving = new Block(36).setBlockName("Piston Move Event Block Why Is This A Block"); // TODO: Behavior script
static readonly flowerDandelion = new Block(37).setHardness(0).setLightPassage(255).setBehaviour(Behaviour.flower).setBlockName("Dandelion");
static readonly flowerRose = new Block(38).setHardness(0).setLightPassage(255).setBehaviour(Behaviour.flower).setBlockName("Rose");
static readonly brownMushroom = new Block(39).setHardness(0).setLightEmission(0.125).setBlockName("Brown Mushroom"); // TODO: Behavior script
static readonly redMushroom = new Block(40).setHardness(0).setBlockName("Red Mushroom"); // TODO: Behavior script
static readonly goldBlock = new Block(41).setHardness(3).setBlockName("Gold Block");
static readonly ironBlock = new Block(42).setHardness(5).setBlockName("Iron Block");
static readonly doubleSlab = new Block(43).setHardness(2).setBlockName("Slab"); // TODO: Behavior script
static readonly singleSlab = new Block(44).setHardness(2).setBlockName("Slab"); // TODO: Behavior script
static readonly brick = new Block(45).setHardness(2).setBlockName("Brick");
static readonly tnt = new Block(46).setHardness(0).setBlockName("TNT"); // TODO: Behavior script
static readonly bookshelf = new Block(47).setHardness(1.5).setBlockName("Bookshelf"); // TODO: Behavior script
static readonly mossyCobblestone = new Block(48).setHardness(2).setBlockName("Mossy Cobblestone");
static readonly obsidian = new Block(49).setHardness(10).setBlockName("Obsidian"); // TODO: Behavior script?
static readonly torch = new Block(50).setHardness(0).setLightEmission(0.9).setBlockName("Torch"); // TODO: Behavior script
static readonly fire = new Block(51).setHardness(0).setLightEmission(1).setBlockName("Fire"); // TODO: Behavior script
static readonly mobSpawner = new Block(52).setHardness(5).setBlockName("Mob Spawner"); // TODO: Behavior script
static readonly woodenStairs = new Block(53).setBlockName("Wooden Stairs"); // TODO: Behavior script
static readonly chest = new Block(54).setHardness(2.5).setBlockName("Chest"); // TODO: Behavior script
static readonly redstoneDust = new Block(55).setHardness(0).setBlockName("Redstone Dust"); // TODO: Behavior script
static readonly diamondOre = new Block(56).setHardness(3).setBlockName("Diamond Ore"); // TODO: Behavior script
static readonly diamondBlock = new Block(57).setHardness(5).setBlockName("Diamond Block"); // TODO: Behavior script
static readonly craftingTable = new Block(58).setHardness(2.5).setBlockName("Crafting Table"); // TODO: Behavior script
static readonly wheatCrop = new Block(59).setHardness(0).setBlockName("Wheet Crop"); // TODO: Behavior script
static readonly farmland = new Block(60).setHardness(0.6).setBlockName("Farmland"); // TODO: Behavior script
static readonly furnaceIdle = new Block(61).setHardness(3.5).setBlockName("Furnace"); // TODO: Behavior script
static readonly furnaceActive = new Block(62).setHardness(3.5).setBlockName("Furnace"); // TODO: Behavior script
static readonly sign = new Block(63).setHardness(1).setBlockName("Sign"); // TODO: Behavior script
static readonly woodenDoor = new Block(64).setHardness(3).setBlockName("Wooden Door"); // TODO: Behavior script
static readonly ladder = new Block(65).setHardness(0.4).setBlockName("Ladder"); // TODO: Behavior script
static readonly rail = new Block(66).setHardness(0.7).setBlockName("Rail"); // TODO: Behavior script
static readonly cobblestoneStairs = new Block(67).setBlockName("Cobblestone Stairs"); // TODO: Behavior script
static readonly signWall = new Block(68).setHardness(1).setBlockName("Sign"); // TODO: Behavior script
static readonly lever = new Block(69).setHardness(0.5).setBlockName("Lever"); // TODO: Behavior script
static readonly stonePressurePlate = new Block(70).setHardness(0.5).setBlockName("Stone Pressure Plate"); // TODO: Behavior script
static readonly ironDoor = new Block(71).setHardness(5).setBlockName("Iron Door"); // TODO: Behavior script
static readonly woodenPressurePlate = new Block(72).setHardness(0.5).setBlockName("Wooden Pressure Plate"); // TODO: Behavior script
static readonly redstoneOre = new Block(73).setHardness(3).setBehaviour(Behaviour.redstoneOre).setBlockName("Redstone Ore"); // TODO: Behavior script
static readonly redstoneOreGlowing = new Block(74).setHardness(3).setLightEmission(0.625).setBehaviour(Behaviour.redstoneOre).setBlockName("Redstone Ore"); // TODO: Behavior script
static readonly redstoneTorchIdle = new Block(75).setHardness(0).setBlockName("Redstone Torch"); // TODO: Behavior script
static readonly redstoneTorchActive = new Block(76).setHardness(0).setLightEmission(0.5).setBlockName("Redstone Torch"); // TODO: Behavior script
static readonly button = new Block(77).setHardness(0.5).setBlockName("Button"); // TODO: Behavior script
static readonly snow = new Block(78).setHardness(0.1).setBlockName("Snow Layer"); // TODO: Behavior script
static readonly ice = new Block(79).setHardness(0.5).setLightPassage(128).setBlockName("Ice"); // TODO: Behavior script
static readonly snowBlock = new Block(80).setHardness(0.2).setBlockName("Snow"); // TODO: Behavior script
static readonly cactus = new Block(81).setHardness(0.4).setBlockName("Cactus"); // TODO: Behavior script
static readonly clay = new Block(82).setHardness(0.6).setBehaviour(Behaviour.clay).setBlockName("Clay");
static readonly sugarcane = new Block(83).setHardness(0).setLightPassage(255).setBehaviour(Behaviour.sugarcane).setBlockName("Sugar Cane"); // TODO: Behavior script
static readonly jukebox = new Block(84).setHardness(2).setBlockName("Jukebox"); // TODO: Behavior script
static readonly fence = new Block(85).setHardness(2).setBlockName("Fence"); // TODO: Behavior script
static readonly pumpkin = new Block(86).setHardness(1).setBlockName("Pumpkin");
static readonly netherrack = new Block(87).setHardness(0.4).setBlockName("Netherrack");
static readonly soulSand = new Block(88).setHardness(0.5).setBlockName("Soul Sand");
static readonly glowStone = new Block(89).setHardness(0.3).setBlockName("Glowstone"); // TODO: Behavior script
static readonly netherPortal = new Block(90).setUnbreakable().setLightEmission(0.75).setBlockName("Nether Portal"); // TODO: Behavior script
static readonly jackOLantern = new Block(91).setHardness(1).setLightEmission(1).setBlockName("Jack O' Lantern");
static readonly cake = new Block(92).setHardness(0.5).setBlockName("Cake"); // TODO: Behavior script
static readonly redstoneRepeaterIdle = new Block(93).setHardness(0).setBlockName("Redstone Repeater"); // TODO: Behavior script
static readonly redstoneRepeaterActive = new Block(94).setHardness(0).setLightEmission(0.625).setBlockName("Redstone Repeater"); // TODO: Behavior script
static readonly aprilFoolsLockedChest = new Block(95).setHardness(0).setLightEmission(1).setBlockName("Locked Chest");
static readonly trapdoor = new Block(96).setHardness(3).setBlockName("Trapdoor"); // TODO: Behavior script
}

View file

@ -1,16 +0,0 @@
import BlockBehaviour from "./BlockBehaviour";
import Random from "../Random";
import World from "../World";
export default class BlockBehaviourSapling extends BlockBehaviour {
public randomTick(world:World, x:number, y:number, z:number, random:Random) {
if (world.getBlockLight(x, y + 1, z) >= 9 && random.nextInt(30) === 0) {
const blockMetadata = world.getBlockMetadata(x, y, z);
if ((blockMetadata & 8) === 0) {
world.setBlockMetadataWithNotify(x, y, z, blockMetadata | 8);
} else {
console.log("UNIMPLEMENTED TREE GROW!!");
}
}
}
}

View file

@ -1,46 +0,0 @@
import AABB from "../AABB";
import Block from "./Block";
import BlockBehaviour from "./BlockBehaviour";
import World from "../World";
export default class BlockBehaviourSugarcane extends BlockBehaviour {
public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {
const block = world.getBlockId(x, y - 1, z);
if (block === 0 || block !== Block.sugarcane.blockId) {
world.setBlockWithNotify(x, y, z, 0);
}
}
public canPlaceBlockAt(world: World, x: number, y: number, z: number) {
const blockBelow = world.getBlockId(x, y - 1, z);
if (blockBelow === this.block.blockId) {
return true;
}
// Check if right ground block
if (blockBelow !== Block.grass.blockId && blockBelow !== Block.dirt.blockId) {
return false;
}
// Check if water is around
if (world.getBlockId(x - 1, y - 1, z) === Block.waterStill.blockId) {
return true;
}
if (world.getBlockId(x + 1, y - 1, z) === Block.waterStill.blockId) {
return true;
}
if (world.getBlockId(x, y - 1, z - 1) === Block.waterStill.blockId) {
return true;
}
if (world.getBlockId(x, y - 1, z + 1) === Block.waterStill.blockId) {
return true;
}
return false;
}
public getBoundingBox() {
return AABB.getAABB(0, 0, 0, 0, 0, 0);
}
}

View file

@ -1,16 +0,0 @@
import AABB from "../AABB";
import Block from "./Block";
import IBlockBehaviour from "./IBlockBehaviour";
import Random from "../Random";
import World from "../World";
export default class BlockBehaviour implements IBlockBehaviour {
public block!:Block;
public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {}
public droppedItem(blockId:number) { return blockId; }
public droppedCount(blockId:number) { return 1; }
public getBoundingBox(x:number, y:number, z:number) { return AABB.getAABB(0 + x, 0 + y, 0 + z, 1 + x, 1 + y, 1 + z); }
public randomTick(world:World, x:number, y:number, z:number, random:Random) {}
public canPlaceBlockAt(world:World, x:number, y:number, z:number) { return true; }
}

View file

@ -1,12 +0,0 @@
import BlockBehaviour from "./BlockBehaviour";
import Item from "../items/Item";
export default class BlockBehaviourClay extends BlockBehaviour {
public droppedItem(blockId:number) {
return Item.clay.shiftedItemID;
}
public droppedCount(blockId:number) {
return 4;
}
}

View file

@ -1,17 +0,0 @@
import AABB from "../AABB";
import Block from "./Block";
import BlockBehaviour from "./BlockBehaviour";
import World from "../World";
export default class BlockBehaviourFlower extends BlockBehaviour {
public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {
const block = world.getBlockId(x, y - 1, z);
if (block !== Block.grass.blockId && block !== Block.dirt.blockId) {
world.setBlockWithNotify(x, y, z, 0);
}
}
public getBoundingBox() {
return AABB.getAABB(0, 0, 0, 0, 0, 0);
}
}

View file

@ -1,8 +0,0 @@
import Block from "./Block";
import BlockBehaviour from "./BlockBehaviour";
export default class BlockBehaviourGrass extends BlockBehaviour {
public droppedItem(blockId:number) {
return Block.dirt.blockId;
}
}

View file

@ -1,12 +0,0 @@
import BlockBehaviour from "./BlockBehaviour";
import Item from "../items/Item";
export default class BlockBehaviourOre extends BlockBehaviour {
public droppedItem(blockId:number) {
return Item.clay.shiftedItemID;
}
public droppedCount(blockId:number) {
return 4;
}
}

View file

@ -1,12 +0,0 @@
import BlockBehaviour from "./BlockBehaviour";
import Item from "../items/Item";
export default class BlockBehaviourRedstoneOre extends BlockBehaviour {
public droppedItem(blockId:number) {
return Item.clay.shiftedItemID;
}
public droppedCount(blockId:number) {
return 4;
}
}

View file

@ -1,8 +0,0 @@
import Block from "./Block";
import BlockBehaviour from "./BlockBehaviour";
export default class BlockBehaviourStone extends BlockBehaviour {
public droppedItem(blockId:number) {
return Block.cobblestone.blockId;
}
}

View file

@ -1,21 +0,0 @@
import AABB from "../AABB";
import Block from "./Block";
import BlockBehaviour from "./BlockBehaviour";
import World from "../World";
export default class BlockBehaviourTallGrass extends BlockBehaviour {
public neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number) {
const block = world.getBlockId(x, y - 1, z);
if (block !== Block.grass.blockId && block !== Block.dirt.blockId) {
world.setBlockWithNotify(x, y, z, 0);
}
}
public droppedItem(blockId:number) {
return -1;
}
public getBoundingBox() {
return AABB.getAABB(0, 0, 0, 0, 0, 0);
}
}

View file

@ -1,15 +0,0 @@
import AABB from "../AABB";
import Block from "./Block";
import Random from "../Random";
import World from "../World";
export default interface IBlockBehaviour {
block:Block,
neighborBlockChange(world:World, x:number, y:number, z:number, blockId:number): void,
droppedItem: (blockId:number) => number,
droppedCount: (blockId:number) => number,
getBoundingBox: (x:number, y:number, z:number) => AABB,
randomTick: (world:World, x:number, y:number, z:number, random:Random) => void,
canPlaceBlockAt: (world:World, x:number, y:number, z:number) => boolean
}

225
server/bufferStuff.js Normal file
View file

@ -0,0 +1,225 @@
/*
===========- 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;
}
}

130
server/chunkManager.js Normal file
View file

@ -0,0 +1,130 @@
/*
===========- 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);
}
}

View file

@ -1,314 +0,0 @@
import { IReader, IWriter } from "bufferstuff";
import { MetadataEntry, MetadataWriter } from "../MetadataWriter";
import AABB from "../AABB";
import Block from "../blocks/Block";
import Chunk from "../Chunk";
import IEntity from "./IEntity";
import MetadataFieldType from "../enums/MetadataFieldType";
import PacketEntityLook from "../packets/EntityLook";
import PacketEntityLookRelativeMove from "../packets/EntityLookRelativeMove";
import PacketEntityMetadata from "../packets/EntityMetadata";
import PacketEntityRelativeMove from "../packets/EntityRelativeMove";
import PacketEntityTeleport from "../packets/EntityTeleport";
import PacketEntityVelocity from "../packets/EntityVelocity";
import Player from "./Player";
import Rotation from "../Rotation";
import Vec2 from "../Vec2";
import Vec3 from "../Vec3";
import World from "../World";
export default class Entity implements IEntity {
public static nextEntityId:number = 0;
public entityId:number;
public entitySize:Vec2;
public world:World;
public position:Vec3;
public lastPosition:Vec3;
public absPosition:Vec3;
public lastAbsPosition:Vec3;
public motion:Vec3;
private moveEntityBlockPosRel:Vec3;
public rotation:Rotation;
public lastRotation:Rotation;
public absRotation:Rotation;
public lastAbsRotation:Rotation;
public health:number;
public wasHurt:boolean;
public isDead:boolean;
public fire:number;
public fallDistance:number;
public onGround:boolean;
public chunk:Chunk;
public crouching:boolean;
private lastCrouchState:boolean;
private lastFireState:boolean;
public entityAABB:AABB;
public readonly isPlayer:boolean;
private queuedChunkUpdate:boolean;
public markedForDisposal:boolean = false;
public constructor(world:World, isPlayer:boolean = false) {
this.entityId = Entity.nextEntityId++;
this.isPlayer = isPlayer;
this.entitySize = new Vec2(0.6, 1.8);
this.entityAABB = new AABB(-this.entitySize.x / 2, 0, -this.entitySize.x / 2, this.entitySize.x / 2, this.entitySize.y, this.entitySize.x / 2);
this.fire = this.fallDistance = 0;
this.onGround = false;
this.world = world;
this.position = new Vec3();
this.lastPosition = new Vec3();
this.absPosition = new Vec3();
this.lastAbsPosition = new Vec3();
this.motion = new Vec3();
this.moveEntityBlockPosRel = new Vec3();
this.rotation = new Rotation();
this.lastRotation = new Rotation();
this.absRotation = new Rotation();
this.lastAbsRotation = new Rotation();
this.crouching = this.lastCrouchState = this.lastFireState = this.queuedChunkUpdate = false;
this.chunk = world.getChunk(this.position.x >> 4, this.position.z >> 4);
this.health = 20;
this.wasHurt = false;
this.isDead = false;
}
public fromSave(reader:IReader) {
this.position.set(reader.readDouble(), reader.readDouble(), reader.readDouble());
this.motion.set(reader.readFloat(), reader.readFloat(), reader.readFloat());
this.rotation.set(reader.readFloat(), reader.readFloat());
this.fire = reader.readShort();
this.fallDistance = reader.readFloat();
this.health = reader.readByte();
}
public toSave(writer:IWriter) {
writer.writeDouble(this.position.x).writeDouble(this.position.y).writeDouble(this.position.z) // Position
.writeFloat(this.motion.x).writeFloat(this.motion.y).writeFloat(this.motion.z) // Motion
.writeFloat(this.rotation.x).writeFloat(this.rotation.y) // Rotation
.writeShort(this.fire)
.writeFloat(this.fallDistance)
.writeByte(this.health);
}
async collidesWithPlayer(aabb:AABB) {
let collidedWith:Player | undefined;
await this.world.players.forEach(player => {
if (this.entityAABB.intersects(player.entityAABB) && collidedWith == undefined) {
collidedWith = player;
}
});
return collidedWith;
}
sendToNearby(buffer:Buffer) {
this.world.sendToNearbyClients(this, buffer);
}
sendToAllNearby(buffer:Buffer) {
this.world.sendToNearbyAllNearbyClients(this, buffer);
}
updateMetadata() {
const crouchStateChanged = this.crouching !== this.lastCrouchState;
const fireStateChanged = this.fire > 0 !== this.lastFireState;
if (crouchStateChanged || fireStateChanged) {
const metadata = new MetadataWriter();
// Flags:
// 1 = On Fire
// 2 = Player crouched
// 4 = Player on mount?
metadata.addMetadataEntry(0, new MetadataEntry(MetadataFieldType.Byte, Number(this.fire > 0) + Number(this.crouching) * 2));
this.sendToNearby(new PacketEntityMetadata(this.entityId, metadata.writeBuffer()).writeData());
this.lastCrouchState = this.crouching;
this.lastFireState = this.fire > 0;
}
}
distanceTo(entity:IEntity) {
const dX = entity.position.x - this.position.x,
dY = entity.position.y - this.position.y,
dZ = entity.position.z - this.position.z;
return Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2) + Math.pow(dZ, 2));
}
damageFrom(damage:number, entity?:IEntity) {
if (this.health <= 0) {
return;
}
if (entity === undefined) {
this.health -= damage;
} else {
this.health -= damage;
}
this.wasHurt = true;
}
updateEntityChunk() {
const bitX = this.position.x >> 4;
const bitZ = this.position.z >> 4;
if (bitX != this.lastPosition.x >> 4 || bitZ != this.lastPosition.z >> 4 || this.queuedChunkUpdate) {
if (this.world.chunkExists(bitX, bitZ)) {
this.chunk = this.world.getChunk(bitX, bitZ);
this.queuedChunkUpdate = false;
} else {
this.queuedChunkUpdate = true;
}
}
}
private constrainRot(rot:number) {
return Math.min(Math.max(rot, -128), 127);
}
private sendPositionUpdate() {
this.absPosition.set(Math.floor(this.position.x * 32), Math.floor(this.position.y * 32), Math.floor(this.position.z * 32));
const yaw = this.rotation.yaw / 256 * 180;
const pitch = this.rotation.pitch / 256 * 180;
this.absRotation.set(
Math.floor(yaw - Math.floor((yaw + 128) / 256) * 256),
Math.floor(pitch - Math.floor((pitch + 128) / 256) * 256)
);
const diffX = this.absPosition.x - this.lastAbsPosition.x;
const diffY = this.absPosition.y - this.lastAbsPosition.y;
const diffZ = this.absPosition.z - this.lastAbsPosition.z;
const diffYaw = this.absRotation.yaw - this.lastAbsRotation.yaw;
const diffPitch = this.absRotation.pitch - this.lastAbsRotation.pitch;
const doRelativeMove = Math.abs(diffX) >= 4 || Math.abs(diffY) >= 4 || Math.abs(diffZ) >= 4;
const doLook = Math.abs(diffYaw) >= 4 || Math.abs(diffPitch) >= 4;
if (Math.abs(diffX) > 128 || Math.abs(diffY) > 128 || Math.abs(diffZ) > 128) {
this.sendToNearby(new PacketEntityTeleport(this.entityId, this.absPosition.x, this.absPosition.y, this.absPosition.z, this.absRotation.yaw, this.absRotation.pitch).writeData());
} else if (doRelativeMove && doLook) {
this.sendToNearby(new PacketEntityLookRelativeMove(this.entityId, diffX, diffY, diffZ, this.absRotation.yaw, this.absRotation.pitch).writeData());
} else if (doRelativeMove) {
this.sendToNearby(new PacketEntityRelativeMove(this.entityId, diffX, diffY, diffZ).writeData());
} else if (doLook) {
this.sendToNearby(new PacketEntityLook(this.entityId, this.absRotation.yaw, this.absRotation.pitch).writeData());
}
if (!this.motion.isZero) {
this.sendToNearby(new PacketEntityVelocity(this.entityId, this.motion.x, this.motion.y, this.motion.z).writeData());
}
if (doRelativeMove) {
this.lastAbsPosition.set(this.absPosition);
}
if (doLook) {
this.lastAbsRotation.set(this.absRotation);
}
}
fall(distance:number) {
// TODO: Entity falling mount transfer
}
kill() {
this.health = 0;
this.markedForDisposal = true;
}
updateFalling(distance:number) {
if (this.onGround) {
if (this.fallDistance > 0) {
this.fall(this.fallDistance);
this.fallDistance = 0;
}
} else if (distance < 0) {
this.fallDistance -= distance;
}
}
private getBlockAABBFor(x:number, y:number, z:number) {
const blockId = this.chunk.getBlockId(x, y, z);
const blockEntityIsTouching = blockId > 0 ? Block.blocks[blockId] : null;
if (blockEntityIsTouching != null) {
return blockEntityIsTouching.getBoundingBox(Math.floor(this.position.x), Math.floor(this.position.y), Math.floor(this.position.z));
}
return null;
}
moveEntity(motionX:number, motionY:number, motionZ:number) {
this.position.add(motionX, motionY, motionZ);
this.entityAABB.move(this.position);
let blockAABB = this.getBlockAABBFor(Math.floor(this.position.x) & 0xf, Math.floor(this.position.y), Math.floor(this.position.z) & 0xf);
if (blockAABB !== null) {
this.moveEntityBlockPosRel.set(this.position);
this.moveEntityBlockPosRel.sub(Math.floor(this.position.x), Math.floor(this.position.y), Math.floor(this.position.z));
// TODO: Handle X and Z collisions.
if (this.entityAABB.intersects(blockAABB)) {
const intersectionY = this.entityAABB.intersectionY(blockAABB);
if (this.moveEntityBlockPosRel.y > 0.5) {
motionY = intersectionY;
} else {
motionY = -intersectionY;
}
}
this.position.add(0, motionY, 0);
this.motion.y = 0;
this.onGround = true;
}
}
onTick() {
this.updateMetadata();
this.updateEntityChunk();
this.updateFalling(this.motion.y);
if (this.fire > 0) {
if (this.fire % 20 === 0) {
this.damageFrom(1);
}
this.fire--;
}
if (!this.isDead && this.health <= 0) {
this.isDead = true;
}
if (this.wasHurt) {
this.wasHurt = false;
}
this.sendPositionUpdate();
this.lastPosition.set(this.position);
}
}

View file

@ -1,61 +0,0 @@
import Entity from "./Entity";
import ItemStack from "../inventories/ItemStack";
import World from "../World";
export default class EntityItem extends Entity {
public age:number;
public itemStack:ItemStack;
public pickupDelay:number;
public constructor(world:World, itemStack:ItemStack) {
super(world);
this.itemStack = itemStack;
this.entitySize.set(0.2, 0.2);
this.pickupDelay = 0;
this.motion.set(Math.random() * 0.2 - 0.1, 0.2, Math.random() * 0.2 - 0.1);
this.age = 0;
this.health = 5;
}
async onTick() {
super.onTick();
if (this.pickupDelay > 0) {
this.pickupDelay--;
} else {
let playerCollided = await this.collidesWithPlayer(this.entityAABB);
if (playerCollided !== undefined) {
playerCollided.inventory.addItemStack(this.itemStack);
playerCollided.itemPickup(this, this.itemStack.size);
if (this.itemStack.size <= 0) {
this.kill();
}
}
}
this.motion.add(0, -0.04, 0);
this.moveEntity(this.motion.x, this.motion.y, this.motion.z);
let xyMult = 0.98;
if (this.onGround) {
xyMult = 0.59;
}
// TODO: Change the x and z based on the slipperiness of a block
this.motion.mult(xyMult, 0.98, xyMult);
if (this.onGround) {
this.motion.y *= -0.5;
}
this.age++;
if (this.age >= 6000) {
this.kill();
}
}
}

View file

@ -1,84 +0,0 @@
import { IReader, IWriter } from "bufferstuff";
import Block from "../blocks/Block";
import Entity from "./Entity";
import EntityStatus from "../enums/EntityStatus";
import IEntity from "./IEntity";
import PacketEntityStatus from "../packets/EntityStatus";
import World from "../World";
export default class EntityLiving extends Entity {
public timeInWater:number;
public headHeight:number;
public lastHealth:number;
public constructor(world:World, isPlayer:boolean = false) {
super(world, isPlayer);
this.timeInWater = 0;
this.headHeight = 1.62;
this.lastHealth = this.health;
}
public fromSave(reader:IReader) {
super.fromSave(reader);
this.timeInWater = reader.readShort();
}
public toSave(writer:IWriter) {
super.toSave(writer);
writer.writeShort(this.timeInWater == Number.MIN_SAFE_INTEGER ? 0 : this.timeInWater);
}
damageFrom(damage:number, entity?:IEntity) {
super.damageFrom(damage, entity);
if (this.health <= 0) {
this.isDead = true;
}
// Send Damage Animation packet or death packet
if (this.isDead) {
this.sendToAllNearby(new PacketEntityStatus(this.entityId, EntityStatus.Dead).writeData());
} else {
this.sendToAllNearby(new PacketEntityStatus(this.entityId, EntityStatus.Hurt).writeData());
}
}
isInWater(fromHead:boolean) {
return this.world.getChunkBlockId(this.chunk, this.position.x, this.position.y + (fromHead ? this.headHeight : 0), this.position.z) === Block.waterStill.blockId;
}
fall(distance:number) {
const adjustedFallDistance = Math.ceil(distance - 3);
if (adjustedFallDistance > 0) {
this.damageFrom(adjustedFallDistance);
}
}
onTick() {
super.onTick();
if (!this.isPlayer && !this.motion.isZero) {
this.moveEntity(this.motion.x, this.motion.y, this.motion.z);
}
// Drowning
if (this.isInWater(true)) {
if (this.timeInWater == Number.MIN_SAFE_INTEGER) {
this.timeInWater = 320;
}
if (this.timeInWater <= 0 && this.timeInWater % 20 === 0) {
this.damageFrom(1);
}
this.timeInWater--;
} else {
this.timeInWater = Number.MIN_SAFE_INTEGER;
}
if (this.isInWater(false)) {
this.fallDistance = 0;
}
}
}

View file

@ -1,16 +0,0 @@
import AABB from "../AABB"
import Vec3 from "../Vec3"
export default interface IEntity {
entityId:number,
position:Vec3,
motion:Vec3,
lastPosition:Vec3,
crouching:boolean,
isDead:boolean,
markedForDisposal:boolean,
entityAABB:AABB,
updateMetadata:() => void,
distanceTo:(entity:IEntity) => number,
onTick:() => void
}

View file

@ -1,201 +0,0 @@
import { IReader, IWriter } from "bufferstuff";
import Block from "../blocks/Block";
import Chunk from "../Chunk";
import Entity from "./Entity";
import EntityItem from "./EntityItem";
import EntityLiving from "./EntityLiving";
import Item from "../items/Item";
import ItemStack from "../inventories/ItemStack";
import MinecraftServer from "../MinecraftServer";
import MPClient from "../MPClient";
import PacketCollectItem from "../packets/CollectItem";
import PacketEntityEquipment from "../packets/EntityEquipment";
import PacketMapChunk from "../packets/MapChunk";
import PacketPreChunk from "../packets/PreChunk";
import PacketUpdateHealth from "../packets/UpdateHealth";
import PlayerInventory from "../inventories/PlayerInventory";
import World from "../World";
const CHUNK_LOAD_RANGE = 15;
export default class Player extends EntityLiving {
public username:string;
private server:MinecraftServer;
private firstUpdate:boolean;
public loadedChunks:Array<number>;
public justUnloaded:Array<number>;
public mpClient?:MPClient;
public inventory:PlayerInventory;
public trackedEquipment:Array<ItemStack | null>;
public constructor(server:MinecraftServer, world:World, username:string) {
super(world, true);
this.server = server;
this.firstUpdate = true;
this.loadedChunks = new Array<number>();
this.justUnloaded = new Array<number>();
this.inventory = new PlayerInventory(this);
this.inventory.setSlotItemStack(36, new ItemStack(Item.ironSword, 1));
this.inventory.setSlotItemStack(37, new ItemStack(Item.ironPickaxe, 1));
this.inventory.setSlotItemStack(38, new ItemStack(Item.ironShovel, 1));
this.inventory.setSlotItemStack(39, new ItemStack(Item.ironAxe, 1));
this.inventory.setSlotItemStack(43, new ItemStack(Block.dirt, 32));
this.trackedEquipment = new Array<ItemStack | null>();
for (let i = 0; i < 5; i++) {
this.trackedEquipment.push(null);
}
this.username = username;
this.position.set(8, 64, 8);
}
public fromSave(reader:IReader) {
super.fromSave(reader);
this.inventory.fromSave(reader);
}
public toSave(writer:IWriter) {
super.toSave(writer);
this.inventory.toSave(writer);
}
// Forces a player chunk update *next tick*
public forceUpdatePlayerChunks() {
this.firstUpdate = true;
}
public itemPickup(entity:Entity, stackSize:number) {
if (!this.isDead) {
if (entity instanceof EntityItem) {
this.sendToAllNearby(new PacketCollectItem(entity.entityId, this.entityId).writeData());
}
}
}
dropAllItems() {
for (const itemStack of this.inventory.itemStacks) {
if (itemStack) {
const item = new EntityItem(this.world, itemStack);
item.position.set(this.position);
this.world.addEntity(item);
}
}
}
private async updatePlayerChunks() {
const bitX = this.position.x >> 4;
const bitZ = this.position.z >> 4;
if (bitX != this.lastPosition.x >> 4 || bitZ != this.lastPosition.z >> 4 || this.firstUpdate) {
if (this.firstUpdate) {
this.firstUpdate = false;
// TODO: Make this based on the player's initial coords
this.mpClient?.send(new PacketPreChunk(0, 0, true).writeData());
const chunk = await this.world.getChunkSafe(0, 0);
const chunkData = await (new PacketMapChunk(0, 0, 0, 15, 127, 15, chunk).writeData());
this.mpClient?.send(chunkData);
}
// Load or keep any chunks we need
const currentLoads = [];
for (let x = bitX - CHUNK_LOAD_RANGE; x < bitX + CHUNK_LOAD_RANGE; x++) {
for (let z = bitZ - CHUNK_LOAD_RANGE; z < bitZ + CHUNK_LOAD_RANGE; z++) {
const coordPair = Chunk.CreateCoordPair(x, z);
if (!this.loadedChunks.includes(coordPair)) {
const chunk = await this.world.getChunkSafe(x, z);
this.mpClient?.send(new PacketPreChunk(x, z, true).writeData());
this.loadedChunks.push(coordPair);
chunk.playersInChunk.set(this.entityId, this);
const chunkData = await (new PacketMapChunk(x, 0, z, 15, 127, 15, chunk).writeData());
this.mpClient?.send(chunkData);
}
currentLoads.push(coordPair);
}
}
// Mark any unaccounted chunks for unload
for (const coordPair of this.loadedChunks) {
if (!currentLoads.includes(coordPair) && this.world.chunkExists(coordPair)) {
this.justUnloaded.push(coordPair);
const chunkToUnload = this.world.getChunkByCoordPair(coordPair);
this.mpClient?.send(new PacketPreChunk(chunkToUnload.x, chunkToUnload.z, false).writeData());
}
}
// Overwrite loaded chunks
this.loadedChunks = currentLoads;
}
}
private getEquipmentForVirtualSlot(slot:number) {
if (slot === 0) {
return this.mpClient?.getHeldItemStack() ?? null;
} else {
this.inventory.getSlotItemStack(4 + slot); // 5 - 8
}
return null;
}
private sendEquipment(equipmentId:number, itemStack:ItemStack | null) {
this.sendToNearby(new PacketEntityEquipment(this.entityId, equipmentId, itemStack == null ? -1 : itemStack.itemID, itemStack == null ? 0 : itemStack.damage).writeData());
}
private sendEquipmentPlayer(mpClient:MPClient, equipmentId:number, itemStack:ItemStack | null) {
mpClient.send(new PacketEntityEquipment(this.entityId, equipmentId, itemStack == null ? -1 : itemStack.itemID, itemStack == null ? 0 : itemStack.damage).writeData());
}
// For login.
public sendPlayerEquipment(playerToSendTo:Player) {
const mpClient = playerToSendTo.mpClient;
if (mpClient == null) {
return;
}
for (let slotId = 0; slotId < 5; slotId++) {
const itemStack = this.getEquipmentForVirtualSlot(slotId);
const trackedEquipment = this.trackedEquipment[slotId];
if ((itemStack == null || trackedEquipment == null) || !itemStack.compare(trackedEquipment)) {
this.trackedEquipment[slotId] = itemStack;
this.sendEquipmentPlayer(mpClient, slotId, itemStack);
}
}
}
public onTick() {
this.updatePlayerChunks();
// Calculate player motion since we don't have it serverside.
this.motion.set(this.position.x - this.lastPosition.x, this.position.y - this.lastPosition.y, this.position.z - this.lastPosition.z);
if (!this.motion.isZero) {
this.entityAABB.move(this.position);
}
super.onTick();
for (let slotId = 0; slotId < 5; slotId++) {
const itemStack = this.getEquipmentForVirtualSlot(slotId);
const trackedEquipment = this.trackedEquipment[slotId];
if ((itemStack == null || trackedEquipment == null) || !itemStack.compare(trackedEquipment)) {
this.trackedEquipment[slotId] = itemStack;
this.sendEquipment(slotId, itemStack);
}
}
if (this.health != this.lastHealth) {
if (this.health <= 0 && this.isDead) {
this.dropAllItems();
}
this.lastHealth = this.health;
this.mpClient?.send(new PacketUpdateHealth(this.health).writeData());
}
}
}

View file

@ -1,11 +0,0 @@
enum Animation {
None = 0,
SwingArm = 1,
Damage = 2,
LeaveBed = 3,
Unknown102 = 102,
Crouch = 104,
Uncrouch = 105,
}
export default Animation;

View file

@ -1,10 +0,0 @@
enum EntityStatus {
Unknown0,
Unknown1,
Hurt,
Dead,
Unknown4,
Unknown5
}
export default EntityStatus;

View file

@ -1,9 +0,0 @@
enum MaxUses {
GOLD = 32,
WOOD = 59,
STONE = 131,
IRON = 250,
DIAMOND = 1561
}
export default MaxUses;

View file

@ -1,11 +0,0 @@
enum MetadataFieldType {
Byte = 0,
Short = 1,
Int = 2,
Float = 3,
String = 4,
Item = 5,
Vector = 6
}
export default MetadataFieldType;

View file

@ -1,54 +0,0 @@
// https://wiki.vg/index.php?title=Protocol&oldid=488
enum Packet {
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,
PlayerDigging = 0x0E,
PlayerBlockPlacement = 0x0F,
HoldingChange = 0x10,
UseBed = 0x11,
Animation = 0x12,
EntityAction = 0x13,
NamedEntitySpawn = 0x14,
PickupSpawn = 0x15,
CollectItem = 0x16,
EntityVelocity = 0x1C,
DestroyEntity = 0x1D,
EntityStatus = 0x26,
EntityMetadata = 0x28,
PreChunk = 0x32,
MapChunk = 0x33,
MultiBlockChange = 0x34,
BlockChange = 0x035,
SoundEffect = 0x3D,
Entity = 0x1E,
EntityRelativeMove = 0x1F,
EntityLook = 0x20,
EntityLookRelativeMove = 0x21,
EntityTeleport = 0x22,
CloseWindow = 0x65,
WindowClick = 0x66,
SetSlot = 0x67,
WindowItems = 0x68,
DisconnectKick = 0xff
}
export default Packet;

View file

@ -1,7 +0,0 @@
enum SaveCompressionType {
NONE = 0,
DEFLATE = 1,
XZ = 2
}
export default SaveCompressionType;

View file

@ -1,12 +0,0 @@
enum SoundEffects {
CLICK2 = 1000,
CLICK1 = 1001,
BOW_FIRE = 1002,
DOOR_TOGGLE = 1003,
EXTINGUISH = 1004,
RECORD_PLAY = 1005,
SMOKE = 2000,
BLOCK_BREAK = 2001
}
export default SoundEffects;

View file

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

View file

@ -1,23 +0,0 @@
import Block from "../../blocks/Block";
import Chunk from "../../Chunk";
import IGenerator from "../IGenerator";
export default 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

@ -1,216 +0,0 @@
import { Noise2D, makeNoise2D } from "../../../external/OpenSimplex2D";
import { Noise3D, makeNoise3D } from "../../../external/OpenSimplex3D";
import Block from "../../blocks/Block";
import Chunk from "../../Chunk";
import IGenerator from "../IGenerator";
import mulberry32 from "../../mulberry32";
import QueuedBlockUpdate from "../../queuedUpdateTypes/BlockUpdate";
export default class HillyGenerator implements IGenerator {
private seed:number;
seedGenerator:() => number;
private generator:Noise2D;
private generator1:Noise2D;
private generator2:Noise2D;
private generator3:Noise2D;
private generator4:Noise2D;
private generator5:Noise2D;
private generator6:Noise2D;
private oceanGenerator:Noise2D;
private hillGenerator:Noise2D;
private caveGenerator1:Noise3D;
private caveGenerator2:Noise3D;
private caveGenerator3:Noise3D;
private caveGenerator4:Noise3D;
private underwaterGravelGenerator:Noise2D;
private underwaterSandGenerator:Noise2D;
private underwaterClayGenerator:Noise2D;
private flowerGenerator:Noise2D;
public constructor(seed:number) {
this.seed = seed;
this.seedGenerator = mulberry32(this.seed);
this.generator = this.createGenerator2D();
this.generator1 = this.createGenerator2D();
this.generator2 = this.createGenerator2D();
this.generator3 = this.createGenerator2D();
this.generator4 = this.createGenerator2D();
this.generator5 = this.createGenerator2D();
this.generator6 = this.createGenerator2D();
this.oceanGenerator = this.createGenerator2D();
this.hillGenerator = this.createGenerator2D();
this.caveGenerator1 = this.createGenerator3D();
this.caveGenerator2 = this.createGenerator3D();
this.caveGenerator3 = this.createGenerator3D();
this.caveGenerator4 = this.createGenerator3D();
this.underwaterGravelGenerator = this.createGenerator2D();
this.underwaterSandGenerator = this.createGenerator2D();
this.underwaterClayGenerator = this.createGenerator2D();
this.flowerGenerator = this.createGenerator2D();
}
private createGenerator2D() {
return makeNoise2D(this.seedGenerator() * Number.MAX_SAFE_INTEGER);
}
private createGenerator3D() {
return makeNoise3D(this.seedGenerator() * Number.MAX_SAFE_INTEGER);
}
public generate(chunk:Chunk) {
const treeRNG = mulberry32(this.seed + chunk.x + chunk.z);
const sugarcaneRNG = mulberry32(this.seed + chunk.x + chunk.z);
const grassRNG = mulberry32(this.seed + chunk.x + chunk.z);
const flowerRNG = mulberry32(this.seed + chunk.x + chunk.z);
let colY = 0, colDirtMin = 0, colWaterY = 0, orgColY = 0;
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
const oceanValue = this.oceanGenerator((chunk.x * 16 + x) / 128, (chunk.z * 16 + z) / 128) * 100;
orgColY = colWaterY = colY = 60 + Math.round((
this.generator((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16) * 16 +
this.generator1((chunk.z * 16 + z) / 16, (chunk.x * 16 + x) / 16) * 16 +
this.generator2((chunk.x * 16 + x) / 8, (chunk.z * 16 + z) / 8) * 8 +
this.generator3((chunk.z * 16 + z) / 4, (chunk.x * 16 + x) / 4) * 4 +
this.generator4((chunk.x * 16 + x) / 4, (chunk.z * 16 + z) / 4) * 4 +
this.generator5((chunk.z * 16 + z) / 10, (chunk.x * 16 + x) / 10) * 10 +
this.generator6((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16) * 16 +
oceanValue +
(Math.max(this.hillGenerator((chunk.x * 16 + x) / 128, (chunk.z * 16 + z) / 128), 0) * 50 + Math.min(oceanValue, 0))
) / 9);
colDirtMin = colY - 2;
const sandNoise = this.underwaterSandGenerator((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16);
if (colY <= 60 && sandNoise > 0.5) {
chunk.setBlock(Block.sand.blockId, x, colY, z);
} else {
chunk.setBlock(Block.grass.blockId, x, colY, z);
}
let caveY = colY + 1;
while (colY-- > 0) {
if (colY >= colDirtMin) {
chunk.setBlock(Block.dirt.blockId, x, colY, z);
} else if (colY === 0) {
chunk.setBlock(Block.bedrock.blockId, x, colY, z);
} else {
chunk.setBlock(Block.stone.blockId, x, colY, z);
}
}
// Generate underwater blocks
if (colWaterY <= 58) {
if (this.underwaterGravelGenerator((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16) > 0.3) {
chunk.setBlock(Block.gravel.blockId, x, colWaterY, z);
} else if (sandNoise > 0.4) {
chunk.setBlock(Block.sand.blockId, x, colWaterY, z);
} else if (this.underwaterClayGenerator((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16) > 0.5) {
chunk.setBlock(Block.clay.blockId, x, colWaterY, z);
} else {
chunk.setBlock(Block.dirt.blockId, x, colWaterY, z);
}
}
while (colWaterY <= 58) {
colWaterY++;
chunk.setBlock(Block.waterStill.blockId, x, colWaterY, z);
}
while (caveY-- > 1) {
if (
((this.caveGenerator1((chunk.x * 16 + x) / 16, caveY / 16, (chunk.z * 16 + z) / 16) +
this.caveGenerator2((chunk.x * 16 + x) / 8, caveY / 8, (chunk.z * 16 + z) / 8)) / 2) > 0.45
|| this.caveGenerator3((chunk.x * 16 + x) / 16, caveY / 16, (chunk.z * 16 + z) / 16) > 0.6 ||
this.caveGenerator4((chunk.x * 16 + x) / 8, caveY / 8, (chunk.z * 16 + z) / 8) > 0.6
) {
if (caveY <= 3) {
chunk.setBlock(Block.lavaStill.blockId, x, caveY, z);
} else {
chunk.setBlock(0, x, caveY, z);
}
}
}
const queuedChunkBlocks = chunk.world.queuedChunkBlocks;
if (queuedChunkBlocks.length > 0) {
const thisCoordPair = Chunk.CreateCoordPair(chunk.x, chunk.z);
for (let i = queuedChunkBlocks.length - 1; i >= 0; i--) {
const blockUpdate = queuedChunkBlocks[i];
if (blockUpdate instanceof QueuedBlockUpdate && blockUpdate.coordPair === thisCoordPair) {
queuedChunkBlocks.splice(i, 1);
chunk.setBlockWithMetadata(blockUpdate.blockId, blockUpdate.metadata, blockUpdate.x, blockUpdate.y, blockUpdate.z);
}
}
}
// Grass and flowers
if (chunk.getBlockId(x, orgColY, z) === Block.grass.blockId) {
if (grassRNG() > 0.9) {
chunk.setBlockWithMetadata(Block.tallGrass.blockId, 1, x, orgColY + 1, z);
} else if (this.flowerGenerator((chunk.x * 16 + x) / 16, (chunk.z * 16 + z) / 16) > 0.5 && flowerRNG() > 0.9) {
if (flowerRNG() > 0.4) {
chunk.setBlockWithMetadata(Block.flowerRose.blockId, 1, x, orgColY + 1, z);
} else {
chunk.setBlockWithMetadata(Block.flowerDandelion.blockId, 1, x, orgColY + 1, z);
}
}
}
if (
sugarcaneRNG() > 0.992 &&
chunk.getBlockId(x, orgColY + 1, z) !== Block.waterStill.blockId &&
chunk.getBlockId(x, orgColY, z) === Block.grass.blockId &&
(((x - 1) < 0 ? false : chunk.getBlockId(x - 1, orgColY, z) === Block.waterStill.blockId) ||
((x + 1) > 15 ? false : chunk.getBlockId(x + 1, orgColY, z) === Block.waterStill.blockId) ||
((z - 1) < 0 ? false : chunk.getBlockId(x, orgColY, z - 1) === Block.waterStill.blockId) ||
((z + 1) > 15 ? false : chunk.getBlockId(x, orgColY, z + 1) === Block.waterStill.blockId))
) {
let sugarcaneYHeight = 2 + Math.round(sugarcaneRNG() * 2.5);
while (sugarcaneYHeight > 0) {
chunk.setBlock(Block.sugarcane.blockId, x, orgColY + sugarcaneYHeight, z);
sugarcaneYHeight--;
}
}
// TODO: Move trees to it's own generator
if (chunk.getBlockId(x, orgColY + 1, z) !== Block.waterStill.blockId && chunk.getBlockId(x, orgColY, z) === Block.grass.blockId && treeRNG() > 0.995) {
const treeType = treeRNG() >= 0.5;
chunk.setBlock(Block.dirt.blockId, x, orgColY, z);
let tYT = 0, tY = tYT = orgColY + 4 + Math.round(treeRNG() - 0.2), tLY = 0;
while (tY > orgColY) {
chunk.setBlockWithMetadata(Block.wood.blockId, treeType ? 2 : 0, x, tY, z);
if (tLY !== 0 && tLY < 3) {
for (let tX = -2; tX <= 2; tX++) {
for (let tZ = -2; tZ <= 2; tZ++) {
if (tX === 0 && tZ === 0) {
continue;
}
chunk.setBlockWithMetadata(Block.leaves.blockId, treeType ? 2 : 0, x + tX, tY, z + tZ);
}
}
}
tY--;
tLY++;
}
tY = 0;
while (tY < 2) {
for (let tX = -1; tX < 2; tX++) {
for (let tZ = -1; tZ < 2; tZ++) {
if (tX === 0 && tZ === 0 && tY !== 1) {
continue;
}
chunk.setBlockWithMetadata(Block.leaves.blockId, treeType ? 2 : 0, x + tX, tYT + tY, z + tZ);
}
}
tY++;
}
}
}
}
}
}

View file

@ -1,60 +0,0 @@
import { Noise2D, makeNoise2D } from "../../../external/OpenSimplex2D";
import { Noise3D, makeNoise3D } from "../../../external/OpenSimplex3D";
import Block from "../../blocks/Block";
import Chunk from "../../Chunk";
import IGenerator from "../IGenerator";
import mulberry32 from "../../mulberry32";
export default class NetherGenerator implements IGenerator {
private seed:number;
seedGenerator:() => number;
private generator:Noise3D;
private generator1:Noise3D;
private generator2:Noise3D;
private generator3:Noise3D;
private generator4:Noise3D;
private generator5:Noise3D;
public constructor(seed:number) {
this.seed = seed;
this.seedGenerator = mulberry32(this.seed);
this.generator = this.createGenerator3D();
this.generator1 = this.createGenerator3D();
this.generator2 = this.createGenerator3D();
this.generator3 = this.createGenerator3D();
this.generator4 = this.createGenerator3D();
this.generator5 = this.createGenerator3D();
}
private createGenerator2D() {
return makeNoise2D(this.seedGenerator() * Number.MAX_SAFE_INTEGER);
}
private createGenerator3D() {
return makeNoise3D(this.seedGenerator() * Number.MAX_SAFE_INTEGER);
}
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 === 0) {
chunk.setBlock(Block.bedrock.blockId, x, y, z);
continue;
}
const layer1 = (this.generator((chunk.x * 16 + x) / 32, y / 32, (chunk.z * 16 + z) / 32) + this.generator1((chunk.x * 16 + x) / 32, y / 32, (chunk.z * 16 + z) / 32)) / 2;
const layer2 = (this.generator2((chunk.x * 16 + x) / 128, y / 128, (chunk.z * 16 + z) / 128) + this.generator3((chunk.x * 16 + x) / 128, y / 128, (chunk.z * 16 + z) / 128)) / 2;
const layer3 = (this.generator4((chunk.x * 16 + x) / 16, y / 16, (chunk.z * 16 + z) / 16) + this.generator5((chunk.x * 16 + x) / 16, y / 16, (chunk.z * 16 + z) / 16)) / 2;
if ((layer1 + layer2 + layer3) / 3 >= 0.1) {
chunk.setBlock(Block.netherrack.blockId, x, y, z);
} else if (y < 10) {
chunk.setBlock(Block.lavaStill.blockId, x, y, z);
}
}
}
}
}
}

View file

@ -1,83 +0,0 @@
import { makeNoise2D, Noise2D } from "../../../external/OpenSimplex2D";
import { makeNoise3D, Noise3D } from "../../../external/OpenSimplex3D";
import Block from "../../blocks/Block";
import Chunk from "../../Chunk";
import IGenerator from "../IGenerator";
import mulberry32 from "../../mulberry32";
export default class NewOverworld implements IGenerator {
private seed:number;
seedGenerator:() => number;
public layer1:Noise3D;
public layer2:Noise3D;
public mix:Noise3D;
public maxHeight:Noise2D;
private createGenerator2D() {
return makeNoise2D(this.seedGenerator() * Number.MAX_SAFE_INTEGER);
}
private createGenerator3D() {
return makeNoise3D(this.seedGenerator() * Number.MAX_SAFE_INTEGER);
}
public constructor(seed:number) {
this.seed = seed;
this.seedGenerator = mulberry32(this.seed);
this.layer1 = this.createGenerator3D();
this.layer2 = this.createGenerator3D();
this.mix = this.createGenerator3D();
this.maxHeight = this.createGenerator2D();
}
private noiseForCoord(chunk:Chunk, x:number, y:number, z:number) {
const mixValue = this.mix((chunk.x * 16 + x) / 16, y / 16, (chunk.z * 16 + z) / 16);
const layer1 = this.layer1((chunk.x * 16 + x) / 64, y / 64, (chunk.z * 16 + z) / 64) / 2048;
const layer2 = this.layer2((chunk.x * 16 + x) / 64, y / 64, (chunk.z * 16 + z) / 64) / 2048;
const maxHeightLayer = 80 + this.maxHeight((chunk.x * 16 + x) / 64, (chunk.z * 16 + z) / 64) * 32;
if (y > maxHeightLayer) {
return 0;
}
if (mixValue < 0) {
return layer1;
} else if (mixValue > 1) {
return layer2;
} else {
return layer1 + (layer2 - layer1) * mixValue;
}
}
public generate(chunk:Chunk) {
for (let y = 0; y < 128; y++) {
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
if (y === 0 || (y < 3 && this.layer1(x, y, z) > 0.1)) {
chunk.setBlock(Block.bedrock.blockId, x, y, z);
} else {
if (this.noiseForCoord(chunk, x, y, z) > 0) {
chunk.setBlock(Block.stone.blockId, x, y, z);
} else if (y < 64) {
chunk.setBlock(Block.waterStill.blockId, x, y, z);
}
}
}
}
}
for (let y = 0; y < 128; y++) {
for (let x = 0; x < 16; x++) {
for (let z = 0; z < 16; z++) {
if (y === 0 || (y < 3 && this.layer1(x, y, z) > 0.1)) {
chunk.setBlock(Block.bedrock.blockId, x, y, z);
} else {
}
}
}
}
}
}

View file

@ -1,11 +0,0 @@
import { IReader, IWriter } from "bufferstuff";
import ItemStack from "./ItemStack";
export default interface IInventory {
fromSave:(reader:IReader) => void,
toSave:(writer:IWriter) => void,
getInventoryName:() => string,
getInventorySize:() => number,
getSlotItemStack:(slotId:number) => ItemStack | null
setSlotItemStack:(slotId:number, itemStack:ItemStack | null) => IInventory
}

View file

@ -1,122 +0,0 @@
import { Endian, IReader, IWriter, createWriter } from "bufferstuff";
import IInventory from "./IInventory";
import ItemStack from "./ItemStack";
export default class Inventory implements IInventory {
public itemStacks:Array<ItemStack | null>;
private size:number;
private name:string;
public constructor(size:number, name:string) {
this.itemStacks = new Array<ItemStack | null>();
for (let i = 0; i < size; i++) {
this.itemStacks.push(null);
}
this.size = size;
this.name = name;
}
public fromSave(reader:IReader) {
const inventorySize = reader.readByte();
for (let i = 0; i < inventorySize; i++) {
this.itemStacks[i] = ItemStack.FromSave(reader);
}
}
public toSave(writer:IWriter) {
writer.writeByte(this.size);
for (const itemStack of this.itemStacks) {
if (itemStack === null) {
writer.writeShort(-1);
continue;
}
itemStack.toSave(writer);
}
}
addItemStack(itemStack:ItemStack) {
throw new Error("Adding items to non player inventories is unimplemented.");
// Check bottom inventory row (hotbar) first.
/*let workingItemStack:ItemStack | null;
for (let slotId = 9; slotId <= 35; slotId++) {
if (itemStack.size === 0) {
break;
}
if ((workingItemStack = this.itemStacks[slotId]) != null) {
workingItemStack.insert(itemStack);
}
}*/
}
getInventoryName() {
return this.name;
}
getInventorySize() {
return this.itemStacks.length;
}
getSlotItemStack(slotId:number) {
return this.itemStacks[slotId];
}
dropEmptyItemStacks() {
for (let i = 0; i < this.itemStacks.length; i++) {
const itemStack = this.itemStacks[i];
if (itemStack?.size === 0) {
this.itemStacks[i] = null;
}
}
}
setSlotItemStack(slotId:number, itemStack: ItemStack | null) {
if (slotId < 0 || slotId > this.size - 1) {
throw new Error(`Tried to set an Inventory ItemStack out of bounds! Requested slot: ${slotId}, Inventory Size: ${this.size}`);
}
this.itemStacks[slotId] = itemStack;
return this;
}
private calculateInventoryPayloadSize() {
let bufferSize = 0;
for (const stack of this.itemStacks) {
if (stack) {
bufferSize += 5; // short + byte + short
} else {
bufferSize += 2; // short
}
}
return bufferSize;
}
constructInventoryPayload() {
const writer = createWriter(Endian.BE, this.calculateInventoryPayloadSize());
for (const stack of this.itemStacks) {
writer.writeShort(stack == null ? -1 : stack.itemID);
if (stack != null) {
writer.writeByte(stack.size);
writer.writeShort(stack.damage);
}
}
return writer.toBuffer();
}
constructInventorySinglePayload(slotId:number) {
const stack = this.itemStacks[slotId];
const writer = createWriter(Endian.BE, stack == null ? 2 : 5);
writer.writeShort(stack == null ? -1 : stack.itemID);
if (stack != null) {
writer.writeByte(stack.size);
writer.writeShort(stack.damage);
}
return writer.toBuffer();
}
}

View file

@ -1,134 +0,0 @@
import { IReader, IWriter } from "bufferstuff";
import Block from "../blocks/Block";
import IEntity from "../entities/IEntity";
import Item from "../items/Item";
export default class ItemStack {
private static ITEMSTACK_ID_ADDER = 0;
private readonly itemStackId:number;
public readonly itemID:number;
public readonly isBlock:boolean;
public size:number;
public damage:number;
private readonly maxSize:number;
private readonly maxDamage:number;
private readonly canBeDamaged:boolean;
public constructor(blockOrItemOrItemID:Block|Item|number, size?:number, damage?:number) {
if (blockOrItemOrItemID instanceof Block && size === undefined && damage === undefined) {
this.itemID = blockOrItemOrItemID.blockId;
this.size = 0;
this.damage = 0;
} else if (blockOrItemOrItemID instanceof Block && typeof(size) === "number" && damage === undefined) {
this.itemID = blockOrItemOrItemID.blockId;
this.size = size;
this.damage = 0;
} else if (blockOrItemOrItemID instanceof Block && typeof(size) === "number" && typeof(damage) === "number") {
this.itemID = blockOrItemOrItemID.blockId;
this.size = size;
this.damage = damage;
} else if (blockOrItemOrItemID instanceof Item && size === undefined && damage === undefined) {
this.itemID = blockOrItemOrItemID.shiftedItemID;
this.size = 0;
this.damage = 0;
} else if (blockOrItemOrItemID instanceof Item && typeof(size) === "number" && damage === undefined) {
this.itemID = blockOrItemOrItemID.shiftedItemID;
this.size = size;
this.damage = 0;
} else if (blockOrItemOrItemID instanceof Item && typeof(size) === "number" && typeof(damage) === "number") {
this.itemID = blockOrItemOrItemID.shiftedItemID;
this.size = size;
this.damage = damage;
} else if (typeof(blockOrItemOrItemID) === "number" && typeof(size) === "number" && typeof(damage) === "number") {
this.itemID = blockOrItemOrItemID;
this.size = size;
this.damage = damage;
} else if (typeof(blockOrItemOrItemID) === "number" && typeof(size) === "number" && damage === undefined) {
this.itemID = blockOrItemOrItemID;
this.size = size;
this.damage = 0;
} else if (typeof(blockOrItemOrItemID) === "number" && size === undefined && damage === undefined) {
this.itemID = blockOrItemOrItemID;
this.size = 0;
this.damage = 0;
} else {
throw new Error(`ItemStack created with invalid properties (${typeof(blockOrItemOrItemID)}, ${typeof(size)}, ${typeof(damage)})`);
}
this.isBlock = this.itemID < 256;
this.maxSize = this.isBlock ? 64 : Item.getByShiftedItemId(this.itemID).maxStackSize;
this.maxDamage = this.isBlock ? 0 : Item.getByShiftedItemId(this.itemID).maxDamage;
this.canBeDamaged = this.maxDamage > 0;
this.itemStackId = ItemStack.ITEMSTACK_ID_ADDER++;
}
public static FromSave(reader:IReader) {
const itemId = reader.readShort();
if (itemId === -1) {
return null;
}
return new ItemStack(itemId, reader.readByte(), reader.readShort());
}
public toSave(writer:IWriter) {
writer.writeShort(this.itemID)
.writeByte(this.size)
.writeShort(this.damage);
}
public static Compare(itemStack1:ItemStack, itemStack2:ItemStack) {
return itemStack1.itemStackId === itemStack2.itemStackId;
}
public compare(itemStack:ItemStack) {
return this.itemStackId === itemStack.itemStackId;
}
public insert(itemStack:ItemStack) {
const remainingSpace = this.spaceAvaliable;
if (remainingSpace === 0) {
return false;
}
if (remainingSpace >= itemStack.size) {
this.size += itemStack.size;
itemStack.size = 0;
return true;
}
if (remainingSpace < itemStack.size) {
this.size += remainingSpace;
itemStack.size -= remainingSpace;
return true;
}
}
public damageItem(damageAmount:number, entity:IEntity) {
if (!this.canBeDamaged) {
return;
}
this.damage += damageAmount;
if (this.damage > this.maxDamage) {
this.size--;
if (this.size < 0) {
this.size = 0;
}
this.damage = 0;
}
}
public get spaceAvaliable() {
// Stack size check for Item(s) and Block(s).
return Math.max(this.maxSize - this.size, 0);
}
split(amount:number) {
this.size -= amount;
return new ItemStack(this.itemID, amount, this.damage);
}
}

View file

@ -1,107 +0,0 @@
import Inventory from "./Inventory";
import ItemStack from "./ItemStack";
import PacketSetSlot from "../packets/SetSlot";
import PacketWindowItems from "../packets/WindowItems";
import Player from "../entities/Player";
export default class PlayerInventory extends Inventory {
private player:Player;
public constructor(player:Player) {
super(44, "Player Inventory");
this.player = player;
}
sendUpdatedStacks(stackIdsChanged:Array<number>) {
let updateBuffer = Buffer.alloc(0);
for (const slotId of stackIdsChanged) {
const slotItem = this.itemStacks[slotId];
let buffer:Buffer;
if (slotItem == null) {
buffer = new PacketSetSlot(0, slotId, -1).writeData();
} else {
buffer = new PacketSetSlot(0, slotId, slotItem.itemID, slotItem.size, slotItem.damage).writeData();
}
updateBuffer = Buffer.concat([updateBuffer, buffer], updateBuffer.length + buffer.length);
}
if (updateBuffer.length > 0) {
this.player.mpClient?.send(updateBuffer);
}
}
addItemStack(itemStack:ItemStack) {
const itemStacksOfSameType:Array<ItemStack> = new Array<ItemStack>();
const itemStackIds:Array<number> = new Array<number>();
const stackIdsChanged:Array<number> = new Array<number>();
// Check bottom inventory row (hotbar) first.
let workingItemStack:ItemStack | null;
for (let slotId = 36; slotId <= 44; slotId++) {
if ((workingItemStack = this.itemStacks[slotId]) != null) {
itemStacksOfSameType.push(workingItemStack);
itemStackIds.push(slotId);
}
}
for (let slotId = 9; slotId <= 35; slotId++) {
if ((workingItemStack = this.itemStacks[slotId]) != null) {
itemStacksOfSameType.push(workingItemStack);
itemStackIds.push(slotId);
}
}
// Insert into existing stacks first.
for (let i = 0; i < itemStacksOfSameType.length; i++) {
const inventoryItemStack = itemStacksOfSameType[i];
// Exit early if we have nothing left
if (itemStack.size === 0) {
return this.sendUpdatedStacks(stackIdsChanged);
}
if (inventoryItemStack.itemID !== itemStack.itemID || inventoryItemStack.damage !== itemStack.damage) {
continue;
}
if (inventoryItemStack.insert(itemStack)) {
stackIdsChanged.push(itemStackIds[i]);
}
}
// Exit early if we have nothing left
if (itemStack.size === 0) {
return this.sendUpdatedStacks(stackIdsChanged);
}
for (let slotId = 36; slotId <= 44; slotId++) {
// Exit early if we have nothing left
if (itemStack.size === 0) {
return this.sendUpdatedStacks(stackIdsChanged);
}
if ((workingItemStack = this.itemStacks[slotId]) == null) {
const stack = this.itemStacks[slotId] = new ItemStack(itemStack.itemID, 0, itemStack.damage);
if (stack.insert(itemStack)) {
stackIdsChanged.push(slotId);
}
}
}
for (let slotId = 9; slotId <= 35; slotId++) {
// Exit early if we have nothing left
if (itemStack.size === 0) {
return this.sendUpdatedStacks(stackIdsChanged);
}
if ((workingItemStack = this.itemStacks[slotId]) == null) {
const stack = this.itemStacks[slotId] = new ItemStack(itemStack.itemID, 0, itemStack.damage);
if (stack.insert(itemStack)) {
stackIdsChanged.push(slotId);
}
}
}
this.sendUpdatedStacks(stackIdsChanged)
}
}

View file

@ -1,6 +0,0 @@
import EntityLiving from "../entities/EntityLiving";
import ItemStack from "../inventories/ItemStack";
export default interface IItemBehaviour {
}

View file

@ -1,137 +0,0 @@
import MaxUses from "../enums/MaxUses";
export default class Item {
public static items:Array<Item> = new Array<Item>();
public maxStackSize:number;
public maxDamage:number;
public shiftedItemID:number;
public name:string;
public constructor(itemID:number) {
this.shiftedItemID = 256 + itemID;
this.maxDamage = 0;
this.maxStackSize = 64;
this.name = "UNNAMED";
Item.items[itemID] = this;
}
public static getByShiftedItemId(shiftedItemID:number) {
return Item.items[shiftedItemID - 256];
}
public setMaxStackSize(stackSize:number) {
this.maxStackSize = stackSize;
return this;
}
public getMaxDamage() {
return this.maxDamage;
}
public setMaxDamage(value:number) {
this.maxDamage = value;
return this;
}
public setName(name:string) {
this.name = name;
return this;
}
public getName() {
return this.name;
}
// Define statics here
static ironShovel = new Item(0).setMaxDamage(MaxUses.IRON).setName("Iron Shovel");
static ironPickaxe = new Item(1).setMaxDamage(MaxUses.IRON).setName("Iron Pickaxe");
static ironAxe = new Item(2).setMaxDamage(MaxUses.IRON).setName("Iron Axe");
static flintAndSteel = new Item(3).setName("Flint and Steel");
static apple = new Item(4).setName("Apple");
static bow = new Item(5).setName("Bow");
static arrow = new Item(6).setName("Arrow");
static coal = new Item(7).setName("Coal");
static diamond = new Item(8).setName("Diamond");
static iron = new Item(9).setName("Iron Ingot");
static gold = new Item(10).setName("Gold Ingot");
static ironSword = new Item(11).setMaxDamage(MaxUses.IRON).setName("Iron Sword");
static woodenSword = new Item(12).setMaxDamage(MaxUses.WOOD).setName("Wooden Sword");
static woodenShovel = new Item(13).setMaxDamage(MaxUses.WOOD).setName("Wooden Shovel");
static woodenPickaxe = new Item(14).setMaxDamage(MaxUses.WOOD).setName("Wooden Pickaxe");
static woodenAxe = new Item(15).setMaxDamage(MaxUses.WOOD).setName("Wooden Axe");
static stoneSword = new Item(16).setMaxDamage(MaxUses.STONE).setName("Stone Sword");
static stoneShovel = new Item(17).setMaxDamage(MaxUses.STONE).setName("Stone Shovel");
static stonePickaxe = new Item(18).setMaxDamage(MaxUses.STONE).setName("Stone Pickaxe");
static stoneAxe = new Item(19).setMaxDamage(MaxUses.STONE).setName("Stone Axe");
static diamondSword = new Item(20).setMaxDamage(MaxUses.DIAMOND).setName("Diamond Sword");
static diamondShovel = new Item(21).setMaxDamage(MaxUses.DIAMOND).setName("Diamond Shovel");
static diamondPickaxe = new Item(22).setMaxDamage(MaxUses.DIAMOND).setName("Diamond Pickaxe");
static diamondAxe = new Item(23).setMaxDamage(MaxUses.DIAMOND).setName("Diamond Axe");
static stick = new Item(24).setName("Stick");
static bowl = new Item(25).setName("Bowl");
static mushroomStew = new Item(26).setName("Mushroom Stew");
static goldSword = new Item(27).setMaxDamage(MaxUses.GOLD).setName("Gold Sword");
static goldShovel = new Item(28).setMaxDamage(MaxUses.GOLD).setName("Gold Shovel");
static goldPickaxe = new Item(29).setMaxDamage(MaxUses.GOLD).setName("Gold Pickaxe");
static goldAxe = new Item(30).setMaxDamage(MaxUses.GOLD).setName("Gold Axe");
static string = new Item(31).setName("String");
static feather = new Item(32).setName("Feather");
static gunpowder = new Item(33).setName("Gunpowder");
static woodenHoe = new Item(34).setMaxDamage(MaxUses.WOOD).setName("Wooden Hoe");
static stoneHoe = new Item(35).setMaxDamage(MaxUses.WOOD).setName("Stone Hoe");
static ironHoe = new Item(36).setMaxDamage(MaxUses.WOOD).setName("Iron Hoe");
static diamondHoe = new Item(37).setMaxDamage(MaxUses.WOOD).setName("Diamond Hoe");
static goldHoe = new Item(38).setMaxDamage(MaxUses.WOOD).setName("Gold Hoe");
static seeds = new Item(39).setName("Seeds");
static wheat = new Item(40).setName("Wheat");
static bread = new Item(41).setName("Bread");
// TODO: Armor items
static flint = new Item(62).setName("Flint");
static rawPorkchop = new Item(63).setName("Raw Porkchop");
static cookedPorkchop = new Item(64).setName("Cooked Porkchop");
static painting = new Item(65).setName("Painting");
static goldenApple = new Item(66).setName("Golden Apple");
static sign = new Item(67).setName("Sign");
static woodenDoor = new Item(68).setName("Wooden Door");
static bucket = new Item(69).setName("Bucket");
static waterBucket = new Item(70).setName("Water Bucket");
static lavaBucket = new Item(71).setName("Lava Bucket");
static minecart = new Item(72).setName("Minecart");
static saddle = new Item(73).setName("Saddle");
static ironDoor = new Item(74).setName("Iron Door");
static redstone = new Item(75).setName("Redstone");
static snowball = new Item(76).setName("Snowball");
static boat = new Item(77).setName("Boat");
static leather = new Item(78).setName("Leather");
static milkBucket = new Item(79).setName("Milk Bucket");
static brick = new Item(80).setName("Brick");
static clay = new Item(81).setName("Clay");
static sugarcane = new Item(82).setName("Sugar Cane");
static paper = new Item(83).setName("Paper");
static book = new Item(84).setName("Book");
static slimeBall = new Item(85).setName("Slime Ball");
static minecartChest = new Item(86).setName("Minecart with Chest");
static minecartFurnace = new Item(87).setName("Minecart with Furnace");
static egg = new Item(88).setName("Egg");
static compass = new Item(89).setName("Compass");
static fishingRod = new Item(90).setName("Fishing Rod");
static clock = new Item(91).setName("Clock");
static glowstoneDust = new Item(92).setName("Glowstone Dust");
static rawFish = new Item(93).setName("Raw Fish");
static cookedFish = new Item(94).setName("Cooked Fish");
static dye = new Item(95).setName("Dye");
static bone = new Item(96).setName("Bone");
static sugar = new Item(97).setName("Sugar");
static cake = new Item(98).setName("Cake");
static bed = new Item(99).setName("Bed");
static redstoneRepeater = new Item(100).setName("Redstone Repeater");
static cooking = new Item(101).setName("Cookie");
static map = new Item(102).setName("Map");
static shears = new Item(103).setName("Shears");
static record13 = new Item(2000).setName("13");
static recordCat = new Item(2001).setName("Cat");
}

View file

@ -1,7 +0,0 @@
import EntityLiving from "../entities/EntityLiving";
import ItemStack from "../inventories/ItemStack";
import IItemBehaviour from "./IItemBehaviour";
export default class ItemBehaviour implements IItemBehaviour {
}

Some files were not shown because too many files have changed in this diff Show more