Compare commits

..

No commits in common. "main" and "TCP" have entirely different histories.
main ... TCP

13 changed files with 218 additions and 295 deletions

7
.gitignore vendored
View file

@ -1,7 +1,2 @@
.env .env
.vscode .vscode
plugins/*
!plugins/commands.ts
!plugins/world.ts
worlds/*
node_modules

BIN
bun.lockb

Binary file not shown.

View file

@ -108,6 +108,7 @@ export class PacketWriter {
} }
} }
import { gzip } from "https://cdn.skypack.dev/pako";
import { Position, World } from "./classes.ts"; import { Position, World } from "./classes.ts";
import { Player } from "./Player.ts"; import { Player } from "./Player.ts";
@ -136,7 +137,7 @@ export class PacketDefinitions {
player.position = world.getSpawn(); player.position = world.getSpawn();
const compressedMap = Bun.gzipSync(world.data)!; const compressedMap = gzip(world.data)!;
for (let i = 0; i < compressedMap.length; i += 1024) { for (let i = 0; i < compressedMap.length; i += 1024) {
const chunk = compressedMap.slice( const chunk = compressedMap.slice(
@ -300,7 +301,7 @@ export class PacketDefinitions {
.writeByte(0x07) .writeByte(0x07)
.writeString(name) .writeString(name)
.writeString(motd) .writeString(motd)
.writeByte(0x64) .writeByte(0x00)
.toPacket(), .toPacket(),
); );
} }

View file

@ -1,23 +1,21 @@
import { Position, Rotation, World } from "./classes.ts"; import { Position, Rotation, World } from "./classes.ts";
import { PacketDefinitions, PacketWriter } from "./Packets.ts"; import { PacketDefinitions, PacketWriter } from "./Packets.ts";
import { config, log } from "../deps.ts"; import { log } from "../deps.ts";
import { Server } from "./Server.ts"; import { Server } from "./Server.ts";
import {Socket} from 'bun';
export class Player { export class Player {
socket: Socket<{dataBuffer?: Buffer}>; socket: Deno.Conn;
private server: Server; private server: Server;
username: string; username: string;
ip: string; ip: string;
id: number; id: number;
world = config.main; world = "main";
position: Position; position: Position;
rotation: Rotation = { yaw: 0, pitch: 0 }; rotation: Rotation = { yaw: 0, pitch: 0 };
constructor( constructor(
socket: Socket<{dataBuffer?: Buffer}>, socket: Deno.Conn,
username: string, username: string,
position: Position, position: Position,
server: Server, server: Server,
@ -26,8 +24,8 @@ export class Player {
this.username = username; this.username = username;
this.position = position; this.position = position;
this.server = server; this.server = server;
this.ip = this.socket.remoteAddress; this.ip = (this.socket.remoteAddr as Deno.NetAddr).hostname;
let id = Math.floor(Math.random() * server.maxUsers); let id = Math.floor(Math.random() * server.maxUsers);
// reassigns ID until finds available one // reassigns ID until finds available one
@ -39,15 +37,10 @@ export class Player {
} }
async writeToSocket(ar: Uint8Array) { async writeToSocket(ar: Uint8Array) {
try { await this.socket.write(ar).catch((e) => {
this.socket.write(ar)
} catch(e) {
log.critical(e); log.critical(e);
await this.server.removeUser( this.server.removeUser(this.socket);
this.socket, });
"Write failed" + e.message.split("\n")[0],
);
}
} }
message(text: string, id = 0) { message(text: string, id = 0) {
@ -82,5 +75,7 @@ export class Player {
); );
this.message("You have been moved."); this.message("You have been moved.");
//await world.save(); TODO: this causes way too many issues
} }
} }

View file

@ -5,10 +5,8 @@ import {
Plugin, Plugin,
World, World,
} from "./classes.ts"; } from "./classes.ts";
import { Socket, TCPSocketListener } from "bun"
import { config, log } from "../deps.ts"; import { config, crypto, log, s3, toHexString } from "../deps.ts";
import * as fs from "node:fs/promises"
type PlayerFunction = (a: Player) => void; type PlayerFunction = (a: Player) => void;
@ -18,7 +16,7 @@ interface PluginUpdateTime {
} }
export class Server { export class Server {
server!: TCPSocketListener; server!: Deno.Listener;
players: Player[] = []; players: Player[] = [];
plugins: Map<string, PluginUpdateTime> = new Map(); plugins: Map<string, PluginUpdateTime> = new Map();
@ -30,73 +28,38 @@ export class Server {
[13, 65], [13, 65],
]); ]);
maxUsers = config.maxUsers; maxUsers = 69
worlds: World[] = [new World({ x: 64, y: 64, z: 64 }, config.main), new World({ x: 256, y: 64, z: 256 }, "large")]; worlds: World[] = [new World({ x: 64, y: 64, z: 64 }, "main")];
async start(port: number) { async start(port: number) {
this.server = Bun.listen<{dataBuffer?: Buffer}>({ this.server = Deno.listen({ port: port });
hostname: process.env.HOST || "127.0.0.1",
port: +process.env.PORT!,
socket: {
data: async (socket, data) => {
if(socket.data.dataBuffer) {
const newBuffer = Buffer.concat([socket.data.dataBuffer, data]);
socket.data.dataBuffer = newBuffer;
} else {
socket.data.dataBuffer = data;
}
log.debug("Socket", socket.remoteAddress, "has", socket.data.dataBuffer.length, "data for parsing.");
//if(config.debug) await new Promise(r => setTimeout(r, 300));
const parseBuffer = () => {
if(!socket.data.dataBuffer) return;
if(socket.data.dataBuffer.length == 0) return;
const packetId = socket.data.dataBuffer.readUint8(0);
const packetLength = this.lengthMap.get(packetId);
if(!packetLength) {
log.debug("Incorrect packet ID", packetId, "packet length could not be found.")
return;
};
if(socket.data.dataBuffer.byteLength < packetLength) {
log.debug("not enough bytes for packet", packetId)
return;
};
this.handlePacket(socket.data.dataBuffer.copyWithin(0, 1, packetLength), packetId, socket);
log.debug("Parsed packet", packetId, "with packet length", packetLength)
//console.log(socket.data.dataBuffer, socket.data.dataBuffer.copyWithin(0, 1, packetLength))
socket.data.dataBuffer = Uint8Array.prototype.slice.call(socket.data.dataBuffer, packetLength+1);
parseBuffer();
}
parseBuffer();
},
open: (socket) => {
socket.data = {}
},
close: async (socket) => {
await this.removeUser(socket, "Disconnected");
},
drain(socket) {},
error(socket, error) {},
},
});
try { try {
await fs.stat("worlds/"); await s3.headBucket({
for await (const dirEntry of await fs.readdir("worlds/", {withFileTypes: true})) { Bucket: "cla66ic",
const world = new World({ x: 0, y: 0, z: 0 }, dirEntry.name.replace(".buf", "")); });
log.info("s3 bucket exists!");
} catch {
log.warning("s3 bucket does not exist.. Creating!");
await s3.createBucket({
Bucket: "cla66ic",
});
}
(await s3.listObjects({
Bucket: "cla66ic",
})).Contents.forEach((e) => {
if (e.Key !== "main.buf") {
log.info(`Autoloaded a world from s3! ${e.Key}`);
const world = new World({ x: 0, y: 0, z: 0 }, e.Key!.split(".buf")[0]);
this.worlds.push(world); this.worlds.push(world);
} }
} catch { });
await fs.mkdir("worlds")
}
if (config.onlineMode) { if (config.onlineMode) {
setInterval(async () => { setInterval(async () => {
@ -104,11 +67,11 @@ export class Server {
"https://www.classicube.net/heartbeat.jsp" + "https://www.classicube.net/heartbeat.jsp" +
`?port=${config.port}` + `?port=${config.port}` +
`&max=${this.maxUsers}` + `&max=${this.maxUsers}` +
`&name=${config.name}` + "&name=Cla66ic" +
"&public=True" + "&public=True" +
`&software=${config.software}` + "&software=Cla66ic" +
`&version=7&salt=${config.hash}` + `&version=7&salt=${config.hash}` +
`&users=${[...new Set(this.players.map((obj) => obj.ip))].length}`, `&users=${this.players.length}`,
); );
}, 10000); }, 10000);
} }
@ -117,6 +80,9 @@ export class Server {
log.info(`Listening on port ${config.port}!`); log.info(`Listening on port ${config.port}!`);
for await (const socket of this.server) {
this.startSocket(socket);
}
} }
broadcast(text: string) { broadcast(text: string) {
@ -138,13 +104,13 @@ export class Server {
} }
async updatePlugins() { async updatePlugins() {
for await (const file of await fs.readdir("./plugins", {withFileTypes:true})) { for await (const file of Deno.readDir("./plugins")) {
if (file.isFile()) { if (file.isFile) {
const name = file.name.split(".ts")[0]; const name = file.name.split(".ts")[0];
if (!this.plugins.has(name)) { if (!this.plugins.has(name)) {
this.plugins.set(name, { this.plugins.set(name, {
lastUpdated: (await fs.stat(`./plugins/${file.name}`)).mtime!, lastUpdated: Deno.statSync(`./plugins/${file.name}`).mtime!,
plugin: new ((await import(`../plugins/${file.name}`)).default)( plugin: new ((await import(`../plugins/${file.name}`)).default)(
this, this,
), ),
@ -153,12 +119,12 @@ export class Server {
const plugin = this.plugins.get(name); const plugin = this.plugins.get(name);
if ( if (
(await fs.stat(`./plugins/${file.name}`)).mtime!.getTime() !== Deno.statSync(`./plugins/${file.name}`).mtime!.getTime() !==
plugin?.lastUpdated.getTime() plugin?.lastUpdated.getTime()
) { ) {
plugin?.plugin.emit("stop"); plugin?.plugin.emit("stop");
this.plugins.set(name, { this.plugins.set(name, {
lastUpdated: (await fs.stat(`./plugins/${file.name}`)).mtime!, lastUpdated: Deno.statSync(`./plugins/${file.name}`).mtime!,
plugin: plugin:
new ((await import(`../plugins/${file.name}#` + Math.random())) new ((await import(`../plugins/${file.name}#` + Math.random()))
.default)( .default)(
@ -170,7 +136,7 @@ export class Server {
} }
} }
} }
async removeUser(conn: Socket<{dataBuffer?: Buffer}>, text: string) { removeUser(conn: Deno.Conn) {
const player = this.players.find((e) => e.socket == conn); const player = this.players.find((e) => e.socket == conn);
if (!player) return; if (!player) return;
@ -178,14 +144,12 @@ export class Server {
this.players = this.players.filter((e) => e != player); this.players = this.players.filter((e) => e != player);
try { try {
conn.end(); conn.close();
} catch { } catch {
// whatever // whatever
} }
this.broadcast(`${player.username} has &cleft&f, "${text}"`); this.broadcast(`${player.username} has &cleft`);
await this.worlds.find((e) => e.name == player.world)!.save();
this.broadcastPacket( this.broadcastPacket(
(e) => PacketDefinitions.despawn(player.id, e), (e) => PacketDefinitions.despawn(player.id, e),
@ -196,7 +160,7 @@ export class Server {
async handlePacket( async handlePacket(
buffer: Uint8Array, buffer: Uint8Array,
packetType: number, packetType: number,
connection: Socket<{dataBuffer?: Buffer}>, connection: Deno.Conn,
) { ) {
const packet = new PacketReader(buffer); const packet = new PacketReader(buffer);
if (packetType == 0x00) { if (packetType == 0x00) {
@ -206,8 +170,9 @@ export class Server {
const verification = packet.readString(); const verification = packet.readString();
if (this.players.length >= this.maxUsers) { if(this.players.length >= this.maxUsers) {
connection.end()
connection.close();
return; return;
} }
@ -219,25 +184,31 @@ export class Server {
); );
if (!verification) { if (!verification) {
player.socket.end(); player.socket.close();
return true; return true;
} }
const hasher = new Bun.CryptoHasher("md5");
hasher.update(config.hash + player.username);
const str = toHexString(
new Uint8Array(
await crypto.subtle.digest(
"MD5",
new TextEncoder().encode(config.hash + player.username),
),
),
);
if ( if (
config.onlineMode && verification != config.hash && config.onlineMode && verification != config.hash &&
!this.players.find((e) => e.socket == connection) !this.players.find((e) => e.socket == connection)
) { ) {
if ( if (
hasher.digest("hex") !== verification str !== verification
) { ) {
await PacketDefinitions.disconnect( await PacketDefinitions.disconnect(
"Refresh your playerlist! Incorrect hash!", "Refresh your playerlist! Incorrect hash!",
player, player,
); );
player.socket.end(); player.socket.close();
return true; return true;
} }
@ -248,7 +219,7 @@ export class Server {
"Your name is already being used!", "Your name is already being used!",
player, player,
); );
player.socket.end(); player.socket.close();
return true; return true;
} }
@ -258,10 +229,9 @@ export class Server {
player.toWorld(this.worlds.find((e) => e.name == player.world)!); player.toWorld(this.worlds.find((e) => e.name == player.world)!);
this.broadcast(`${player.username} has &ajoined`); this.broadcast(`${player.username} has &ajoined`);
} else if (packetType == 0x08) { } else if (packetType == 0x08) {
const player = this.players.find((e) => e.socket == connection); const player = this.players.find((e) => e.socket == connection)!;
if (!player) return;
packet.readSByte(); packet.readByte();
player.position.x = packet.readShort(); player.position.x = packet.readShort();
player.position.y = packet.readShort(); player.position.y = packet.readShort();
player.position.z = packet.readShort(); player.position.z = packet.readShort();
@ -276,8 +246,7 @@ export class Server {
} else if (packetType == 0x0d) { } else if (packetType == 0x0d) {
packet.readByte(); packet.readByte();
const player = this.players.find((e) => e.socket == connection); const player = this.players.find((e) => e.socket == connection)!;
if (!player) return;
const message = packet.readString(); const message = packet.readString();
let playerColor = "[member] &b"; let playerColor = "[member] &b";
@ -301,21 +270,19 @@ export class Server {
} }
this.broadcast(`${playerColor}${player.username}&f: ${message}`); this.broadcast(`${playerColor}${player.username}&f: ${message}`);
} else if (packetType == 0x05) { } else if (packetType == 0x05) {
const player = this.players.find((e) => e.socket == connection); const player = this.players.find((e) => e.socket == connection)!;
if (!player) return;
let position = {x:0,y:0,z:0} const position = {
position.x = packet.readShort(); x: packet.readShort(),
position.y = packet.readShort(); y: packet.readShort(),
position.z = packet.readShort(); z: packet.readShort(),
};
const mode = packet.readByte(); const mode = packet.readByte();
const block = packet.readByte(); const block = packet.readByte();
const id = mode ? block : 0; const id = mode ? block : 0;
const world = this.worlds.find((e) => e.name == player.world); const world = this.worlds.find((e) => e.name == player.world)!;
if (!world) return;
let pluginAnswer: boolean[] = []; let pluginAnswer: boolean[] = [];
@ -338,4 +305,51 @@ export class Server {
); );
} }
} }
async startSocket(connection: Deno.Conn) {
while (true) {
const packetID = new Uint8Array(1);
let packetIDReadAttempt;
try {
packetIDReadAttempt = await connection.read(packetID);
} catch {
this.removeUser(connection); // TODO: add a reason to this
break;
}
if (packetIDReadAttempt) {
const packetLength = this.lengthMap.get(packetID[0]);
if (!packetLength) {
log.critical("Unknown Packet: " + packetID[0]);
this.removeUser(connection); // TODO: add a reason to this
break;
}
let rawPacket = new Uint8Array(packetLength);
let packetReadAttempt;
try {
packetReadAttempt = await connection.read(rawPacket);
} catch {
this.removeUser(connection); // TODO: add a reason to this
break;
}
let fullRead = packetReadAttempt!;
while (fullRead < packetLength) {
const halfPacket = new Uint8Array(packetLength - fullRead);
rawPacket = new Uint8Array([...rawPacket, ...halfPacket]);
fullRead += (await connection.read(halfPacket))!;
}
this.handlePacket(rawPacket, packetID[0], connection);
} else {
this.removeUser(connection);
break;
}
}
}
} }

View file

@ -1,15 +1,14 @@
import { decode, encode } from "cbor-x"; import { gzip, ungzip } from "https://cdn.skypack.dev/pako";
import { s3 } from "../deps.ts";
import { Position } from "./classes.ts"; import { Position } from "./classes.ts";
import { unlink, readFile, writeFile} from "node:fs/promises";
export class World { export class World {
size: Position; size: Position;
data: Uint8Array; data: Uint8Array;
private dataView: DataView; private dataView: DataView;
name: string; name: string;
// deno-lint-ignore no-explicit-any // deno-lint-ignore no-explicit-any
metadata: any = {}; optionalJson: any = {};
constructor(size: Position, name: string) { constructor(size: Position, name: string) {
this.size = size; this.size = size;
@ -34,7 +33,7 @@ export class World {
} }
findID(block: number): Position[] { findID(block: number): Position[] {
const position: Position[] = []; const position = [];
for (let z = 0; z < this.size.z; z++) { for (let z = 0; z < this.size.z; z++) {
for (let y = 0; y < this.size.y; y++) { for (let y = 0; y < this.size.y; y++) {
for (let x = 0; x < this.size.x; x++) { for (let x = 0; x < this.size.x; x++) {
@ -73,37 +72,39 @@ export class World {
async delete() { async delete() {
try { try {
await unlink(`worlds/${this.name}.buf`) await s3.deleteObject({
Bucket: "cla66ic",
Key: this.name + ".buf",
});
} catch { } catch {
// gang // doesn't exist, probably..
} }
} }
private async load() { private async load() {
try { try {
const ungziped = Bun.gunzipSync( const head = await s3.headObject({
await readFile(`worlds/${this.name}.buf`) Bucket: "cla66ic",
Key: this.name + ".buf",
});
const ungziped = ungzip(
(await s3.getObject({
Bucket: "cla66ic",
Key: this.name + ".buf",
})).Body,
); );
if (!(ungziped instanceof Uint8Array)) return; if (!(ungziped instanceof Uint8Array)) return;
const dv = new DataView(ungziped.buffer);
const cborSize = dv.getUint32(0);
this.metadata = decode(new Uint8Array(ungziped.buffer.slice(4, cborSize+4)));
this.size = { this.size = {
x: this.metadata.x!, x: +head.Metadata.x!,
y: this.metadata.y!, y: +head.Metadata.y!,
z: this.metadata.z!, z: +head.Metadata.z!,
}; };
this.data = ungziped.slice(cborSize+4); this.data = ungziped;
this.dataView = new DataView(this.data.buffer); this.dataView = new DataView(this.data.buffer);
this.optionalJson = JSON.parse(head.Metadata.json || "{}");
if(4 + this.size.x * this.size.y * this.size.z == this.data.length) { } catch {
console.log('[WARNING] Encoding was wrong somewhere!')
}
} catch(e) {
const layers = Math.floor(this.size.y / 2); const layers = Math.floor(this.size.y / 2);
for (let i = 0; i < layers; i += 1) { for (let i = 0; i < layers; i += 1) {
@ -117,18 +118,16 @@ export class World {
} }
async save() { async save() {
const metadata = { await s3.putObject({
x: this.size.x!, Bucket: "cla66ic",
y: this.size.y!, Key: this.name + ".buf",
z: this.size.z!, Body: gzip(this.data),
...this.metadata Metadata: {
} "x": this.size.x + "",
const cborData = encode(metadata); "y": this.size.y + "",
const buffer = new Uint8Array(4 + cborData.byteLength + this.data.byteLength); "z": this.size.z + "",
const dv = new DataView(buffer.buffer); "json": JSON.stringify(this.optionalJson),
dv.setUint32(0, cborData.byteLength); },
buffer.set(cborData, 4); });
buffer.set(this.data, 4 + cborData.byteLength);
await writeFile(`worlds/${this.name}.buf`, Bun.gzipSync(buffer)!);
} }
} }

View file

@ -1,4 +1,4 @@
import EventEmitter from "./../events"; import { EventEmitter } from "../deps.ts";
import { Player } from "./Player.ts"; import { Player } from "./Player.ts";
import { Server } from "./Server.ts"; import { Server } from "./Server.ts";
@ -29,7 +29,7 @@ export abstract class Plugin extends EventEmitter<{
mode: number, mode: number,
id: number, id: number,
position: Position, position: Position,
): Promise<boolean> | boolean; ): boolean;
stop(): void; stop(): void;
}> { }> {

46
deps.ts
View file

@ -1,29 +1,27 @@
import "https://deno.land/x/dotenv@v3.2.0/load.ts";
import { ApiFactory } from "https://deno.land/x/aws_api@v0.7.0/client/mod.ts";
import { S3 } from "https://aws-api.deno.dev/v0.3/services/s3.ts";
export * as log from "https://deno.land/std@0.136.0/log/mod.ts";
export { crypto } from "https://deno.land/std@0.136.0/crypto/mod.ts";
export { EventEmitter } from "./events.ts";
import "https://deno.land/x/dotenv@v3.2.0/load.ts";
export const toHexString = (bytes: Uint8Array) => export const toHexString = (bytes: Uint8Array) =>
bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), ""); bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
export const log = {
info: (...a) => { export const s3 = new ApiFactory({
console.log("[INFO]", ...a); credentials: {
awsAccessKeyId: Deno.env.get("S3_ACCESS_KEY_ID")!,
awsSecretKey: Deno.env.get("S3_SECRET_KEY")!,
}, },
warning: (...a) => { fixedEndpoint: "https://s3.us-west-004.backblazeb2.com",
console.warn("[WARNING]", ...a); region: "us-west-004",
}, }).makeNew(S3);
critical: (...a) => {
console.error("[ERROR]", ...a);
},
debug: (...a) => {
if(config.debug) {
console.log("[DEBUG]", ...a)
}
}
}
export const config = { export const config = {
ops: process.env.OPS ? JSON.parse(process.env.OPS!) : [], ops: Deno.env.get("OPS") ? JSON.parse(Deno.env.get("OPS")!) : [],
port: +process.env.PORT!, port: +Deno.env.get("PORT")!,
hash: process.env.HASH, hash: Deno.env.get("HASH"),
onlineMode: process.env.ONLINEMODE == "true", onlineMode: Deno.env.get("ONLINEMODE") == "true",
main: process.env.MAIN || "main",
maxUsers: +(process.env.USERS || 24) > 255 ? 255 : +(process.env.USERS || 24),
software: process.env.SOFTWARE || "Custom Cla66ic",
name: process.env.NAME || "Cla66ic Server",
debug: process.env.DEBUG == "true",
}; };

View file

@ -208,7 +208,7 @@ export class EventEmitter<E extends EventsType = {}> {
timeout?: number, timeout?: number,
): Promise<Parameters<Callback>> { ): Promise<Parameters<Callback>> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
let timeoutId: Timer | null; let timeoutId: number | null;
let listener = (...args: any[]) => { let listener = (...args: any[]) => {
if (timeoutId !== null) clearTimeout(timeoutId); if (timeoutId !== null) clearTimeout(timeoutId);

View file

@ -1,12 +0,0 @@
{
"name": "cla66ic",
"module": "index.ts",
"type": "module",
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"cbor-x": "^1.5.9",
"@types/bun": "latest"
}
}

View file

@ -1,4 +1,4 @@
import { PacketWriter, Player, Plugin } from "../classes/classes.ts"; import { Plugin } from "../classes/classes.ts";
import { Server } from "../classes/Server.ts"; import { Server } from "../classes/Server.ts";
import { config } from "../deps.ts"; import { config } from "../deps.ts";
@ -7,36 +7,14 @@ export default class CommandPlugin extends Plugin {
"help", "help",
"reloadplugins", "reloadplugins",
"clients", "clients",
"tp",
"eval"
]; ];
async tp(from: Player, to: Player) {
if(to.world != from.world) {
from.toWorld(this.server.worlds.find((e) =>
e.name == to.world
)!);
}
await from.writeToSocket(
new PacketWriter()
.writeByte(0x08)
.writeSByte(255)
.writeShort(to.position.x)
.writeShort(to.position.y)
.writeShort(to.position.z)
.writeByte(to.rotation.yaw)
.writeByte(to.rotation.pitch)
.toPacket(),
);
}
constructor(server: Server) { constructor(server: Server) {
super(); super();
this.server = server; this.server = server;
this.on("command", async (command, player, args) => { this.on("command", async (command, player) => {
if (command == "help") { if (command == "help") {
let allComamnds = ""; let allComamnds = "";
for (const [_k, v] of server.plugins) { for (const [_k, v] of server.plugins) {
@ -60,42 +38,7 @@ export default class CommandPlugin extends Plugin {
); );
} }
}); });
} else if(command == "eval") { }
if(config.ops.includes(player.username)) { });
server.broadcast(eval(args.join(" ")));
}
} else if (command == "tp") {
if(args.length == 1) {
const teleportTo = this.server.players.find((e) => args[0] === e.username)
if(teleportTo) {
await this.tp(player, teleportTo);
} else {
player.message("Player is missing")
}
} else if(args.length == 3) {
const x = +args[0]
const y = +args[1]
const z = +args[2]
if(isNaN(x) || isNaN(y) || isNaN(z)) {
player.message("invalid coords")
return;
}
await player.writeToSocket(
new PacketWriter()
.writeByte(0x08)
.writeSByte(255)
.writeShort(x * 32)
.writeShort(y * 32)
.writeShort(z * 32)
.writeByte(player.rotation.yaw)
.writeByte(player.rotation.pitch)
.toPacket(),
);
}
}
});
} }
} }

View file

@ -7,7 +7,6 @@ export default class CommandPlugin extends Plugin {
"g", "g",
"worlds", "worlds",
"world", "world",
"main",
]; ];
constructor(server: Server) { constructor(server: Server) {
@ -16,8 +15,8 @@ export default class CommandPlugin extends Plugin {
this.server = server; this.server = server;
this.on("setblock", (player, _mode, _id) => { this.on("setblock", (player, _mode, _id) => {
const world = server.worlds.find((e) => e.name == player.world)!; const world = server.worlds.find((e) => e.name == player.world)!;
if (!world.metadata?.builders?.includes("*")) { if (!world.optionalJson?.builders?.includes("*")) {
if (!world.metadata?.builders?.includes(player.username)) { if (!world.optionalJson?.builders?.includes(player.username)) {
player.message("You are %cnot allowed &fto build in this world!"); player.message("You are %cnot allowed &fto build in this world!");
return true; return true;
} }
@ -26,19 +25,10 @@ export default class CommandPlugin extends Plugin {
return false; return false;
}); });
this.on("command", async (command, player, args) => { this.on("command", async (command, player, args) => {
if (command == "main") { if (command == "g") {
await server.worlds.find((e) => e.name == player.world)!.save();
player.toWorld(
server.worlds.find((e) => e.name.toLowerCase() == config.main)!,
);
} else if (command == "g") {
await server.worlds.find((e) => e.name == player.world)!.save();
const requestedWorld = server.worlds.find((e) => const requestedWorld = server.worlds.find((e) =>
e.name.toLowerCase() == args.join(" ").toLowerCase() e.name.toLowerCase() == args.join(" ").toLowerCase()
); );
if (requestedWorld) { if (requestedWorld) {
player.toWorld(requestedWorld); player.toWorld(requestedWorld);
} else { } else {
@ -59,8 +49,8 @@ export default class CommandPlugin extends Plugin {
{ x: 64, y: 64, z: 64 }, { x: 64, y: 64, z: 64 },
player.username, player.username,
); );
world.metadata.builders = []; world.optionalJson.builders = [];
world.metadata.builders.push(player.username); world.optionalJson.builders.push(player.username);
server.worlds.push(world); server.worlds.push(world);
player.message(`&aWorld created!&f Use /g ${player.username}!`); player.message(`&aWorld created!&f Use /g ${player.username}!`);
@ -105,11 +95,11 @@ export default class CommandPlugin extends Plugin {
return; return;
} }
if (!world.metadata?.builders) world.metadata.builders = []; if (!world.optionalJson?.builders) world.optionalJson.builders = [];
if (subcategory == "add") { if (subcategory == "add") {
const username = args[2]; const username = args[2];
world.metadata.builders.push(username); world.optionalJson.builders.push(username);
player.message( player.message(
`&a${username}&f sucesfully added as a builder to world &a${world.name}!`, `&a${username}&f sucesfully added as a builder to world &a${world.name}!`,
); );
@ -117,13 +107,13 @@ export default class CommandPlugin extends Plugin {
} else if (subcategory == "remove") { } else if (subcategory == "remove") {
const username = args[2]; const username = args[2];
const before = world.metadata.builders.length; const before = world.optionalJson.builders.length;
world.metadata.builders = world.metadata.builders.filter(( world.optionalJson.builders = world.optionalJson.builders.filter((
e: string, e: string,
) => e !== username); ) => e !== username);
const after = world.metadata.builders.length; const after = world.optionalJson.builders.length;
player.message( player.message(
`Removed &a${ `Removed &a${
@ -134,7 +124,7 @@ export default class CommandPlugin extends Plugin {
} else if (subcategory == "list") { } else if (subcategory == "list") {
player.message( player.message(
`&a${world.name}&f's builders: &a${ `&a${world.name}&f's builders: &a${
world.metadata.builders.join(", ") world.optionalJson.builders.join(", ")
}`, }`,
); );
} else { } else {

View file

@ -8,27 +8,28 @@
2. entierly cloud based (meaning you can host it anywhere) 2. entierly cloud based (meaning you can host it anywhere)
3. extremely extensive plugin system 3. extremely extensive plugin system
4. solid implementation of sockets in deno and how to patch them together 4. solid implementation of sockets in deno and how to patch them together
5. Bun!! It's not node, and a classic server 5. DENO!! It's not node, and a classic server.
6. very fast
### setup tutorial ### setup tutorial (be warned it's not the easiest)
1. configure .env file to look something like 1. make a backblaze b2 account, make a bucket, and get your keys from the bucket
2. configure .env file to look something like
``` ```
PORT=6969 PORT=6969
HASH=RandomHashIlIke HASH=RandomHashIlIke
OPS=["Me"] OPS=["Me"]
ONLINEMODE=true ONLINEMODE=true
MAIN=main
HOST=0.0.0.0 S3_ACCESS_KEY_ID="MyAccessKey"
S3_SECRET_KEY="SecretKey"
``` ```
NOTE: if you are running inside of a cloud provider, just set these as your NOTE: if you are running inside of a cloud provider, just set these as your
environment variables environment variables
2. install bun 3. install deno
3. run `bun index.ts` 4. run `deno run --allow-env --allow-net --allow-read index.ts`
### insipration taken from: ### insipration taken from:
@ -38,13 +39,12 @@ environment variables
### issues: ### issues:
1. ~~Properly queue up map saves instead of just blantantly saving whenever
possible~~ it now saves to disk, IO is very fast and shouldn't cause issues anymore 1. Properly queue up map saves instead of just blantantly saving whenever possible
2. ~~massive performance issues, running more than 100 something accounts makes 2. massive performance issues, running more than 100 something accounts makes the server instead insane amounts of cpu (most likely multithreading needed)
the server instead insane amounts of cpu (most likely multithreading needed)~~ the server is now async so it's way quicker (untested)
3. no cpe support! i want to get all of the above issues fixed before 3. no cpe support! i want to get all of the above issues fixed before
implementing CPE support implementing CPE support
4. no IP cooldown connections (no block cooldown either), no anticheat 4. no IP cooldown connections (no block cooldown either), no anticheat, no unique IP heartbeats
5. proper rank support (implemented as plugin) 5. proper rank support (implemented as plugin)
6. no discord bridge (implemented as plugin) 6. no discord bridge (implemented as plugin)
7. no cla66ic/plugins repository 7. no cla66ic/plugins repository