Communication between JS runtimes and core itself is done through GCRuntime which introduces following functions to VM:
gcruntime.onevent listener is not really a javascript Event/CustomEvent, but a hook that will register callback inside GhostCore and will be triggered as soon as matching event is fired
callbackcan return modified data (useful for preprocessors) or nothing
gcruntime.on = (
module: string, //relay event only for specified module
event: string, //relay event only for specified type
callback: (data: object) => (object | void)
): void => {/*@__PURE__*/}
//Example that recieves event:
gcruntime.on("pong", "ping", (e) => console.log(e))
//Example that modifies event:
gcruntime.on("pong", "ping", (e) => ({...e, value: 17}))
gcruntime.emitsends event to all runtimes and core itself
emitcannot return any values or have callbacks
gcruntime.emit = (
event: string,
data: object
): void => {/*@__PURE__*/}
// Example that sends event (module "pong"):
gcruntime.emit("ping", {ping: "pong", value: 10})Imagine a situation when there is one ping emitter and 10 receivers, 8 of which modifies data. Which value to return?
A much better way of implementing two-way communication is through emitters and listeners on both sides:
//pong.js
gcruntime.emit("ping", "Hi!") //pong module
gcruntime.on("*", "pong", (e)=>console.log("It responded!"))//responder.js
gcruntime.on("pong", "ping", (e)=>{
gcruntime.emit("pong", e+" - and I say 'Hello!'")
})Let's see what just happened:
pongmodule sendspingevent withHi!as it's bodyrespondermodule gets this event because- it listens only for
pongmodule events - it listens only for
pingevents
- it listens only for
- and
respondersendspongevent withHi! - and I say 'Hello!'as it's body pongmodule gets this event because- it uses
*wildcard and doesn't care who sent event - it listens only for
pongevent
- it uses
pongmodule logs this historical event to console
Yes. I mean these are your scripts, you should know what they are doing. Also, you can use gcruntime.on('*','*',()=>{})
for debugging purposes
Well, while runtimes are isolated and opaque for GhostCore, the core itself may emit some :before events, which data
is expected to be modified, like levelName.
Let's make a filter for bad words as an example
gcruntime.on('gc','onLevelUpload:before', (data)=>{
let name = data.name
name = name.replace(/(.+)(bad)(.+)/, "$1***$3")
return {...data, name: name}
})Note: only
gcmodule events support data modification. Also, you can't name your modulegc
- Use
event:beforefor preprocessors: when you need to modify data before it's processed and stored/returned by GhostCore - Use
event:afterfor notifications, webhooks, etc. (Things that don't affect default data flow)
Sometimes you need to store some data, but you have no access to database or similar storages. That's where Data and Config storages come in.
gcruntime.configcontains all fields from configuration file including server id (overwrites your id key, if any). This storage is immutable across VMs and couldn't be changed from inside the moduleTo keep things less messy you can encapsulate each module config into object. But it's just json, so do what you want
gcruntime.config = {
"id": "01Ab",
"webhooks_example": {
"primary": "URL",
"maxlen": 500,
"enabled": true
}
}
gcruntime.getData(key: string): anyandgcruntime.setData(key: string, value: any): voidallow data modification across VMs, so you can keep all your data hereUse wildcard
*to get whole data storage tree
// Runtime 1
let players = gcruntime.getData("players") // ["Robtop", "Camilla", "jake3206", "paprika_god"]
gcruntime.setData("players",[...players, "Mark2"])
// Runtime 2
console.log(gcruntime.getData("*"))
/* Outputs (assuming there was already data):
{
cat_name: "Dusty",
players: ["Robtop", "Camilla", "jake3206", "paprika_god", "Mark2"],
visitors: 7503
}
*/
console.log(gcruntime.getData("visitors")) // Outputs: 7503
console.log(gcruntime.getData("non_existent_key")) //Outputs: undefinedgcruntime.emit("onLoad", {}) // called during initialization phase
gcruntime.emit("onUnload", {}) // called during HTTP request endJS VMs are asynchronous so their initialization won't affect GhostCore performance, but be aware that requests
are usually executed in 500ms or less so if you are using something heavy like discord.js you might want to
write events to a list and iterate over them in client.onready
onUnload event doesn't mean that VM will be killed immediatel, but indicates that HTTP request is completed, and you won't
be able to modify any data. Expect 10s timeout for all your actions
gcruntime.emit("onPlayerRegistered:before", {
uname: "PlayerUsername",
email: "player@example.com",
ip: "1.2.3.4"
}) //return value: bool <- should we allow player to register or not
gcruntime.emit("onPlayerRegistered:after", {
uid: 1,
uname: "PlayerUsername",
email: "player@example.com",
ip: "1.2.3.4"
}) //return value: IGNORED
gcruntime.emit("onPlayerLogin:before", {
uid: 1,
uname: "PlayerUsername",
email: "player@example.com",
ip: "1.2.3.4"
}) //return value: bool <- should we allow player to login or not
gcruntime.emit("onPlayerLogin:after", {
uid: 1,
uname: "PlayerUsername",
email: "player@example.com",
ip: "1.2.3.4",
banned: false
}) //return value: IGNORED
//onPlayerSync and onPlayerBackup are not implemented as it's considered inappropriate to access or modify player savedata
gcruntime.emit("onPlayerScoreUpdate:before", {
uid: 1,
uname: "PlayerUsername",
stats: {
stars: 100,
diamonds: 1000,
coins: 10,
ucoins: 5,
demons: 3,
cpoints: 0,
orbs: 10000,
moons: 0
}
}) //return value: stats object
gcruntime.emit("onPlayerScoreUpdate:after", {
uid: 1,
uname: "PlayerUsername",
stats: {
stars: 100,
diamonds: 1000,
coins: 10,
ucoins: 5,
demons: 3,
cpoints: 0,
orbs: 10000,
moons: 0
}
}) //return value: IGNORED// There will be no emmitters for Leaderboars, but maybe we should add hooks or something to access data in real time? gcruntime.emit("onLevelUpload:before", {
name: "Level Name",
builder: "string",
})-
onGauntletNew- invoked when new gauntlet is created -
onMapPackNew- invoked when new map pack is created
- To Be Done
package modules
import "strconv"
// OnLevelUpload invoked when level was uploaded
func OnLevelUpload(id int, name string, builder string, desc string)
// OnLevelUpdate invoked when level was updated
func OnLevelUpdate(id int, name string, builder string, desc string)
// OnLevelDelete invoked when level was deleted
func OnLevelDelete(id int, name string, builder string)
// OnLevelRate invoked when level was rated/rerated
func OnLevelRate(id int, name string, builder string, stars int, likes int, downloads int, length int, demonDiff int, isEpic bool, isFeatured bool, ratedBy map[string]string)
// OnLevelReport invoked when level was reported
func OnLevelReport(id int, name string, builder string, player string)
// OnLevelScore invoked when player published their score in level scoreboard
func OnLevelScore(id int, name string, player string, percent int, coins int)
var ratedBy = map[string]string{
"uid": strconv.Itoa(1),
"uname": "Username",
}package modules
// Not implemented yet