Skip to content

Commit

Permalink
foundation!
Browse files Browse the repository at this point in the history
  • Loading branch information
vytdev committed Jan 25, 2024
1 parent a60187e commit 02e5b68
Show file tree
Hide file tree
Showing 34 changed files with 2,536 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ node_modules/

# builds
/scripts
msd.mcpack
catalyst_*.mcpack
7 changes: 5 additions & 2 deletions build
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

src=src
out=scripts
pack="catalyst.mcpack"
pack="catalyst_$(
cat "manifest.json" |
sed -n '0,/.*"version": "\(.*\)".*/s//\1/p'
).mcpack"

# some cleanup
mkdir -p "$out"
Expand All @@ -20,4 +23,4 @@ done

# package add-on
rm "$pack"
zip -r "$pack" "$out" 'manifest.json' 'pack_icon.png' 'LICENSE' 'README.md'
zip -r "$pack" "$out" 'manifest.json' 'pack_icon.png' 'LICENSE' 'README.md'
8 changes: 4 additions & 4 deletions manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
{
"module_name": "@minecraft/server",
"version": "1.8.0-beta"
},
{
"module_name": "@minecraft/server-ui",
"version": "1.1.0"
}
//{
// "module_name": "@minecraft/server-ui",
// "version": "1.2.0-beta"
//}
],

"metadata": {
Expand Down
25 changes: 25 additions & 0 deletions src/catalyst/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* contains all default settings of the catalyst core
*/
export default {
/**
* enable debugging
*/
debug: true,
/**
* command prefix to use, default is "\"
*/
commandPrefix: "\\",
/**
* threshold duration for server lag warning (in milliseconds)
*/
serverLagWarning: 400,
/**
* how many commands from the queue should be executed in a tick
*/
commandBuffer: 128,
/**
* max number of thread tasks to execute every tick
*/
threadBuffer: 512,
} as const;
200 changes: 200 additions & 0 deletions src/catalyst/core/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/**
* custom commands
*/

import { ChatSendBeforeEvent, world } from "@minecraft/server";
import { events } from "./index.js";
import { formats } from "./format.js";
import config from "../config.js";

/**
* the current command prefix
*/
export let commandPrefix: string = config.commandPrefix;

/**
* change the command prefix
*/
export function setCommandPrefix(newPrefix: string): void {
events.dispatchEvent("commandPrefixChanged", commandPrefix, newPrefix);
commandPrefix = newPrefix;
}

export type commandCallback = (argv: commandToken[], ev: ChatSendBeforeEvent | null) => void;

export interface commandToken {
text: string,
start: number,
end: number,
quoted: boolean,
}

export interface commandEntry {
name: string,
aliases?: string[],
callback: commandCallback,
}

/**
* utility function to split the command into tokens
* @param cmd the command string to tokenize
* @param [startIndex] optional start index, useful when skipping prefix
* @returns array of command tokens
*/
export function tokenizeCommand(cmd: string, startIndex?: number): commandToken[] {
const result: commandToken[] = [];

let text: string;
let escapeChar = false;

let i: number;
let start: number;
let isQuoted = false;

const addVec = () => {
if (!text || !text.length)
return;
result.push({
text,
start: start + (isQuoted ? -1 : 0),
end: i + (isQuoted ? 1 : 0),
quoted: isQuoted,
});
text = null;
start = null;
}

// extract parts by characters
for (i = startIndex || 0; i < cmd.length; i++) {
if (!text) {
start = i;
text = "";
}

const char = cmd[i];

// char is escaped
if (escapeChar) {
escapeChar = false;
text += char;
continue;
}

// backslash found
if (char == "\\") {
escapeChar = true;
continue;
}

// double quote found
if (char == "\"") {
addVec();
isQuoted = !isQuoted;
continue;
}

// whitespace
if (!isQuoted && /\s/.test(char)) {
addVec();
continue;
}

text += char;
}

if (text && text.length) addVec();

return result;
}

// command registry
const registry: commandEntry[] = [];

/**
* registers a new command
* @param name the name of command
* @param callback the function to execute when the command is called
* @param [aliases] optional command aliases
* @returns the command entry
*/
export function registerCommand(name: string, callback: commandCallback, aliases?: string[]): commandEntry {
const cmd = { name, callback, aliases: aliases ?? [] };
registry.push(cmd);
events.dispatchEvent("commandRegistered", cmd);
return cmd;
}

/**
* remove a command from the registry
* @param name the name of command to remove
*/
export function deregisterCommand(name: string): void {
const idx = registry.findIndex(v => v.name == name);
if (idx == -1) return;
registry.splice(idx, 1);
events.dispatchEvent("commandDeregistered", name);
}

/**
* get a command from the registry
* @param cmd the command name or alias
* @returns commandEntry object
*/
export function getCommand(cmd: string): commandEntry {
return registry.find(v => v.name == cmd || v.aliases?.includes(cmd));
}

/**
* calls a command
* @param cmd the command text
* @throws this can throw errors
*/
export function callCommand(cmd: string): void {
// parse command
const argv = tokenizeCommand(cmd);
if (!argv.length) throw "Unknown command.";
// find command entry
const entry = getCommand(argv[0].text);
if (!entry) throw "Unknown command.";
// trigger event
events.dispatchEvent("commandRun", argv, null);
// run command
entry.callback(argv, null);
}


// listen for chat events
world.beforeEvents.chatSend.subscribe(ev => {
// check for prefix
if (!ev.message.startsWith(commandPrefix)) return;
// cancel broadcast
ev.cancel = true;

try {
// parse the command
const argv = tokenizeCommand(ev.message, commandPrefix.length);
// no parsed arguments
if (!argv.length) throw "Unknown command.";

// find the command
const entry = getCommand(argv[0].text);
// the command doesn't exist
if (!entry) throw "Unknown command.";

// trigger event
events.dispatchEvent("commandRun", argv, ev);

// run the command
entry.callback(argv, ev);
} catch (e) {
let msg: string = formats.red;

// Error instance
if (e?.stack) msg += e.stack;
// string only
else msg += e;

ev.sender.sendMessage(msg);
}

});
123 changes: 123 additions & 0 deletions src/catalyst/core/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* event mechanism of the api
*/

/**
* event listener interface
*/
export interface eventListener {
/**
* name of event
*/
event: string,
/**
* the callback to execute
*/
callback: (...args: any) => void,
/**
* listen for event once
*/
once: boolean,
}

/**
* @class EventManager
* event manager class
*/
export class EventManager<T extends Record<string, any[]>> {
/**
* event listeners
* @private
*/
private readonly _listeners: eventListener[] = [];

/**
* registers an event listener
* @param event the name of event to listen for
* @param callback a function to call when the event is fired
* @param [once] listen for the event only once
* @param [prepend] make the listener in first priority to execute
* @returns the event listener object
*/
public addEventListener<N extends keyof T>(
event: N,
callback: (...args: T[N]) => void,
once?: boolean,
prepend?: boolean
): eventListener {
const listener = {
event: event as string,
callback,
once: !!once
};
this._listeners.push(listener);
return listener;
}

/**
* removes an event listener
* @param listener the listener to remove
* @returns true if succeded
*/
public removeEventListener(listener: eventListener): boolean {
const idx = this._listeners.indexOf(listener);
if (idx == -1) return false;
this._listeners.splice(idx, 1);
return true;
}

/**
* fires an event
* @param event the name of event
* @param args[] the arguments to fire
* @returns number of listeners that is fired
*/
public dispatchEvent<N extends keyof T>(event: N, ...args: T[N]): number {
// number of listeners ran
let n = 0;

// iterate through the _listeners array
this._listeners.forEach(listener => {
// skip listener
if (listener.event != event) return;
// increment the counter
n++;

// run the listener
try {
listener.callback?.(...args);
} catch { /* no-op */ };

// the listener only listens once
if (listener.once) this.removeEventListener(listener);
});

return n;
}

// some aliases for our methods:

public on<N extends keyof T>(event: N, callback: (...args: T[N]) => void): eventListener {
return this.addEventListener(event, callback, false, false);
}

public once<N extends keyof T>(event: N, callback: (...args: T[N]) => void): eventListener {
return this.addEventListener(event, callback, true, false);
}

public prepend<N extends keyof T>(event: N, callback: (...args: T[N]) => void): eventListener {
return this.addEventListener(event, callback, false, true);
}

public prependOnce<N extends keyof T>(event: N, callback: (...args: T[N]) => void): eventListener {
return this.addEventListener(event, callback, true, true);
}

public off(listener: eventListener): boolean {
return this.removeEventListener(listener);
}

public emit<N extends keyof T>(event: N, ...args: T[N]): number {
return this.dispatchEvent(event, ...args);
}
}
Loading

0 comments on commit 02e5b68

Please sign in to comment.