Compare commits
135 commits
master
...
typescript
Author | SHA1 | Date | |
---|---|---|---|
f6abf37f41 | |||
f156e21035 | |||
2761e6978c | |||
25f52fc11d | |||
c521455ec4 | |||
78c894a94d | |||
7d4445112b | |||
a6da71e6a2 | |||
db9847634e | |||
19453b7f16 | |||
9f93f14461 | |||
45c08c0c5f | |||
5c60d1d758 | |||
f606d1547b | |||
61586092d1 | |||
9ae490bba3 | |||
b02ac676e2 | |||
328ddca458 | |||
4afb4a0633 | |||
797cd5db62 | |||
f4733f50a3 | |||
dc05cd4a2d | |||
3ea5e47c57 | |||
91395db919 | |||
9231ce85d6 | |||
d3ebe1518d | |||
a5910abd86 | |||
1a08b5e991 | |||
278c90f5f9 | |||
604bec9e89 | |||
5a250cb601 | |||
e303cbc5df | |||
63333a04aa | |||
6033c86247 | |||
875318db04 | |||
228602a54d | |||
64709abfb9 | |||
de4203d9f0 | |||
79f3ae1d4f | |||
61b667ac49 | |||
0403ace046 | |||
617cebe2e0 | |||
f9d4c6b1f9 | |||
8054f06327 | |||
23a1d124c0 | |||
6d7d1299ce | |||
d64f9469cb | |||
81b5fb302d | |||
8fc6836b29 | |||
1b2b1a091e | |||
bb733ff2af | |||
0eb23adb09 | |||
83f37a0621 | |||
522916ecaa | |||
a3c865bd9f | |||
2daec09426 | |||
0c0d8f7711 | |||
f1c4dcda75 | |||
d8d1eabcf4 | |||
74374cb0d6 | |||
9feafc46c7 | |||
479cc35d89 | |||
a61d807966 | |||
690c7be16f | |||
5c25dd7436 | |||
19328d5500 | |||
aea8c440d9 | |||
ad240d3598 | |||
30569c06ab | |||
5c478f461e | |||
41e1124c04 | |||
79b1f0c0eb | |||
3123dac2c2 | |||
84bbe26454 | |||
310f1bd091 | |||
36ef685813 | |||
ad83454afa | |||
c80dcd2d4f | |||
3351888f19 | |||
247ecb3484 | |||
c8b4a2f756 | |||
6ca1b7f052 | |||
1f7d3652e1 | |||
d387a6c299 | |||
e05f8d6c58 | |||
b038315b35 | |||
8bed6c7310 | |||
a4ef4c6945 | |||
ba1c4e04d8 | |||
3ca20743a3 | |||
59e12c0655 | |||
21a9f64a9e | |||
44f8568cdb | |||
055627bb63 | |||
5fd4f50b1a | |||
3b208eb8db | |||
5396b83f3f | |||
a8d9ab18bb | |||
5fa7927402 | |||
15917f1533 | |||
ef1e7a278e | |||
2aedd81baa | |||
ec24b6433e | |||
93b4e701c6 | |||
c55c091872 | |||
3a84e84090 | |||
98060d67b7 | |||
acf4121727 | |||
ad1c402d84 | |||
8b906bfe8f | |||
e21bda6201 | |||
0653da20b2 | |||
b1a4933990 | |||
61133eb710 | |||
ae25686b10 | |||
686a694432 | |||
|
75d36b49db | ||
|
35aa372d8e | ||
e252fe255c | |||
47f6d5cc37 | |||
dd257acfe2 | |||
f8a52dfc98 | |||
b474d35775 | |||
860c8f4866 | |||
42cef0a838 | |||
b371521fda | |||
f93bb2605d | |||
36d294a810 | |||
e5ba93ce9c | |||
16c7b5ddcc | |||
fa27b476e7 | |||
122e0fa53a | |||
5de6e74323 | |||
2503664723 | |||
3edcbc062f |
150 changed files with 9400 additions and 2537 deletions
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -1,2 +0,0 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
37
.github/workflows/node.js.yml
vendored
Normal file
37
.github/workflows/node.js.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
# 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
114
.gitignore
vendored
|
@ -1,109 +1,7 @@
|
|||
# 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/
|
||||
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
|
||||
build/
|
||||
bundle/
|
||||
world/
|
||||
logs/
|
||||
combined.ts
|
||||
config.json
|
0
server/Commands/Command.js → .gitmodules
vendored
0
server/Commands/Command.js → .gitmodules
vendored
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 holly
|
||||
Copyright (c) 2024 Holly Stubbs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
77
README.md
77
README.md
|
@ -1,28 +1,77 @@
|
|||
# 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)
|
||||
# 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)
|
||||
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
|
||||
- Terrain sending
|
||||
- Block placement
|
||||
- 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
|
||||
- Block breaking
|
||||
- Entities:
|
||||
- Players
|
||||
- Player Inventory
|
||||
|
||||
**WIP:**
|
||||
- Terrain generation using perlin noise
|
||||
- Cross chunk structure generation (trees, buildings, etc...)
|
||||
- Entities:
|
||||
- Players
|
||||
- Dimensions:
|
||||
- Nether
|
||||
- Inventories (containers, etc...)
|
||||
|
||||
**To Implement:**
|
||||
- Block placement
|
||||
- Entities:
|
||||
- Items/Blocks
|
||||
- Animals
|
||||
- Items
|
||||
- Mobs
|
||||
- Inventories
|
||||
- Terrain saving to disk
|
||||
- Saving inventories
|
||||
- Sleeping in beds
|
||||
- Tile entities
|
||||
- Probably a bunch more things that i'm forgetting
|
||||
|
||||
**Long Term:**
|
||||
- Optimise the shit out of this
|
||||
|
||||
<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.
|
||||
|
|
6
config.example.json
Normal file
6
config.example.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"port": 25565,
|
||||
"maxPlayers": 20,
|
||||
"saveCompression": "DEFLATE",
|
||||
"worldName": "world"
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"port": 25565,
|
||||
"worldThreads": 4
|
||||
}
|
9
config.ts
Normal file
9
config.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import SaveCompressionType from "./server/enums/SaveCompressionType";
|
||||
|
||||
export default interface Config {
|
||||
port: number,
|
||||
maxPlayers: number,
|
||||
seed: number|string,
|
||||
worldName: string,
|
||||
saveCompression: SaveCompressionType
|
||||
}
|
195
external/OpenSimplex2D.ts
vendored
Normal file
195
external/OpenSimplex2D.ts
vendored
Normal file
|
@ -0,0 +1,195 @@
|
|||
// 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,
|
||||
];
|
596
external/OpenSimplex3D.ts
vendored
Normal file
596
external/OpenSimplex3D.ts
vendored
Normal file
|
@ -0,0 +1,596 @@
|
|||
// 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,
|
||||
];
|
8
external/shuffle_seed.ts
vendored
Normal file
8
external/shuffle_seed.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
// 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
15
index.js
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
==============- index.js -==============
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const server = new (require('net').Server)();
|
||||
const config = require("./config.json");
|
||||
|
||||
const mcServer = require("./server/server.js");
|
||||
|
||||
server.listen(config.port, () => mcServer.init(config));
|
||||
|
||||
server.on('connection', mcServer.connection);
|
13
index.ts
Normal file
13
index.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
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);
|
35
nibbleArray.ts
Normal file
35
nibbleArray.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
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);
|
||||
}
|
||||
}
|
2777
package-lock.json
generated
2777
package-lock.json
generated
File diff suppressed because it is too large
Load diff
35
package.json
35
package.json
|
@ -2,20 +2,41 @@
|
|||
"name": "mc-beta-server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
"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"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tgpethan/mc-beta-server.git"
|
||||
"url": "git+https://github.com/tgpholly/mc-beta-server.git"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"author": "tgpholly",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/tgpethan/mc-beta-server/issues"
|
||||
"url": "https://github.com/tgpholly/mc-beta-server/issues"
|
||||
},
|
||||
"homepage": "https://github.com/tgpethan/mc-beta-server#readme",
|
||||
"homepage": "https://github.com/tgpholly/mc-beta-server#readme",
|
||||
"dependencies": {
|
||||
"net": "^1.0.2"
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
|
177
server/AABB.ts
Normal file
177
server/AABB.ts
Normal file
|
@ -0,0 +1,177 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
class Block {
|
||||
static blocksList = [];
|
||||
static blockOpaqueList = [];
|
||||
static blockOpacity = [];
|
||||
static blockGrassable = [];
|
||||
static blockContainer = [];
|
||||
|
||||
constructor(blockID = 0) {
|
||||
this.blockID = blockID;
|
||||
|
||||
this.blockHardness = 0;
|
||||
this.blockResistance = 0;
|
||||
this.blockName = "";
|
||||
|
||||
if (Block.blocksList[blockID] != null) {
|
||||
throw `[Block] Slot ${blockID} is already occupied by ${Block.blocksList[blockID]} when adding ID:${blockID}`;
|
||||
} else {
|
||||
Block.blocksList[blockID] = this;
|
||||
Block.blockOpaqueList[blockID] = false; // TODO: Block opaque
|
||||
Block.blockOpacity[blockID] = 0; // TODO: Opacity
|
||||
Block.blockGrassable[blockID] = false; // TODO: Get if block can be grass'd
|
||||
Block.blockContainer[blockID] = false; // TODO: Containers
|
||||
}
|
||||
}
|
||||
|
||||
setHardness(hardness = 0) {
|
||||
this.blockHardness = hardness;
|
||||
return this;
|
||||
}
|
||||
|
||||
setBlockUnbreakable() {
|
||||
this.blockHardness = -1;
|
||||
return this;
|
||||
}
|
||||
|
||||
setResistance(resistance = 0) {
|
||||
this.blockHardness = resistance;
|
||||
return this;
|
||||
}
|
||||
|
||||
setName(name = "") {
|
||||
this.blockName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
static stone = new Block(1).setHardness(1.5).setResistance(10).setName("Stone");
|
||||
static grass = new Block(2).setHardness(0.6).setName("Grass");
|
||||
static dirt = new Block(3).setHardness(0.5).setName("Dirt");
|
||||
static cobblestone = new Block(4).setHardness(2.0).setResistance(10).setName("Cobblestone");
|
||||
static planks = new Block(5).setHardness(2).setResistance(5).setName("Planks");
|
||||
static sapling = new Block(6).setName("Sapling");
|
||||
static bedrock = new Block(7).setBlockUnbreakable().setResistance(6000000).setName("Bedrock");
|
||||
static waterFlowing = new Block(8).setHardness(100).setName("Flowing Water");
|
||||
static waterStill = new Block(9).setHardness(100).setName("Still Water");
|
||||
static lavaMoving = new Block(10).setHardness(1.5).setResistance(10).setName("Flowing Lava");
|
||||
static lavaStill = new Block(11).setHardness(1.5).setResistance(10).setName("Still Lava");
|
||||
static sand = new Block(12).setHardness(1.5).setResistance(10).setName("Sand");
|
||||
static gravel = new Block(13).setHardness(1.5).setResistance(10).setName("Gravel");
|
||||
static goldOre = new Block(14).setHardness(1.5).setResistance(10).setName("Gold Ore");
|
||||
static ironOre = new Block(15).setHardness(1.5).setResistance(10).setName("Iron Ore");
|
||||
static coalOre = new Block(16).setHardness(1.5).setResistance(10).setName("Coal Ore");
|
||||
static wood = new Block(17).setHardness(1.5).setResistance(10).setName("Wood");
|
||||
static leaves = new Block(18).setHardness(1.5).setResistance(10).setName("Leaves");
|
||||
static sponge = new Block(19).setHardness(1.5).setResistance(10).setName("Sponge");
|
||||
static glass = new Block(20).setHardness(1.5).setResistance(10).setName("Glass");
|
||||
static lapisOre = new Block(21).setHardness(1.5).setResistance(10).setName("Lapis Ore");
|
||||
static lapisBlock = new Block(22).setHardness(1.5).setResistance(10).setName("Lapis Block");
|
||||
static dispenser = new Block(23).setHardness(1.5).setResistance(10).setName("Dispenser");
|
||||
static sandStone = new Block(24).setHardness(1.5).setResistance(10).setName("Sandstone");
|
||||
static noteBlock = new Block(25).setHardness(1.5).setResistance(10).setName("Noteblock");
|
||||
static blockBed = new Block(26).setHardness(1.5).setResistance(10).setName("Bed");
|
||||
static poweredRail = new Block(27).setHardness(1.5).setResistance(10).setName("Powered Rail");
|
||||
static detectorRail = new Block(28).setHardness(1.5).setResistance(10).setName("Detector Rail");
|
||||
static stickyPisonBase = new Block(29).setHardness(1.5).setResistance(10).setName("Sticky Piston Base");
|
||||
static cobweb = new Block(30).setHardness(4).setName("Cobweb");
|
||||
static tallGrass = new Block(31).setName("Tall Grass");
|
||||
static deadBush = new Block(32).setName("Dead Bush");
|
||||
static pistonBase = new Block(33).setName("Piston Base");
|
||||
static pistonExtension = new Block(34).setName("Piston Extension");
|
||||
static wool = new Block(35).setHardness(0.8).setName("Wool");
|
||||
static pistonMoving = new Block(36).setName("Piston Moving")
|
||||
static dandilion = new Block(37).setName("Dandilion");
|
||||
static rose = new Block(38).setName("Rose");
|
||||
}
|
||||
|
||||
module.exports = Block;
|
190
server/Chunk.ts
Normal file
190
server/Chunk.ts
Normal file
|
@ -0,0 +1,190 @@
|
|||
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;
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
============- Converter.js -============
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
module.exports.toAbsoluteInt = function(float = 0.0) {
|
||||
return Math.floor(float * 32.0);
|
||||
}
|
||||
|
||||
module.exports.to360Fraction = function(float = 0.0) {
|
||||
if (float < 0) {
|
||||
return Math.abs(Math.max(Math.floor((map(float, 0, -360, 360, 0) / 360) * 256) - 1, 0));
|
||||
} else if (float > 0) {
|
||||
return Math.max(Math.floor((float / 360) * 256) - 1, 0);
|
||||
}
|
||||
//return Math.max(Math.floor((float / 360) * 256), 0) - 1;
|
||||
}
|
||||
|
||||
function map(input, inputMin, inputMax, outputMin, outputMax) {
|
||||
const newv = (input - inputMin) / (inputMax - inputMin) * (outputMax - outputMin) + outputMin;
|
||||
|
||||
if (outputMin < outputMax) return constrain(newv, outputMin, outputMax);
|
||||
else return constrain(newv, outputMax, outputMin);
|
||||
}
|
||||
|
||||
function constrain(input, low, high) {
|
||||
return Math.max(Math.min(input, high), low);
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
==============- Entity.js -=============
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
class Entity {
|
||||
constructor(EID = 0, x = 0, y = 0, z = 0, yaw = 0, pitch = 0) {
|
||||
this.EID = EID;
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.yaw = yaw;
|
||||
this.pitch = pitch;
|
||||
|
||||
this.motionX = 0;
|
||||
this.motionY = 0;
|
||||
this.motionZ = 0;
|
||||
}
|
||||
|
||||
onTick() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Entity;
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
============- EntityItem.js -===========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Entity = require("./Entity.js");
|
||||
|
||||
class EntityItem extends Entity {
|
||||
constructor(itemStack, x = 0, y = 0, z = 0) {
|
||||
super(global.fromIDPool(), x, y, z);
|
||||
|
||||
this.itemStack = itemStack;
|
||||
|
||||
this.motionX = (Math.random() * 0.2 - 0.1);
|
||||
this.motionY = 0.2;
|
||||
this.motionZ = (Math.random() * 0.2 - 0.1);
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
==========- EntityLiving.js -===========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Entity = require("./Entity.js");
|
||||
|
||||
class EntityLiving extends Entity {
|
||||
constructor(EID = 0, x = 0, y = 0, z = 0) {
|
||||
super(EID, x, y, z);
|
||||
|
||||
|
||||
}
|
||||
|
||||
onTick() {
|
||||
super.onTick();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EntityLiving;
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
===========- EntityPlayer.js -==========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const EntityLiving = require("./EntityLiving.js");
|
||||
const user = require("../user.js");
|
||||
|
||||
const Converter = require("../Converter.js");
|
||||
const PacketMappingTable = require("../PacketMappingTable.js");
|
||||
const NamedPackets = require("../NamedPackets.js");
|
||||
|
||||
class EntityPlayer extends EntityLiving {
|
||||
constructor(parent = new user, x = 0, y = 0, z = 0) {
|
||||
super(parent.id, x, y, z);
|
||||
|
||||
this.lastX = 0;
|
||||
this.lastY = 0;
|
||||
this.lastZ = 0;
|
||||
this.absX = 0;
|
||||
this.absY = 0;
|
||||
this.absZ = 0;
|
||||
|
||||
this.absYaw = 0;
|
||||
this.absPitch = 0;
|
||||
|
||||
this.lastYaw = 0;
|
||||
this.lastPitch = 0;
|
||||
|
||||
this.allPacket = new PacketMappingTable[NamedPackets.EntityTeleport](this.EID, this.x, this.y, this.z, this.absYaw, this.absPitch);
|
||||
this.allPacket.soup();
|
||||
this.allPacket.writePacket();
|
||||
|
||||
this.parentPlayer = parent;
|
||||
}
|
||||
|
||||
onTick() {
|
||||
super.onTick();
|
||||
|
||||
this.absX = this.x.toFixed(2);
|
||||
this.absY = this.y.toFixed(2);
|
||||
this.absZ = this.z.toFixed(2);
|
||||
|
||||
this.absYaw = Math.floor(this.yaw);
|
||||
this.absPitch = Math.floor(this.pitch);
|
||||
|
||||
if ((this.absX != this.lastX || this.absY != this.lastY || this.absZ != this.lastZ)) {
|
||||
// all
|
||||
this.allPacket.writer.offset = 5;
|
||||
this.allPacket.writer.writeInt(Converter.toAbsoluteInt(this.x));
|
||||
this.allPacket.writer.writeInt(Converter.toAbsoluteInt(this.y));
|
||||
this.allPacket.writer.writeInt(Converter.toAbsoluteInt(this.z));
|
||||
this.allPacket.writer.writeUByte(Converter.to360Fraction(this.absYaw));
|
||||
this.allPacket.writer.writeUByte(Converter.to360Fraction(this.absPitch));
|
||||
|
||||
global.sendToAllPlayersButSelf(this.EID, this.allPacket.toBuffer());
|
||||
} else if (this.absYaw != this.lastYaw || this.absPitch != this.lastPitch) {
|
||||
// look only
|
||||
global.sendToAllPlayersButSelf(this.EID, new PacketMappingTable[NamedPackets.EntityLook](this.EID, this.absYaw, this.absPitch).writePacket());
|
||||
}
|
||||
|
||||
this.lastYaw = this.absYaw;
|
||||
this.lastPitch = this.absPitch;
|
||||
|
||||
this.lastX = this.absX;
|
||||
this.lastY = this.absY;
|
||||
this.lastZ = this.absZ;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EntityPlayer;
|
26
server/EntityMetadata.ts
Normal file
26
server/EntityMetadata.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
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();
|
||||
}
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
const Block = require("../Blocks/Block.js");
|
||||
|
||||
module.exports = function(x = 0, z = 0) {
|
||||
// Create chunk
|
||||
let chunk = {};
|
||||
for (let y = 0; y < 128; y++) {
|
||||
chunk[y] = {};
|
||||
for (let x = 0; x < 16; x++) {
|
||||
chunk[y][x] = {};
|
||||
for (let z = 0; z < 16; z++) {
|
||||
chunk[y][x][z] = [0, 0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let y = 0; y < 128; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
for (let z = 0; z < 16; z++) {
|
||||
if (y == 64) {
|
||||
chunk[y][x][z] = 2;
|
||||
}
|
||||
else if (y == 63 || y == 62) chunk[y][x][z][0] = Block.dirt.blockID;
|
||||
else if (y == 0) chunk[y][x][z][0] = Block.bedrock.blockID;
|
||||
else if (y < 62) chunk[y][x][z][0] = Block.stone.blockID;
|
||||
else chunk[y][x][z][0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
|
@ -1,187 +0,0 @@
|
|||
const { perlin2D } = require("./perlin.js");
|
||||
const Block = require("../Blocks/Block.js");
|
||||
|
||||
module.exports = function(cx = 0, cz = 0, seed = 0) {
|
||||
// Create bare chunk
|
||||
let chunk = {};
|
||||
for (let y = 0; y < 128; y++) {
|
||||
chunk[y] = {};
|
||||
for (let x = 0; x < 16; x++) {
|
||||
chunk[y][x] = {};
|
||||
for (let z = 0; z < 16; z++) {
|
||||
chunk[y][x][z] = [0, 0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stripTopCoord = {};
|
||||
// History has shown it's better to alloc all at once
|
||||
for (let x = 0; x < 16; x++) {
|
||||
stripTopCoord[x] = {};
|
||||
for (let z = 0; z < 16; z++) {
|
||||
stripTopCoord[x][z] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate top layer of grass
|
||||
for (let x = 0; x < 16; x++) {
|
||||
for (let z = 0; z < 16; z++) {
|
||||
// NOTE: Because of the way this is, it is not random at all. The heightmap is simply offset so uhhh.
|
||||
// TODO: Figure out a better way of dealing with this :)
|
||||
const layer1 = (64 + (perlin2D(((cx << 4) + x) / 15, ((cz << 4) + z) / 15) * 10));
|
||||
const layer2 = (64 + (perlin2D(((cx + (10 + seed) << 4) + x) / 15, ((cz + (4 + seed) << 4) + z) / 15) * 10));
|
||||
const layer3_1 = (64 + (perlin2D(((cx + (-15 + seed) << 4) + x) / 15, ((cz + (-2 + seed) << 4) + z) / 15) * 23));
|
||||
const layer3_2 = (64 + (perlin2D(((cx + (25 + seed) << 4) + x) / 15, ((cz + (-17 + seed) << 4) + z) / 15) * 40));
|
||||
const layer3 = (layer3_1 + layer3_2) / 2;
|
||||
const average = Math.floor((layer1 + layer2 + layer3) / 3);
|
||||
stripTopCoord[x][z] = average;
|
||||
chunk[average][x][z][0] = Block.grass.blockID;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate down from the top layer
|
||||
for (let x = 0; x < 16; x++) {
|
||||
for (let z = 0; z < 16; z++) {
|
||||
// Cache these otherwise we'll be doing more maths 128 times for each column and row
|
||||
const topM1 = stripTopCoord[x][z] - 1,
|
||||
topM2 = topM1 - 1;
|
||||
|
||||
for (let y = stripTopCoord[x][z]; y != -1; y--) {
|
||||
if (y == topM1 || y == topM2) chunk[y][x][z][0] = Block.dirt.blockID;
|
||||
else if (y == 0) chunk[y][x][z][0] = Block.bedrock.blockID;
|
||||
else if (y < topM2) chunk[y][x][z][0] = Block.stone.blockID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2nd pass
|
||||
for (let y = 0; y < 128; y++) {
|
||||
for (let x = 0; x < 16; x++) {
|
||||
for (let z = 0; z < 16; z++) {
|
||||
if (chunk[y][x][z][0] == 0 && y < 64) chunk[y][x][z][0] = Block.waterStill.blockID;
|
||||
|
||||
if (y < 127 && y > 0) if (chunk[y][x][z][0] == Block.waterStill.blockID && chunk[y - 1][x][z][0] == 2) chunk[y - 1][x][z][0] = Block.dirt.blockID;
|
||||
|
||||
//if (x == 0 && z == 0) chunk[y][x][z] = 57;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let treeBlocks = [];
|
||||
|
||||
const chunkX = cx << 4;
|
||||
const chunkZ = cz << 4;
|
||||
let topBlock = 0;
|
||||
// 3rd pass???
|
||||
for (let x = 0; x < 16; x++) {
|
||||
for (let z = 0; z < 16; z++) {
|
||||
topBlock = stripTopCoord[x][z];
|
||||
|
||||
if (chunk[topBlock][x][z][0] == Block.grass.blockID) {
|
||||
if (Math.floor(Math.random() * 5) == 0) {
|
||||
chunk[topBlock + 1][x][z][0] = Block.tallGrass.blockID;
|
||||
chunk[topBlock + 1][x][z][1] = 1;
|
||||
} else if (Math.floor(Math.random() * 150) == 0) {
|
||||
chunk[topBlock + 1][x][z][0] = Block.rose.blockID;
|
||||
} else if (Math.floor(Math.random() * 150) == 0) {
|
||||
chunk[topBlock + 1][x][z][0] = Block.dandilion.blockID;
|
||||
}
|
||||
}
|
||||
|
||||
// Need a better way of doing this it currently takes a severely long time (gee I wonder why)
|
||||
if (chunk[topBlock][x][z][0] == 2 && Math.floor(Math.random() * 200) == 0) {
|
||||
chunk[topBlock][x][z][0] = 3;
|
||||
// Logs
|
||||
treeBlocks.push([(chunkX + x), topBlock + 1, (chunkZ + z), Block.wood.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 2, (chunkZ + z), Block.wood.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 3, (chunkZ + z), Block.wood.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 4, (chunkZ + z), Block.wood.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 5, (chunkZ + z), Block.wood.blockID]);
|
||||
|
||||
// Leaves
|
||||
// Layer 1
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 3, (chunkZ + z), Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 2, topBlock + 3, (chunkZ + z), Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 3, (chunkZ + z), Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 2, topBlock + 3, (chunkZ + z), Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x), topBlock + 3, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 3, (chunkZ + z) - 2, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 3, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 3, (chunkZ + z) + 2, Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 3, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 3, (chunkZ + z) - 2, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 3, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 3, (chunkZ + z) + 2, Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x) + 2, topBlock + 3, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 2, topBlock + 3, (chunkZ + z) - 2, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 2, topBlock + 3, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 2, topBlock + 3, (chunkZ + z) + 2, Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 3, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 3, (chunkZ + z) - 2, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 3, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 3, (chunkZ + z) + 2, Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x) - 2, topBlock + 3, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 2, topBlock + 3, (chunkZ + z) - 2, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 2, topBlock + 3, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 2, topBlock + 3, (chunkZ + z) + 2, Block.leaves.blockID]);
|
||||
|
||||
// Layer 2
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 4, (chunkZ + z), Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 2, topBlock + 4, (chunkZ + z), Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 4, (chunkZ + z), Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 2, topBlock + 4, (chunkZ + z), Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x), topBlock + 4, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 4, (chunkZ + z) - 2, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 4, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 4, (chunkZ + z) + 2, Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 4, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 4, (chunkZ + z) - 2, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 4, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 4, (chunkZ + z) + 2, Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x) + 2, topBlock + 4, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 2, topBlock + 4, (chunkZ + z) - 2, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 2, topBlock + 4, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 2, topBlock + 4, (chunkZ + z) + 2, Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 4, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 4, (chunkZ + z) - 2, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 4, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 4, (chunkZ + z) + 2, Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x) - 2, topBlock + 4, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 2, topBlock + 4, (chunkZ + z) - 2, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 2, topBlock + 4, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 2, topBlock + 4, (chunkZ + z) + 2, Block.leaves.blockID]);
|
||||
|
||||
// Layer 3
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 5, (chunkZ + z), Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 5, (chunkZ + z), Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 5, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 5, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 5, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 5, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 5, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 5, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 5, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
|
||||
// Layer 4
|
||||
treeBlocks.push([(chunkX + x) - 1, topBlock + 6, (chunkZ + z), Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x) + 1, topBlock + 6, (chunkZ + z), Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 6, (chunkZ + z) - 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 6, (chunkZ + z) + 1, Block.leaves.blockID]);
|
||||
treeBlocks.push([(chunkX + x), topBlock + 6, (chunkZ + z), Block.leaves.blockID]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [chunk, treeBlocks];
|
||||
}
|
|
@ -1,307 +0,0 @@
|
|||
/**
|
||||
* A Perlin Noise library for JavaScript.
|
||||
*
|
||||
* Based on implementations by Ken Perlin (Java, C) & Stefan Gustavson (C).
|
||||
*
|
||||
* MIT License.
|
||||
* Copyright (c) 2018 Leonardo de S.L.F.
|
||||
* http://leodeslf.com/
|
||||
*/
|
||||
|
||||
// Permutation table.
|
||||
|
||||
const p = [
|
||||
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140,
|
||||
36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234,
|
||||
75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237,
|
||||
149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48,
|
||||
27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105,
|
||||
92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73,
|
||||
209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86,
|
||||
164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38,
|
||||
147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189,
|
||||
28, 42, 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153,
|
||||
101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224,
|
||||
232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144,
|
||||
12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214,
|
||||
31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150,
|
||||
254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66,
|
||||
215, 61, 156, 180
|
||||
];
|
||||
const P = [...p, ...p];
|
||||
|
||||
// Utility functions.
|
||||
|
||||
function fade(t) {
|
||||
return t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
function lerp(t, a, b) {
|
||||
return a + t * (b - a);
|
||||
}
|
||||
|
||||
// Helper functions to find gradients for each vertex.
|
||||
|
||||
function gradient1D(hash, x) {
|
||||
return hash & 1 ? -x : x;
|
||||
}
|
||||
|
||||
function gradient2D(hash, x, y) {
|
||||
const H = hash & 3;
|
||||
return (H < 2 ? x : -x) + (H === 0 || H === 2 ? y : -y);
|
||||
}
|
||||
|
||||
function gradient3D(hash, x, y, z) {
|
||||
const
|
||||
H = hash & 15,
|
||||
U = H < 8 ? x : y,
|
||||
V = H < 4 ? y : H === 12 || H === 14 ? x : z;
|
||||
return ((H & 1) ? -U : U) + ((H & 2) ? -V : V);
|
||||
}
|
||||
|
||||
function gradient4D(hash, x, y, z, t) {
|
||||
const
|
||||
H = hash & 31,
|
||||
U = H < 24 ? x : y,
|
||||
V = H < 16 ? y : z,
|
||||
W = H < 8 ? z : t;
|
||||
return ((H & 1) ? -U : U) + ((H & 2) ? -V : V) + ((H & 4) ? -W : W);
|
||||
}
|
||||
|
||||
// Perlin Noise functions.
|
||||
|
||||
/**
|
||||
* Returns a one-dimensional noise value between -1 and 1.
|
||||
* @param {number} x A numeric expression.
|
||||
* @returns {number} Perlin Noise value.
|
||||
*/
|
||||
function perlin1D(x) {
|
||||
const
|
||||
FX = Math.floor(x),
|
||||
X = FX & 255;
|
||||
|
||||
x = x - FX;
|
||||
|
||||
return lerp(
|
||||
fade(x),
|
||||
gradient1D(P[P[X]], x),
|
||||
gradient1D(P[P[X + 1]], x - 1)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a two-dimensional noise value between -1 and 1.
|
||||
* @param {number} x A numeric expression.
|
||||
* @param {number} y A numeric expression.
|
||||
* @returns {number} Perlin Noise value.
|
||||
*/
|
||||
function perlin2D(x = 0, y = 0) {
|
||||
// Hack to allow this to work with integer block coords
|
||||
x += 0.2;
|
||||
y += 0.2;
|
||||
|
||||
const FX = Math.floor(x),
|
||||
FY = Math.floor(y),
|
||||
X = FX & 255,
|
||||
Y = FY & 255,
|
||||
A = P[X] + Y,
|
||||
B = P[X + 1] + Y;
|
||||
|
||||
x = x - FX;
|
||||
y = y - FY;
|
||||
|
||||
const
|
||||
FDX = fade(x),
|
||||
x1 = x - 1,
|
||||
y1 = y - 1;
|
||||
|
||||
return lerp(
|
||||
fade(y),
|
||||
lerp(
|
||||
FDX,
|
||||
gradient2D(P[A], x, y),
|
||||
gradient2D(P[B], x1, y)
|
||||
),
|
||||
lerp(
|
||||
FDX,
|
||||
gradient2D(P[A + 1], x, y1),
|
||||
gradient2D(P[B + 1], x1, y1)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a three-dimensional noise value between -1 and 1.
|
||||
* @param {number} x A numeric expression.
|
||||
* @param {number} y A numeric expression.
|
||||
* @param {number} z A numeric expression.
|
||||
* @returns {number} Perlin Noise value.
|
||||
*/
|
||||
function perlin3D(x, y, z) {
|
||||
const
|
||||
FX = Math.floor(x),
|
||||
FY = Math.floor(y),
|
||||
FZ = Math.floor(z),
|
||||
X = FX & 255,
|
||||
Y = FY & 255,
|
||||
Z = FZ & 255,
|
||||
A = P[X] + Y,
|
||||
B = P[X + 1] + Y,
|
||||
AA = P[A] + Z,
|
||||
BA = P[B] + Z,
|
||||
AB = P[A + 1] + Z,
|
||||
BB = P[B + 1] + Z;
|
||||
|
||||
x = x - FX;
|
||||
y = y - FY;
|
||||
z = z - FZ;
|
||||
|
||||
const
|
||||
FDX = fade(x),
|
||||
FDY = fade(y),
|
||||
x1 = x - 1,
|
||||
y1 = y - 1,
|
||||
z1 = z - 1;
|
||||
|
||||
return lerp(
|
||||
fade(z),
|
||||
lerp(
|
||||
FDY,
|
||||
lerp(
|
||||
FDX,
|
||||
gradient3D(P[AA], x, y, z),
|
||||
gradient3D(P[BA], x1, y, z)
|
||||
),
|
||||
lerp(
|
||||
FDX,
|
||||
gradient3D(P[AB], x, y1, z),
|
||||
gradient3D(P[BB], x1, y1, z)
|
||||
)
|
||||
),
|
||||
lerp(
|
||||
FDY,
|
||||
lerp(
|
||||
FDX,
|
||||
gradient3D(P[AA + 1], x, y, z1),
|
||||
gradient3D(P[BA + 1], x1, y, z1)
|
||||
),
|
||||
lerp(
|
||||
FDX,
|
||||
gradient3D(P[AB + 1], x, y1, z1),
|
||||
gradient3D(P[BB + 1], x1, y1, z1)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a four-dimensional noise value between -1 and 1.
|
||||
* @param {number} x A numeric expression.
|
||||
* @param {number} y A numeric expression.
|
||||
* @param {number} z A numeric expression.
|
||||
* @param {number} t A numeric expression.
|
||||
* @returns {number} Perlin Noise value.
|
||||
*/
|
||||
function perlin4D(x, y, z, t) {
|
||||
const
|
||||
FX = Math.floor(x),
|
||||
FY = Math.floor(y),
|
||||
FZ = Math.floor(z),
|
||||
FT = Math.floor(t),
|
||||
X = FX & 255,
|
||||
Y = FY & 255,
|
||||
Z = FZ & 255,
|
||||
T = FT & 255,
|
||||
A = P[X] + Y,
|
||||
B = P[X + 1] + Y,
|
||||
AA = P[A] + Z,
|
||||
BA = P[B] + Z,
|
||||
AB = P[A + 1] + Z,
|
||||
BB = P[B + 1] + Z,
|
||||
AAA = P[AA] + T,
|
||||
BAA = P[BA] + T,
|
||||
ABA = P[AB] + T,
|
||||
BBA = P[BB] + T,
|
||||
AAB = P[AA + 1] + T,
|
||||
BAB = P[BA + 1] + T,
|
||||
ABB = P[AB + 1] + T,
|
||||
BBB = P[BB + 1] + T;
|
||||
|
||||
x = x - FX;
|
||||
y = y - FY;
|
||||
z = z - FZ;
|
||||
t = t - FT;
|
||||
|
||||
const
|
||||
FDX = fade(x),
|
||||
FDY = fade(y),
|
||||
FDZ = fade(z),
|
||||
x1 = x - 1,
|
||||
y1 = y - 1,
|
||||
z1 = z - 1,
|
||||
t1 = t - 1;
|
||||
|
||||
return lerp(
|
||||
fade(t),
|
||||
lerp(
|
||||
FDZ,
|
||||
lerp(
|
||||
FDY,
|
||||
lerp(
|
||||
FDX,
|
||||
gradient4D(P[AAA], x, y, z, t),
|
||||
gradient4D(P[BAA], x1, y, z, t)
|
||||
),
|
||||
lerp(
|
||||
FDX,
|
||||
gradient4D(P[ABA], x, y1, z, t),
|
||||
gradient4D(P[BBA], x1, y1, z, t)
|
||||
)
|
||||
),
|
||||
lerp(
|
||||
FDY,
|
||||
lerp(
|
||||
FDX,
|
||||
gradient4D(P[AAB], x, y, z1, t),
|
||||
gradient4D(P[BAB], x1, y, z1, t)
|
||||
),
|
||||
lerp(
|
||||
FDX,
|
||||
gradient4D(P[ABB], x, y1, z1, t),
|
||||
gradient4D(P[BBB], x1, y1, z1, t)
|
||||
)
|
||||
)
|
||||
),
|
||||
lerp(
|
||||
FDZ,
|
||||
lerp(
|
||||
FDY,
|
||||
lerp(
|
||||
FDX,
|
||||
gradient4D(P[AAA + 1], x, y, z, t1),
|
||||
gradient4D(P[BAA + 1], x1, y, z, t1)
|
||||
),
|
||||
lerp(
|
||||
FDX,
|
||||
gradient4D(P[ABA + 1], x, y1, z, t1),
|
||||
gradient4D(P[BBA + 1], x1, y1, z, t1)
|
||||
)
|
||||
),
|
||||
lerp(
|
||||
FDY,
|
||||
lerp(
|
||||
FDX,
|
||||
gradient4D(P[AAB + 1], x, y, z1, t1),
|
||||
gradient4D(P[BAB + 1], x1, y, z1, t1)
|
||||
),
|
||||
lerp(
|
||||
FDX,
|
||||
gradient4D(P[ABB + 1], x, y1, z1, t1),
|
||||
gradient4D(P[BBB + 1], x1, y1, z1, t1)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { perlin1D, perlin2D, perlin3D, perlin4D };
|
|
@ -1,23 +0,0 @@
|
|||
const ItemStack = require("../ItemStack.js");
|
||||
|
||||
module.exports = class {
|
||||
constructor() {
|
||||
this.Slots = {};
|
||||
}
|
||||
|
||||
saveData() {
|
||||
|
||||
}
|
||||
|
||||
loadData() {
|
||||
|
||||
}
|
||||
|
||||
getSlot(slot = 0) {
|
||||
return this.Slots[slot]; // If the slot doesn't exist well sucks to be you I guess! Haha :Þ
|
||||
}
|
||||
|
||||
setSlot(slot = 0, itemStack = new ItemStack) {
|
||||
this.Slots[slot] = itemStack;
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
module.exports = class {
|
||||
constructor(id, count) {
|
||||
this.id = id;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
updateCount(count) {
|
||||
this.count += count;
|
||||
}
|
||||
}
|
336
server/MPClient.ts
Normal file
336
server/MPClient.ts
Normal file
|
@ -0,0 +1,336 @@
|
|||
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);
|
||||
}
|
||||
}
|
90
server/MetadataWriter.ts
Normal file
90
server/MetadataWriter.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
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();
|
||||
}
|
||||
}
|
296
server/MinecraftServer.ts
Normal file
296
server/MinecraftServer.ts
Normal file
|
@ -0,0 +1,296 @@
|
|||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
===========- NamedPackets.js -==========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const namedPackets = {
|
||||
"Disconnect": -1,
|
||||
"KeepAlive": 0x00,
|
||||
"LoginRequest": 0x01,
|
||||
"Handshake": 0x02,
|
||||
"ChatMessage": 0x03,
|
||||
"TimeUpdate": 0x04,
|
||||
"EntityEquipment": 0x05,
|
||||
"SpawnPosition": 0x06,
|
||||
"UseEntity": 0x07,
|
||||
"UpdateHealth": 0x08,
|
||||
"Respawn": 0x09,
|
||||
"Player": 0x0A,
|
||||
"PlayerPosition": 0x0B,
|
||||
"PlayerLook": 0x0C,
|
||||
"PlayerPositionAndLook": 0x0D,
|
||||
"PlayerDigging": 0x0E,
|
||||
"PlayerBlockPlacement": 0x0F,
|
||||
"HoldingChange": 0x10,
|
||||
"UseBed": 0x11,
|
||||
"Animation": 0x12,
|
||||
"EntityAction": 0x13,
|
||||
"NamedEntitySpawn": 0x14,
|
||||
"PickupSpawn": 0x15,
|
||||
"CollectItem": 0x16,
|
||||
"AddObjectOrVehicle": 0x17,
|
||||
"MobSpawn": 0x18,
|
||||
"EntityPainting": 0x19,
|
||||
"StanceUpdate": 0x1B,
|
||||
"EntityVelocity": 0x1C,
|
||||
"DestroyEntity": 0x1D,
|
||||
"Entity": 0x1E,
|
||||
"EntityRelativeMove": 0x1F,
|
||||
"EntityLook": 0x20,
|
||||
"EntityLookAndRelativeMove": 0x21,
|
||||
"EntityTeleport": 0x22,
|
||||
"EntityStatus": 0x26,
|
||||
"AttachEntity": 0x27,
|
||||
"EntityMetadata": 0x28,
|
||||
"PreChunk": 0x32,
|
||||
"MapChunk": 0x33,
|
||||
"MultiBlockChange": 0x34,
|
||||
"BlockChange": 0x35,
|
||||
"BlockAction": 0x36,
|
||||
"Explosion": 0x3C,
|
||||
"SoundEffect": 0x3D,
|
||||
"NewOrInvalidState": 0x46,
|
||||
"Thunderbolt": 0x47,
|
||||
"OpenWindow": 0x64,
|
||||
"CloseWindow": 0x65,
|
||||
"WindowClick": 0x66,
|
||||
"SetSlot": 0x67,
|
||||
"WindowItems": 0x68,
|
||||
"UpdateProgressBar": 0x69,
|
||||
"Transaction": 0x6A,
|
||||
"UpdateSign": 0x82,
|
||||
"ItemData": 0x83,
|
||||
"IncrementStatistic": 0xC8,
|
||||
"DisconnectOrKick": 0xFF
|
||||
};
|
||||
|
||||
module.exports = namedPackets;
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
========- PacketMappingTable.js -=======
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet0KeepAlive = require("./Packets/Packet0KeepAlive.js"),
|
||||
Packet1LoginRequest = require("./Packets/Packet1LoginRequest.js"),
|
||||
Packet2Handshake = require("./Packets/Packet2Handshake.js"),
|
||||
Packet3Chat = require("./Packets/Packet3Chat.js"),
|
||||
Packet4TimeUpdate = require("./Packets/Packet4TimeUpdate"),
|
||||
Packet6SpawnPosition = require("./Packets/Packet6SpawnPosition.js"),
|
||||
Packet10Player = require("./Packets/Packet10Player.js"),
|
||||
Packet13PlayerPositionAndLook = require("./Packets/Packet13PlayerPositionAndLook.js"),
|
||||
Packet18Animation = require("./Packets/Packet18Animation.js"),
|
||||
Packet20NamedEntitySpawn = require("./Packets/Packet20NamedEntitySpawn.js"),
|
||||
Packet32EntityLook = require("./Packets/Packet32EntityLook.js"),
|
||||
Packet34EntityTeleport = require("./Packets/Packet34EntityTeleport.js"),
|
||||
Packet50PreChunk = require("./Packets/Packet50PreChunk.js"),
|
||||
Packet53BlockChange = require("./Packets/Packet53BlockChange.js"),
|
||||
Packet103SetSlot = require("./Packets/Packet103SetSlot.js");
|
||||
|
||||
const mappingTable = {
|
||||
0x00: Packet0KeepAlive,
|
||||
0x01: Packet1LoginRequest,
|
||||
0x02: Packet2Handshake,
|
||||
0x03: Packet3Chat,
|
||||
0x04: Packet4TimeUpdate,
|
||||
0x06: Packet6SpawnPosition,
|
||||
0x0A: Packet10Player,
|
||||
0x0D: Packet13PlayerPositionAndLook,
|
||||
0x12: Packet18Animation,
|
||||
0x14: Packet20NamedEntitySpawn,
|
||||
0x20: Packet32EntityLook,
|
||||
0x22: Packet34EntityTeleport,
|
||||
0x32: Packet50PreChunk,
|
||||
0x35: Packet53BlockChange,
|
||||
0x67: Packet103SetSlot
|
||||
};
|
||||
|
||||
module.exports = mappingTable;
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
==============- Packet.js -=============
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const bufferStuff = require("../bufferStuff.js");
|
||||
|
||||
module.exports = class {
|
||||
constructor(packetID = 0x00) {
|
||||
this.id = packetID;
|
||||
|
||||
this.packetSize = 0;
|
||||
|
||||
this.writer = null;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
this.writer = new bufferStuff.Writer(this.packetSize);
|
||||
|
||||
this.writer.writeByte(this.id);
|
||||
|
||||
return this.writer;
|
||||
}
|
||||
|
||||
toBuffer() {
|
||||
return this.writer == null ? Buffer.alloc(0) : this.writer.buffer;
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
========- Packet0KeepAlive.js -=========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet0KeepAlive extends Packet {
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet0KeepAlive;
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
========- Packet103SetSlot.js -=========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet103SetSlot extends Packet {
|
||||
constructor(window_id = 0, slot = 0, item_id = -1, item_count = 0, item_uses = 0) {
|
||||
super(0x67);
|
||||
|
||||
this.window_id = window_id;
|
||||
this.slot = slot;
|
||||
this.item_id = item_id;
|
||||
this.item_count = item_count;
|
||||
this.item_uses = item_uses;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeByte(this.window_id);
|
||||
this.writer.writeShort(this.slot);
|
||||
this.writer.writeShort(this.item_id);
|
||||
this.writer.writeByte(this.item_count);
|
||||
if (this.item_id != -1) this.writer.writeShort(this.item_uses);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet103SetSlot;
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
=========- Packet10Player.js -==========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet10Player extends Packet {
|
||||
constructor(onGround = true) {
|
||||
super(0x0A);
|
||||
|
||||
this.onGround = onGround;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeBool(this.onGround);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet10Player;
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
==- Packet13PlayerPositionAndLook.js -==
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet13PlayerPositionAndLook extends Packet {
|
||||
constructor(x = 0, y = 65, stance = 67, z = 0, yaw = 0.0, pitch = 0.0, onGround = true) {
|
||||
super(0x0D);
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.stance = stance;
|
||||
this.z = z;
|
||||
this.yaw = yaw;
|
||||
this.pitch = pitch;
|
||||
this.onGround = onGround;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeDouble(this.x);
|
||||
this.writer.writeDouble(this.y);
|
||||
this.writer.writeDouble(this.stance);
|
||||
this.writer.writeDouble(this.z);
|
||||
this.writer.writeFloat(this.yaw);
|
||||
this.writer.writeFloat(this.pitch);
|
||||
this.writer.writeBool(this.onGround);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet13PlayerPositionAndLook;
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
========- Packet18Animation.js -========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet18Animation extends Packet {
|
||||
constructor(EID = 0, animation = 0) {
|
||||
super(0x12);
|
||||
|
||||
this.EID = EID;
|
||||
this.animation = animation;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeInt(this.EID);
|
||||
this.writer.writeByte(this.animation);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet18Animation;
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
=======- Packet1LoginRequest.js -=======
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet1LoginRequest extends Packet {
|
||||
constructor(protocol_version = 0, username = "", map_seed = BigInt(0), dimension = 0) {
|
||||
super(0x01);
|
||||
|
||||
this.protocol_version = protocol_version;
|
||||
this.username = username;
|
||||
this.map_seed = map_seed;
|
||||
this.dimension = dimension;
|
||||
}
|
||||
|
||||
readPacket() {
|
||||
|
||||
}
|
||||
|
||||
writePacket(EID = 0) {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeInt(EID);
|
||||
this.writer.writeString("");
|
||||
this.writer.writeLong(971768181197178410);
|
||||
this.writer.writeByte(0);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet1LoginRequest;
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
=====- Packet20NamedEntitySpawn.js -====
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
const Converter = require("../Converter.js");
|
||||
|
||||
class Packet20NamedEntitySpawn extends Packet {
|
||||
constructor(EID = 0, entityName = "", x = 0.0, y = 0.0, z = 0.0, yaw = 0.0, pitch = 0.0, currentItem = 0) {
|
||||
super(0x14);
|
||||
|
||||
this.EID = EID;
|
||||
this.entityName = entityName;
|
||||
this.absX = Converter.toAbsoluteInt(x);
|
||||
this.absY = Converter.toAbsoluteInt(y);
|
||||
this.absZ = Converter.toAbsoluteInt(z);
|
||||
this.packedYaw = Converter.to360Fraction(yaw);
|
||||
this.packedPitch = Converter.to360Fraction(pitch);
|
||||
this.currentItem = currentItem;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeInt(this.EID);
|
||||
this.writer.writeString(this.entityName);
|
||||
this.writer.writeInt(this.absX);
|
||||
this.writer.writeInt(this.absY);
|
||||
this.writer.writeInt(this.absZ);
|
||||
this.writer.writeUByte(this.packedYaw);
|
||||
this.writer.writeUByte(this.packedPitch);
|
||||
this.writer.writeShort(this.currentItem);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet20NamedEntitySpawn;
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
=========- Packet2Handshake.js -========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet2Handshake extends Packet {
|
||||
constructor(username = "") {
|
||||
super(0x02);
|
||||
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeString("-"); // "-" == Offline mode
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet2Handshake;
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
=======- Packet32EntityLook.js -========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
const Converter = require("../Converter.js");
|
||||
|
||||
class Packet32EntityLook extends Packet {
|
||||
constructor(EID = 0, yaw = 0, pitch = 0) {
|
||||
super(0x20);
|
||||
|
||||
this.packetSize = 7;
|
||||
|
||||
this.EID = EID;
|
||||
this.packedYaw = Converter.to360Fraction(yaw);
|
||||
this.packedPitch = Converter.to360Fraction(pitch);
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeInt(this.EID);
|
||||
this.writer.writeUByte(this.packedYaw);
|
||||
this.writer.writeUByte(this.packedPitch);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet32EntityLook;
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
======- Packet34EntityTeleport.js -=====
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
const Converter = require("../Converter.js");
|
||||
|
||||
class Packet34EntityTeleport extends Packet {
|
||||
constructor(EID = 0, x = 0, y = 0, z = 0, yaw = 0, pitch = 0) {
|
||||
super(0x22);
|
||||
|
||||
this.packetSize = 19;
|
||||
|
||||
this.EID = EID;
|
||||
this.absX = Converter.toAbsoluteInt(x);
|
||||
this.absY = Converter.toAbsoluteInt(y);
|
||||
this.absZ = Converter.toAbsoluteInt(z);
|
||||
this.packedYaw = Converter.to360Fraction(yaw);
|
||||
this.packedPitch = Converter.to360Fraction(pitch);
|
||||
}
|
||||
|
||||
soup() {
|
||||
super.writePacket();
|
||||
this.writer.writeInt(this.EID);
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
this.writer.writeInt(this.absX);
|
||||
this.writer.writeInt(this.absY);
|
||||
this.writer.writeInt(this.absZ);
|
||||
this.writer.writeUByte(this.packedYaw);
|
||||
this.writer.writeUByte(this.packedPitch);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet34EntityTeleport;
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
===========- Packet3Chat.js -===========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet3Chat extends Packet {
|
||||
constructor(message = "") {
|
||||
super(0x03);
|
||||
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeString(this.message);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet3Chat;
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
========- Packet4TimeUpdate.js -========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet4TimeUpdate extends Packet {
|
||||
constructor(time = BigInt(0)) {
|
||||
super(0x04);
|
||||
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
readPacket() {
|
||||
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeLong(this.time);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet4TimeUpdate;
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
========- Packet50PreChunk.js -=========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet50PreChunk extends Packet {
|
||||
constructor(x = 0, z = 0, mode = true) {
|
||||
super(0x32);
|
||||
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeInt(this.x);
|
||||
this.writer.writeInt(this.z);
|
||||
this.writer.writeBool(this.mode);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet50PreChunk;
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
=======- Packet53BlockChange.js -=======
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet53BlockChange extends Packet {
|
||||
constructor(x = 0, y = 0, z = 0, block_type = 0, block_metadata = 0) {
|
||||
super(0x35);
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.block_type = block_type;
|
||||
this.block_metadata = block_metadata;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeInt(this.x);
|
||||
this.writer.writeByte(this.y);
|
||||
this.writer.writeInt(this.z);
|
||||
this.writer.writeByte(this.block_type);
|
||||
this.writer.writeByte(this.block_metadata);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet53BlockChange;
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
======- Packet6SpawnPosition.js -=======
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const Packet = require("./Packet.js");
|
||||
|
||||
class Packet6SpawnPosition extends Packet {
|
||||
constructor(x = 8.5, y = 65.5, z = 8.5) {
|
||||
super(0x06);
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
}
|
||||
|
||||
writePacket() {
|
||||
super.writePacket();
|
||||
|
||||
this.writer.writeInt(this.x);
|
||||
this.writer.writeInt(this.y);
|
||||
this.writer.writeInt(this.z);
|
||||
|
||||
return this.toBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Packet6SpawnPosition;
|
17
server/Random.ts
Normal file
17
server/Random.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
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();
|
||||
}
|
||||
}
|
19
server/Rotation.ts
Normal file
19
server/Rotation.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
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;
|
||||
}
|
||||
}
|
23
server/TrackedProperty.ts
Normal file
23
server/TrackedProperty.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
===========- funkyArray.js -============
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const pRandom = require("./prettyRandom.js");
|
||||
|
||||
module.exports = class {
|
||||
constructor(indexingMode = false) {
|
||||
this.items = {};
|
||||
this.itemKeys = Object.keys(this.items);
|
||||
|
||||
this.indexingMode = indexingMode;
|
||||
this.index = 0;
|
||||
|
||||
this.iterableArray = [];
|
||||
}
|
||||
|
||||
add(item, regenerate = true) {
|
||||
let id;
|
||||
if (this.indexingMode) {
|
||||
this.items[id = this.index] = item;
|
||||
this.index++;
|
||||
} else {
|
||||
this.items[id = pRandom()] = item;
|
||||
}
|
||||
|
||||
if (regenerate) {
|
||||
this.regenerateIterableArray();
|
||||
this.itemKeys = Object.keys(this.items);
|
||||
}
|
||||
|
||||
return this.items[id];
|
||||
}
|
||||
|
||||
remove(id, regenerate = true) {
|
||||
delete this.items[id];
|
||||
if (regenerate) {
|
||||
this.regenerateIterableArray();
|
||||
this.itemKeys = Object.keys(this.items);
|
||||
}
|
||||
}
|
||||
|
||||
removeFirstItem(regenerate = true) {
|
||||
delete this.items[this.itemKeys[0]];
|
||||
if (regenerate) this.regenerateIterableArray();
|
||||
this.itemKeys = Object.keys(this.items);
|
||||
}
|
||||
|
||||
regenerateIterableArray() {
|
||||
this.iterableArray = new Array();
|
||||
for (let itemKey of this.itemKeys) {
|
||||
this.iterableArray.push(this.items[itemKey]);
|
||||
}
|
||||
this.itemKeys = Object.keys(this.items);
|
||||
}
|
||||
|
||||
getFirstItem() {
|
||||
return this.items[this.itemKeys[0]];
|
||||
}
|
||||
|
||||
getLength() {
|
||||
return this.itemKeys.length;
|
||||
}
|
||||
|
||||
getKeyById(id) {
|
||||
return this.itemKeys[id];
|
||||
}
|
||||
|
||||
getById(id) {
|
||||
return this.items[this.itemKeys[id]];
|
||||
}
|
||||
|
||||
getByKey(key) {
|
||||
return this.items[key];
|
||||
}
|
||||
|
||||
getKeys() {
|
||||
return this.itemKeys;
|
||||
}
|
||||
|
||||
getItems() {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
getIterableItems() {
|
||||
return this.iterableArray;
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
===========- prettyRandom.js -==========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
let lastRandom = -1;
|
||||
|
||||
function pRandom(from = 0, to = 2147483647) {
|
||||
let thisRandom = Math.floor(map(Math.random(), 0, 1, from, to));
|
||||
if (thisRandom == lastRandom) thisRandom = pRandom(from, to);
|
||||
|
||||
return thisRandom;
|
||||
}
|
||||
|
||||
function map(input, inputMin, inputMax, outputMin, outputMax) {
|
||||
const newv = (input - inputMin) / (inputMax - inputMin) * (outputMax - outputMin) + outputMin;
|
||||
|
||||
if (outputMin < outputMax) return constrain(newv, outputMin, outputMax);
|
||||
else return constrain(newv, outputMax, outputMin);
|
||||
}
|
||||
|
||||
function constrain(input, low, high) {
|
||||
return Math.max(Math.min(input, high), low);
|
||||
}
|
||||
|
||||
module.exports = pRandom;
|
35
server/Vec2.ts
Normal file
35
server/Vec2.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
75
server/Vec3.ts
Normal file
75
server/Vec3.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
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));
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
/*
|
||||
===========- ChunkWorker.js -===========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const { parentPort } = require('worker_threads'),
|
||||
{ deflateSync } = require("zlib");
|
||||
|
||||
const GeneratorFlat = require("../Generators/GeneratorFlat.js");
|
||||
const GeneratorPerlin = require("../Generators/GeneratorPerlin.js");
|
||||
|
||||
const bufferStuff = require("../bufferStuff.js");
|
||||
|
||||
let busyInterval = null;
|
||||
|
||||
parentPort.on("message", (data) => {
|
||||
// This stops the thread from stopping :)
|
||||
if (busyInterval == null) busyInterval = setInterval(() => {}, 86400000); // Runs once a day
|
||||
|
||||
switch (data[0]) {
|
||||
case "chunk":
|
||||
parentPort.postMessage([data[0], doChunk(data[1]), data[2]]);
|
||||
break;
|
||||
|
||||
case "generate":
|
||||
const startTime = Date.now();
|
||||
const chunkData = generateChunk(data[1], data[2], data[4]);
|
||||
parentPort.postMessage([data[0], chunkData[0], data[1], data[2], data[3], chunkData[1]]);
|
||||
console.log(`Chunk took ${Date.now() - startTime}ms`);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function generateChunk(x = 0, z = 0, seed = 0) {
|
||||
return GeneratorPerlin(x, z, seed);
|
||||
}
|
||||
|
||||
function doChunk(chunk) {
|
||||
const writer = new bufferStuff.Writer(18);
|
||||
|
||||
writer.writeByte(0x33); // Chunk
|
||||
writer.writeInt(chunk[0] << 4); // Chunk X
|
||||
writer.writeShort(0 << 7); // Chunk Y
|
||||
writer.writeInt(chunk[1] << 4); // Chunk Z
|
||||
writer.writeByte(15); // Size X
|
||||
writer.writeByte(127); // Size Y
|
||||
writer.writeByte(15); // Size Z
|
||||
|
||||
// pre-alloc since doing an alloc 98,304 times takes a while yknow.
|
||||
const blocks = new bufferStuff.Writer(32768);
|
||||
const metadata = new bufferStuff.Writer(32768);
|
||||
const lighting = new bufferStuff.Writer(32768);
|
||||
|
||||
let blockMeta = false;
|
||||
for (let x = 0; x < 16; x++) {
|
||||
for (let z = 0; z < 16; z++) {
|
||||
for (let y = 0; y < 128; y++) {
|
||||
blocks.writeByte(chunk[2][y][x][z][0]);
|
||||
if (blockMeta) {
|
||||
metadata.writeNibble(chunk[2][y - 1][x][z][1], chunk[2][y][x][z][1]); // NOTE: This is sorta jank but it does work
|
||||
// Light level 15 for 2 blocks (1111 1111)
|
||||
lighting.writeNibble(15, 15); // TODO: Lighting (Client seems to do it's own (when a block update happens) so it's not top priority)
|
||||
}
|
||||
// Hack for nibble stuff
|
||||
blockMeta = !blockMeta;
|
||||
}
|
||||
}
|
||||
}
|
||||
// These are hacks for the nibbles
|
||||
blocks.writeBuffer(metadata.buffer);
|
||||
blocks.writeBuffer(lighting.buffer); // Block lighting
|
||||
//blocks.writeBuffer(lighting.buffer); // Sky lighting (Looks like this isn't needed???)
|
||||
|
||||
// We're on another thread we don't care if we halt
|
||||
const deflated = deflateSync(blocks.buffer);
|
||||
writer.writeInt(deflated.length); // Compressed Size
|
||||
writer.writeBuffer(deflated); // Compressed chunk data
|
||||
|
||||
return writer.buffer;
|
||||
}
|
333
server/World.ts
Normal file
333
server/World.ts
Normal file
|
@ -0,0 +1,333 @@
|
|||
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);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
299
server/WorldSaveManager.ts
Normal file
299
server/WorldSaveManager.ts
Normal file
|
@ -0,0 +1,299 @@
|
|||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
256
server/blocks/Block.ts
Normal file
256
server/blocks/Block.ts
Normal file
|
@ -0,0 +1,256 @@
|
|||
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
|
||||
}
|
16
server/blocks/BlockBehaviorSapling.ts
Normal file
16
server/blocks/BlockBehaviorSapling.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
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!!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
server/blocks/BlockBehaviorSugarcane.ts
Normal file
46
server/blocks/BlockBehaviorSugarcane.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
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);
|
||||
}
|
||||
}
|
16
server/blocks/BlockBehaviour.ts
Normal file
16
server/blocks/BlockBehaviour.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
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; }
|
||||
}
|
12
server/blocks/BlockBehaviourClay.ts
Normal file
12
server/blocks/BlockBehaviourClay.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
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;
|
||||
}
|
||||
}
|
17
server/blocks/BlockBehaviourFlower.ts
Normal file
17
server/blocks/BlockBehaviourFlower.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
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);
|
||||
}
|
||||
}
|
8
server/blocks/BlockBehaviourGrass.ts
Normal file
8
server/blocks/BlockBehaviourGrass.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import Block from "./Block";
|
||||
import BlockBehaviour from "./BlockBehaviour";
|
||||
|
||||
export default class BlockBehaviourGrass extends BlockBehaviour {
|
||||
public droppedItem(blockId:number) {
|
||||
return Block.dirt.blockId;
|
||||
}
|
||||
}
|
12
server/blocks/BlockBehaviourOre.ts
Normal file
12
server/blocks/BlockBehaviourOre.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
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;
|
||||
}
|
||||
}
|
12
server/blocks/BlockBehaviourRedstoneOre.ts
Normal file
12
server/blocks/BlockBehaviourRedstoneOre.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
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;
|
||||
}
|
||||
}
|
8
server/blocks/BlockBehaviourStone.ts
Normal file
8
server/blocks/BlockBehaviourStone.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import Block from "./Block";
|
||||
import BlockBehaviour from "./BlockBehaviour";
|
||||
|
||||
export default class BlockBehaviourStone extends BlockBehaviour {
|
||||
public droppedItem(blockId:number) {
|
||||
return Block.cobblestone.blockId;
|
||||
}
|
||||
}
|
21
server/blocks/BlockBehaviourTallGrass.ts
Normal file
21
server/blocks/BlockBehaviourTallGrass.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
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);
|
||||
}
|
||||
}
|
15
server/blocks/IBlockBehaviour.ts
Normal file
15
server/blocks/IBlockBehaviour.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
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
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
/*
|
||||
===========- bufferStuff.js -===========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
module.exports.Writer = class {
|
||||
// bufferStuff supports pre-allocating memory for the buffer
|
||||
// if you pass in a size of 0 then it will just dynamicly allocate at the
|
||||
// cost of performance
|
||||
|
||||
// NOTE: In pre-allocation mode if you exceed the size of the buffer
|
||||
// that you set it will cause a crash.
|
||||
constructor(size = 0) {
|
||||
this.buffer = Buffer.alloc(size);
|
||||
this.offset = 0;
|
||||
this.baseSize = size;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.buffer = Buffer.alloc(0);
|
||||
}
|
||||
|
||||
writeBuffer(buff = Buffer.alloc(0)) {
|
||||
this.buffer = Buffer.concat([this.buffer, buff], this.buffer.length + buff.length);
|
||||
}
|
||||
|
||||
writeBool(data = false) {
|
||||
this.writeByte(data ? 1 : 0);
|
||||
}
|
||||
|
||||
// NOTE: Currently writing a nibble requires you to write both halves at the same time.
|
||||
writeNibble(nibble1 = 0, nibble2 = 0) {
|
||||
this.writeUByte(nibble1 | (nibble2 << 4));
|
||||
}
|
||||
|
||||
writeByte(data = 0) {
|
||||
if (this.baseSize == 0) {
|
||||
const buff = Buffer.alloc(1);
|
||||
buff.writeInt8(data, 0);
|
||||
|
||||
this.writeBuffer(buff);
|
||||
} else {
|
||||
this.buffer.writeInt8(data, this.offset);
|
||||
this.offset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
writeUByte(data = 0) {
|
||||
if (this.baseSize == 0) {
|
||||
const buff = Buffer.alloc(1);
|
||||
buff.writeUInt8(data, 0);
|
||||
|
||||
this.writeBuffer(buff);
|
||||
} else {
|
||||
this.buffer.writeUInt8(data, this.offset);
|
||||
this.offset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
writeByteArray(data = [0]) {
|
||||
if (this.baseSize == 0) {
|
||||
const buff = Buffer.alloc(data.length);
|
||||
|
||||
for (let byte of data) {
|
||||
buff.writeInt8(byte);
|
||||
}
|
||||
|
||||
this.writeBuffer(buff);
|
||||
} else {
|
||||
for (let byte of data) {
|
||||
this.buffer.writeInt8(byte);
|
||||
this.offset += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeShort(data = 0) {
|
||||
if (this.baseSize == 0) {
|
||||
const buff = Buffer.alloc(2);
|
||||
buff.writeIntBE(data, 0, 2);
|
||||
|
||||
this.writeBuffer(buff);
|
||||
} else {
|
||||
this.buffer.writeIntBE(data, this.offset, 2);
|
||||
this.offset += 2;
|
||||
}
|
||||
}
|
||||
|
||||
writeShortArray(data = [0]) {
|
||||
if (this.baseSize == 0) {
|
||||
const buff = Buffer.alloc(data.length * 2);
|
||||
let offset = 0;
|
||||
|
||||
for (let short of data) {
|
||||
buff.writeIntBE(short, offset, 2);
|
||||
offset += 2;
|
||||
}
|
||||
|
||||
this.writeBuffer(buff);
|
||||
} else {
|
||||
for (let short of data) {
|
||||
this.buffer.writeIntBE(short, this.offset, 2);
|
||||
this.offset += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeInt(data = 0) {
|
||||
if (this.baseSize == 0) {
|
||||
const buff = Buffer.alloc(4);
|
||||
buff.writeIntBE(data, 0, 4);
|
||||
|
||||
this.writeBuffer(buff);
|
||||
} else {
|
||||
this.buffer.writeIntBE(data, this.offset, 4);
|
||||
this.offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
writeLong(data = 0) {
|
||||
if (this.baseSize == 0) {
|
||||
const buff = Buffer.alloc(8);
|
||||
if (data instanceof BigInt) buff.writeBigInt64BE(data, 0);
|
||||
else buff.writeBigInt64BE(BigInt(data), 0);
|
||||
|
||||
this.writeBuffer(buff);
|
||||
} else {
|
||||
if (data instanceof BigInt) this.buffer.writeBigInt64BE(data, this.offset);
|
||||
else this.buffer.writeBigInt64BE(BigInt(data), this.offset);
|
||||
this.offset += 8;
|
||||
}
|
||||
}
|
||||
|
||||
writeFloat(data = 0.0) {
|
||||
if (this.baseSize == 0) {
|
||||
const buff = Buffer.alloc(4);
|
||||
buff.writeFloatBE(data, 0);
|
||||
|
||||
this.writeBuffer(buff);
|
||||
} else {
|
||||
this.buffer.writeFloatBE(data, this.offset);
|
||||
this.offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
writeDouble(data = 0.0) {
|
||||
if (this.baseSize == 0) {
|
||||
const buff = Buffer.alloc(8);
|
||||
buff.writeDoubleBE(data, 0);
|
||||
|
||||
this.writeBuffer(buff);
|
||||
} else {
|
||||
this.buffer.writeDoubleBE(data, this.offset);
|
||||
this.offset += 8;
|
||||
}
|
||||
}
|
||||
|
||||
writeString(string = "") {
|
||||
this.writeShort(string.length);
|
||||
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
this.writeShort(string.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.Reader = class {
|
||||
constructor(buffer = Buffer.alloc(0)) {
|
||||
this.buffer = buffer;
|
||||
this.offset = 0;
|
||||
}
|
||||
|
||||
readBool() {
|
||||
return this.readByte() == 0x01 ? true : false;
|
||||
}
|
||||
|
||||
readByte() {
|
||||
const data = this.buffer.readInt8(this.offset);
|
||||
this.offset += 1;
|
||||
return data;
|
||||
}
|
||||
|
||||
readShort() {
|
||||
const data = this.buffer.readIntBE(this.offset, 2);
|
||||
this.offset += 2;
|
||||
return data;
|
||||
}
|
||||
|
||||
readInt() {
|
||||
const data = this.buffer.readIntBE(this.offset, 4);
|
||||
this.offset += 4;
|
||||
return data;
|
||||
}
|
||||
|
||||
readLong() {
|
||||
const data = this.buffer.readBigInt64BE(this.offset);
|
||||
this.offset += 8;
|
||||
return data;
|
||||
}
|
||||
|
||||
readFloat() {
|
||||
const data = this.buffer.readFloatBE(this.offset);
|
||||
this.offset += 4;
|
||||
return data;
|
||||
}
|
||||
|
||||
readDouble() {
|
||||
const data = this.buffer.readDoubleBE(this.offset);
|
||||
this.offset += 8;
|
||||
return data;
|
||||
}
|
||||
|
||||
readString() {
|
||||
const length = this.readShort();
|
||||
let data = "";
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
data += String.fromCharCode(this.readShort());
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
|
@ -1,130 +0,0 @@
|
|||
/*
|
||||
===========- chunkManager.js -==========
|
||||
Created by Holly (tgpethan) (c) 2021
|
||||
Licenced under MIT
|
||||
========================================
|
||||
*/
|
||||
|
||||
const { Worker } = require('worker_threads');
|
||||
const FunkyArray = require("./Util/funkyArray.js");
|
||||
const pRandom = require("./Util/prettyRandom.js");
|
||||
const config = require("../config.json");
|
||||
|
||||
const workerPath = `${__dirname}/Workers/ChunkWorker.js`;
|
||||
|
||||
module.exports = class {
|
||||
constructor() {
|
||||
this.chunks = {};
|
||||
|
||||
this.queuedBlockUpdates = new FunkyArray();
|
||||
|
||||
global.generatingChunks = true;
|
||||
|
||||
this.threadPool = [];
|
||||
this.workPool = new FunkyArray();
|
||||
this.toRemove = [];
|
||||
|
||||
// TODO: Figure out a better way of doing this?
|
||||
this.seed = pRandom(-2147483647, 2147483647) - new Date().getTime() * pRandom(1, 6);
|
||||
|
||||
// WoAh!!! Thread pool in js!?!??!???!11!?!?!
|
||||
for (let i = 0; i < config.worldThreads; i++) {
|
||||
const worker = new Worker(workerPath);
|
||||
this.threadPool.push([false, worker]);
|
||||
const myID = i;
|
||||
worker.on("message", (data) => {
|
||||
let user;
|
||||
switch (data[0]) {
|
||||
case "chunk":
|
||||
user = global.getUserByKey(data[2]);
|
||||
user.chunksToSend.add(Buffer.from(data[1]));
|
||||
this.toRemove.push(data[1]);
|
||||
this.threadPool[myID][0] = false;
|
||||
break;
|
||||
|
||||
case "generate":
|
||||
this.chunks[data[2]][data[3]] = data[1];
|
||||
this.toRemove.push(data[4]);
|
||||
const treeBlocksRef = data[5];
|
||||
treeBlocksRef.forEach((block) => {
|
||||
this.setBlock(block[0], block[1], block[2], block[3], 0, false);
|
||||
});
|
||||
this.queuedBlockUpdates.regenerateIterableArray();
|
||||
this.threadPool[myID][0] = false;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Created thread pool with " + this.threadPool.length + " threads");
|
||||
|
||||
setInterval(() => {
|
||||
if (this.workPool.getLength() > 0) {
|
||||
let limit = Math.min(this.workPool.getLength(), this.threadPool.length);
|
||||
for (let i = 0; i < limit; i++) {
|
||||
for (let i1 = 0; i1 < this.threadPool.length; i1++) {
|
||||
let thread = this.threadPool[i1];
|
||||
if (!thread[0]) {
|
||||
const key = this.workPool.itemKeys[i];
|
||||
const item = this.workPool.getByKey(key);
|
||||
// Already being processed
|
||||
if (item == null) break;
|
||||
if (item[0] == true) {
|
||||
limit += 1;
|
||||
break;
|
||||
}
|
||||
item[0] = true;
|
||||
item[1][3] = key;
|
||||
thread[1].postMessage(item[1]);
|
||||
thread[0] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let item of this.toRemove) {
|
||||
//console.log("removing item " + this.workPool.getByKey(item)[0]);
|
||||
this.workPool.remove(item);
|
||||
}
|
||||
this.toRemove = [];
|
||||
}
|
||||
}, 1000 / 60);
|
||||
|
||||
global.generatingChunks = false;
|
||||
}
|
||||
|
||||
// TODO: Store metadata!
|
||||
createChunk(cx = 0, cz = 0) {
|
||||
if (this.chunks[cx] == null) this.chunks[cx] = {};
|
||||
|
||||
this.workPool.add([false, ["generate", cx, cz, null, this.seed]]);
|
||||
}
|
||||
|
||||
chunkExists(cx = 0, cz = 0) {
|
||||
if (this.chunks[cx] == null) return false;
|
||||
if (this.chunks[cx][cz] == null) return false;
|
||||
|
||||
// In any other case the chunk exists
|
||||
return true;
|
||||
}
|
||||
|
||||
multiBlockChunk(chunkX = 0, chunkZ = 0, user) {
|
||||
this.workPool.add([false, ["chunk", [chunkX, chunkZ, this.chunks[chunkX][chunkZ]], user.id, null]]);
|
||||
}
|
||||
|
||||
setBlock(x = 0, y = 0, z = 0, id = 0, metadata = 0, regenerate = true) {
|
||||
if (y < 0 || y > 127) return console.error("Tried to set a block outside of the world!");
|
||||
|
||||
const chunkX = x >> 4;
|
||||
const chunkZ = z >> 4;
|
||||
const blockX = x - (chunkX << 4);
|
||||
const blockZ = z - (chunkZ << 4);
|
||||
|
||||
// Don't queue a block update if that block is already this block (wow those ifs)
|
||||
if (this.chunks[chunkX] != null)
|
||||
if (this.chunks[chunkX][chunkZ] != null)
|
||||
if (this.chunks[chunkX][chunkZ][y][blockX][blockZ] == id) return;
|
||||
|
||||
this.queuedBlockUpdates.add([chunkX, chunkZ, y, blockX, blockZ, id, metadata], regenerate);
|
||||
}
|
||||
}
|
314
server/entities/Entity.ts
Normal file
314
server/entities/Entity.ts
Normal file
|
@ -0,0 +1,314 @@
|
|||
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);
|
||||
}
|
||||
}
|
61
server/entities/EntityItem.ts
Normal file
61
server/entities/EntityItem.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
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();
|
||||
}
|
||||
}
|
||||
}
|
84
server/entities/EntityLiving.ts
Normal file
84
server/entities/EntityLiving.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
16
server/entities/IEntity.ts
Normal file
16
server/entities/IEntity.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
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
|
||||
}
|
201
server/entities/Player.ts
Normal file
201
server/entities/Player.ts
Normal file
|
@ -0,0 +1,201 @@
|
|||
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());
|
||||
}
|
||||
}
|
||||
}
|
11
server/enums/Animation.ts
Normal file
11
server/enums/Animation.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
enum Animation {
|
||||
None = 0,
|
||||
SwingArm = 1,
|
||||
Damage = 2,
|
||||
LeaveBed = 3,
|
||||
Unknown102 = 102,
|
||||
Crouch = 104,
|
||||
Uncrouch = 105,
|
||||
}
|
||||
|
||||
export default Animation;
|
10
server/enums/EntityStatus.ts
Normal file
10
server/enums/EntityStatus.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
enum EntityStatus {
|
||||
Unknown0,
|
||||
Unknown1,
|
||||
Hurt,
|
||||
Dead,
|
||||
Unknown4,
|
||||
Unknown5
|
||||
}
|
||||
|
||||
export default EntityStatus;
|
9
server/enums/MaxUses.ts
Normal file
9
server/enums/MaxUses.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
enum MaxUses {
|
||||
GOLD = 32,
|
||||
WOOD = 59,
|
||||
STONE = 131,
|
||||
IRON = 250,
|
||||
DIAMOND = 1561
|
||||
}
|
||||
|
||||
export default MaxUses;
|
11
server/enums/MetadataFieldType.ts
Normal file
11
server/enums/MetadataFieldType.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
enum MetadataFieldType {
|
||||
Byte = 0,
|
||||
Short = 1,
|
||||
Int = 2,
|
||||
Float = 3,
|
||||
String = 4,
|
||||
Item = 5,
|
||||
Vector = 6
|
||||
}
|
||||
|
||||
export default MetadataFieldType;
|
54
server/enums/Packet.ts
Normal file
54
server/enums/Packet.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
// 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;
|
7
server/enums/SaveCompressionType.ts
Normal file
7
server/enums/SaveCompressionType.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
enum SaveCompressionType {
|
||||
NONE = 0,
|
||||
DEFLATE = 1,
|
||||
XZ = 2
|
||||
}
|
||||
|
||||
export default SaveCompressionType;
|
12
server/enums/SoundEffects.ts
Normal file
12
server/enums/SoundEffects.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
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;
|
5
server/generators/IGenerator.ts
Normal file
5
server/generators/IGenerator.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Chunk from "../Chunk";
|
||||
|
||||
export default interface IGenerator {
|
||||
generate: (chunk:Chunk) => void
|
||||
}
|
23
server/generators/terrain/Flat.ts
Normal file
23
server/generators/terrain/Flat.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
216
server/generators/terrain/Hilly.ts
Normal file
216
server/generators/terrain/Hilly.ts
Normal file
|
@ -0,0 +1,216 @@
|
|||
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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
60
server/generators/terrain/Nether.ts
Normal file
60
server/generators/terrain/Nether.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
83
server/generators/terrain/NewOverworld.ts
Normal file
83
server/generators/terrain/NewOverworld.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
|
||||
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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
server/inventories/IInventory.ts
Normal file
11
server/inventories/IInventory.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
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
|
||||
}
|
122
server/inventories/Inventory.ts
Normal file
122
server/inventories/Inventory.ts
Normal file
|
@ -0,0 +1,122 @@
|
|||
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();
|
||||
}
|
||||
}
|
134
server/inventories/ItemStack.ts
Normal file
134
server/inventories/ItemStack.ts
Normal file
|
@ -0,0 +1,134 @@
|
|||
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);
|
||||
}
|
||||
}
|
107
server/inventories/PlayerInventory.ts
Normal file
107
server/inventories/PlayerInventory.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
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)
|
||||
}
|
||||
}
|
6
server/items/IItemBehaviour.ts
Normal file
6
server/items/IItemBehaviour.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import EntityLiving from "../entities/EntityLiving";
|
||||
import ItemStack from "../inventories/ItemStack";
|
||||
|
||||
export default interface IItemBehaviour {
|
||||
|
||||
}
|
137
server/items/Item.ts
Normal file
137
server/items/Item.ts
Normal file
|
@ -0,0 +1,137 @@
|
|||
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");
|
||||
}
|
7
server/items/ItemBehaviour.ts
Normal file
7
server/items/ItemBehaviour.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
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
Loading…
Reference in a new issue