-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
34 changed files
with
2,536 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,4 +17,4 @@ node_modules/ | |
|
||
# builds | ||
/scripts | ||
msd.mcpack | ||
catalyst_*.mcpack |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.