The same features in Lua, C#, and JS — use it to switch tracks or split work across languages.
| Task |
Lua (full) |
C# (full) |
JS (UI SDK) |
| Subscribe |
greg.on("greg.PLAYER.CoinChanged", fn) → token |
On(id, fn) (base helper) → IDisposable; or [GregHook(id)] |
greg.on("greg.PLAYER.CoinChanged", function (p) {...}) |
| Once |
greg.once(id, fn) |
Dispose inside the handler after first fire |
n/a (count + ignore after first) |
| Unsubscribe |
greg.off(token) (auto-clean at reload) |
Dispose() / DisposeSubscriptions() in OnUnload |
n/a (lives for the session) |
| Emit |
greg.fire("my_mod.evt", {...}) |
GregEventDispatcher.Emit(id, data) |
greg.fire("my_mod.evt", {...}) |
| Discover |
greg.hooks.groups() / .audio.list() / on_<snake> sugar |
HookName constants, EventIds |
Same string IDs for on/fire |
| Task |
Lua |
C# |
JS |
| Info/warn/error |
greg.ui.log_info / log_warning / log_error |
Logger.Info / Warning / Error (protected IGregLogger) |
greg.log / warn / error |
| Toast |
greg.ui.notify(msg, secs) |
GregNotificationManager.Show(msg, duration) |
greg.toast(msg, secs) / greg.toastRich(top, title, sub, secs) / greg.notify(title, msg, secs) |
| Task |
Lua |
C# |
JS |
| Once / repeat |
greg.wait(s, fn) / greg.every(s, fn) |
Throttled OnUpdate + cached state (no per-frame scans) |
Throttled onUpdate(dt) + cached state |
| Coroutine |
greg.start_coroutine(fn) + coroutine.yield(greg.WAIT) |
MainThread.Enqueue for marshaling |
n/a |
| Task |
Lua |
C# |
JS |
| Settings |
greg.config.* (config.json) |
GregModSettingsService / GregConfigService |
greg.registerToggle/registerSlider/registerKey |
| State |
greg.save.* (save.json) + save_now() |
GregModSave, GregSaveEngine, RegisterSidecar |
n/a (stateless scripts; persist via fired events to Lua/C#) |
| Files/JSON |
greg.io.* sandbox + greg.json.* |
Newtonsoft (configs) / System.Text.Json (runtime) |
n/a |
| Task |
Lua |
C# |
JS |
| Panel |
tablet_open / widget_open / panel_add_* handles |
GregPanelBuilder + click-router + font + input-lock |
greg.createPanel(title) (chainable builder, same Toolkit) |
| Hub/HUD |
automatic via tablet (Hub lists Lua mods) |
GregMenuBinding + GregHudRegistry |
greg.bindMenu(id, toggle, isOpen) + greg.reportMenu(id, open) |
| Settings tab |
greg.ui.register_mod_config_tab |
GregSettingsHub.RegisterTab |
via registerToggle/Slider/Key (core settings UI) |
| Task |
Lua |
C# |
JS |
| Read economy/hardware |
greg.player / server / switch / tech / rack / cable / patch / customer / economy / shop / net / requests / subnet / world |
greg facade modules + GregServers / GregShop / GregTechnicians / … bridges + GregEntityInventory |
n/a (hook payloads only) |
| Drive repairs/shop |
repair_all(), dispatch_server(), shop.buy(idx) |
GregServers.FindAll / Repair, GregShop.CartAddOne / BuyItem, GregTechnicians |
n/a |
| World edits, spawning, cable ops, patches |
Not available (by design) |
GregMod + SafePatch + grid/wall systems |
Not available |
| Task |
Lua |
C# |
| Mod ID |
mod.json (Id, Entrypoint, …) / folder fallback |
[GregMod(id, name, version)] + GregModRegistry.Register |
| Deps |
greg.mods.declare / ensure / check |
[GregDependsOn] + GregModDeps |
- Lua → C#: move timers to throttled updates, tablets to
GregPanelBuilder, config/save to services + sidecars; keep hook IDs identical.
- C# → Lua: replace patches with subscriptions where events exist; move world edits out (Lua cannot do them — keep a C# companion).
- Either → JS: UI work (panels, toasts, menus, settings) ports 1:1; world edits, spawning, saves and patches stay Lua/C# (fire hooks across).