format everything + fix plugin events

This commit is contained in:
yourfriend 2022-06-03 15:36:34 +03:00
parent cf8772f0ed
commit d40e90240c
No known key found for this signature in database
GPG key ID: C28FFD8607DAC4DE
9 changed files with 280 additions and 37 deletions

View file

@ -150,7 +150,6 @@ export class PacketDefinitions {
await PacketDefinitions.levelFinish(world.size, player);
await PacketDefinitions.spawn(player, -1, player);
}
static async levelFinish(size: Position, player: Player) {
await player.writeToSocket(

View file

@ -28,7 +28,9 @@ export class Player {
// reassigns ID until finds available one
// if we reach 255 players this will loop forever
while(server.players.find(e => e.id == id)) id = Math.floor(Math.random() * 255);
while (server.players.find((e) => e.id == id)) {
id = Math.floor(Math.random() * 255);
}
this.id = id;
}
@ -63,8 +65,7 @@ export class Player {
PacketDefinitions.sendPackets(this, world);
this.server.broadcastPacket(
(e) =>
PacketDefinitions.spawn(this, this.id, e),
(e) => PacketDefinitions.spawn(this, this.id, e),
this,
);
this.server.broadcastPacket(

View file

@ -139,7 +139,10 @@ export class Server {
this.broadcast(`${player.username} has &cleft`);
this.worlds.find((e) => e.name == player.world)!.save();
this.broadcastPacket((e) => PacketDefinitions.despawn(player.id, e), player);
this.broadcastPacket(
(e) => PacketDefinitions.despawn(player.id, e),
player,
);
}
async handlePacket(packet: PacketReader, connection: Deno.Conn) {
@ -258,18 +261,25 @@ export class Server {
const world = this.worlds.find((e) => e.name == player.world)!;
const before = world.getBlock(position);
let pluginAnswer: boolean[] = [];
this.worlds.find((e) => e.name == player.world)!.setBlock(position, id);
for await (const [_k, v] of this.plugins) {
pluginAnswer = pluginAnswer.concat(
await v.plugin.emit("setblock", player, mode, id, position),
);
}
if (pluginAnswer.some((e) => e == true)) {
PacketDefinitions.setBlock(position, world.getBlock(position), player);
return;
}
world.setBlock(position, id);
this.broadcastPacket(
(e) => PacketDefinitions.setBlock(position, id, e),
player,
);
this.plugins.forEach((value) => { // TODO: Rework this to work with proper block disabling (not resetting bullshit)
value.plugin.emit("setblock", player, mode, id, position, before);
});
}
if (packet.buffer.length - 1 >= packet.pos) { // TODO: This logic is wrong! Sometimes, TCP packets are still dropped. Need to rewrite this properly.
@ -298,7 +308,6 @@ export class Server {
} else {
const packet = new PacketReader(buffer.subarray(0, count));
this.handlePacket(packet, connection);
}
}
}

View file

@ -29,8 +29,7 @@ export abstract class Plugin extends EventEmitter<{
mode: number,
id: number,
position: Position,
blockBefore: number,
): void;
): boolean;
stop(): void;
}> {

View file

@ -5,7 +5,7 @@ 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 "https://deno.land/x/eventemitter@1.2.1/mod.ts";
export { EventEmitter } from "./events.ts";
import "https://deno.land/x/dotenv@v3.2.0/load.ts";
export const toHexString = (bytes: Uint8Array) =>
bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
@ -23,5 +23,5 @@ export const config = {
ops: Deno.env.get("OPS") ? JSON.parse(Deno.env.get("OPS")!) : [],
port: +Deno.env.get("PORT")!,
hash: Deno.env.get("HASH"),
onlineMode: Deno.env.get("ONLINEMODE") == "true"
onlineMode: Deno.env.get("ONLINEMODE") == "true",
};

231
events.ts Normal file
View file

@ -0,0 +1,231 @@
// deno-lint-ignore-file
/** The callback type. */
type Callback = (...args: any[]) => any | Promise<any>;
/** A listener type. */
type Listener = Callback & { __once__?: true };
/** The name of an event. */
type EventName = string | number;
type EventsType =
& { [key: string]: Callback }
& { [key: number]: Callback };
/**
* The event emitter.
*/
export class EventEmitter<E extends EventsType = {}> {
/**
* This is where the events and listeners are stored.
*/
private _events_: Map<keyof E, Set<Listener>> = new Map();
/**
* Listen for a typed event.
* @param event The typed event name to listen for.
* @param listener The typed listener function.
*/
public on<K extends keyof E>(event: K, listener: E[K]): this;
/**
* Listen for an event.
* @param event The event name to listen for.
* @param listener The listener function.
*/
public on(event: EventName, listener: Callback): this {
if (!this._events_.has(event)) this._events_.set(event, new Set());
this._events_.get(event)!.add(listener);
return this;
}
/**
* Listen for a typed event once.
* @param event The typed event name to listen for.
* @param listener The typed listener function.
*/
public once<K extends keyof E>(event: K, listener: E[K]): this;
/**
* Listen for an event once.
* @param event The event name to listen for.
* @param listener The listener function.
*/
public once(event: EventName, listener: Callback): this {
const l: Listener = listener;
l.__once__ = true;
return this.on(event, l as any);
}
/**
* Remove a specific listener in the event emitter on a specific
* typed event.
* @param event The typed event name.
* @param listener The typed event listener function.
*/
public off<K extends keyof E>(event: K, listener: E[K]): this;
/**
* Remove all listeners on a specific typed event.
* @param event The typed event name.
*/
public off<K extends keyof E>(event: K): this;
/**
* Remove all events from the event listener.
*/
public off(): this;
/**
* Remove a specific listener on a specific event if both `event`
* and `listener` is defined, or remove all listeners on a
* specific event if only `event` is defined, or lastly remove
* all listeners on every event if `event` is not defined.
* @param event The event name.
* @param listener The event listener function.
*/
public off(event?: EventName, listener?: Callback): this {
if (!event && listener) {
throw new Error("Why is there a listener defined here?");
} else if (!event && !listener) {
this._events_.clear();
} else if (event && !listener) {
this._events_.delete(event);
} else if (event && listener && this._events_.has(event)) {
const _ = this._events_.get(event)!;
_.delete(listener);
if (_.size === 0) this._events_.delete(event);
} else {
throw new Error("Unknown action!");
}
return this;
}
/**
* Emit a typed event without waiting for each listener to
* return.
* @param event The typed event name to emit.
* @param args The arguments to pass to the typed listeners.
*/
public emitSync<K extends keyof E>(event: K, ...args: Parameters<E[K]>): this;
/**
* Emit an event without waiting for each listener to return.
* @param event The event name to emit.
* @param args The arguments to pass to the listeners.
*/
public emitSync(event: EventName, ...args: Parameters<Callback>): this {
if (!this._events_.has(event)) return this;
const _ = this._events_.get(event)!;
for (let [, listener] of _.entries()) {
const r = listener(...args);
if (r instanceof Promise) r.catch(console.error);
if (listener.__once__) {
delete listener.__once__;
_.delete(listener);
}
}
if (_.size === 0) this._events_.delete(event);
return this;
}
/**
* Emit a typed event and wait for each typed listener to return.
* @param event The typed event name to emit.
* @param args The arguments to pass to the typed listeners.
*/
public async emit<K extends keyof E>(
event: K,
...args: Parameters<E[K]>
): Promise<any[]>;
/**
* Emit an event and wait for each listener to return.
* @param event The event name to emit.
* @param args The arguments to pass to the listeners.
*/
public async emit(
event: EventName,
...args: Parameters<Callback>
): Promise<any[]> {
let returns: any[] = [];
if (!this._events_.has(event)) return returns;
const _ = this._events_.get(event)!;
for (let [, listener] of _.entries()) {
try {
returns.push(await listener(...args));
if (listener.__once__) {
delete listener.__once__;
_.delete(listener);
}
} catch (error) {
console.error(error);
}
}
if (_.size === 0) this._events_.delete(event);
return returns;
}
/**
* The same as emitSync, but wait for each typed listener to
* return before calling the next typed listener.
* @param event The typed event name.
* @param args The arguments to pass to the typed listeners.
*/
public queue<K extends keyof E>(event: K, ...args: Parameters<E[K]>): this;
/**
* The same as emitSync, but wait for each listener to return
* before calling the next listener.
* @param event The event name.
* @param args The arguments to pass to the listeners.
*/
public queue(event: EventName, ...args: Parameters<Callback>): this {
(async () => await this.emit(event, ...args as any))().catch(console.error);
return this;
}
/**
* Wait for a typed event to be emitted and return the arguments.
* @param event The typed event name to wait for.
* @param timeout An optional amount of milliseconds to wait
* before throwing.
*/
public pull<K extends keyof E>(
event: K,
timeout?: number,
): Promise<Parameters<E[K]>>;
/**
* Wait for an event to be emitted and return the arguments.
* @param event The event name to wait for.
* @param timeout An optional amount of milliseconds to wait
* before throwing.
*/
public pull(
event: EventName,
timeout?: number,
): Promise<Parameters<Callback>> {
return new Promise(async (resolve, reject) => {
let timeoutId: number | null;
let listener = (...args: any[]) => {
if (timeoutId !== null) clearTimeout(timeoutId);
resolve(args);
};
timeoutId = typeof timeout !== "number"
? null
: setTimeout(() => (this.off(event, listener as any),
reject(
new Error("Timed out!"),
))
);
this.once(event, listener as any);
});
}
}
export default EventEmitter;

View file

@ -1,4 +1,4 @@
import { PacketDefinitions, Plugin, World } from "../classes/classes.ts";
import { Plugin, World } from "../classes/classes.ts";
import { Server } from "../classes/Server.ts";
import { config } from "../deps.ts";
@ -13,20 +13,16 @@ export default class CommandPlugin extends Plugin {
super();
this.server = server;
this.on("setblock", (player, _mode, _id, position, blockBefore) => {
this.on("setblock", (player, _mode, _id) => {
const world = server.worlds.find((e) => e.name == player.world)!;
if (!world.optionalJson?.builders?.includes("*")) {
if (!world.optionalJson?.builders?.includes(player.username)) {
player.message("You are %cnot allowed &fto build in this world!");
world.setBlock(position, blockBefore);
server.players.forEach(async (e) => {
if (e.world == player.world) {
await PacketDefinitions.setBlock(position, blockBefore, e);
}
});
return true;
}
}
return false;
});
this.on("command", async (command, player, args) => {
if (command == "g") {

View file

@ -1,8 +1,9 @@
# cla66ic
## sucessor of cla55ic
## sucessor of cla55ic
### features:
1. written in typescript (types and shit)
2. entierly cloud based (meaning you can host it anywhere)
3. extremely extensive plugin system
@ -10,8 +11,10 @@
5. DENO!! It's not node, and a classic server.
### setup tutorial (be warned it's not the easiest)
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
HASH=RandomHashIlIke
@ -21,19 +24,24 @@ ONLINEMODE=true
S3_ACCESS_KEY_ID="MyAccessKey"
S3_SECRET_KEY="SecretKey"
```
NOTE: if you are running inside of a cloud provider, just set these as
your environment variables
NOTE: if you are running inside of a cloud provider, just set these as your
environment variables
3. install deno
4. run `deno run --allow-env --allow-net --allow-read index.ts`
### insipration taken from:
1. mcgalaxy (obviuouuusly!!)
2. https://github.com/Patbox/Cobblestone-Classic (some protocol information and worldhandling)
2. https://github.com/Patbox/Cobblestone-Classic (some protocol information and
worldhandling)
3. cla55ic (world data too)
### issues:
1. plugin system event handling is lackluster in some palces
2. tcp packet splitting fails sometimes
3. no cpe support! i want to get all of the above issues fixed before implementing CPE support
4. proper rank support (implemented as plugin)
5. no discord bridge (implemented as plugin)
1. tcp packet splitting fails sometimes
2. no cpe support! i want to get all of the above issues fixed before
implementing CPE support
3. proper rank support (implemented as plugin)
4. no discord bridge (implemented as plugin)