Hello mod
Example apps / hello-mod / v0.1.0
Node modIn the download
The smallest useful Node mod: a window built with the ui builder that follows V, plays a sound and counts save loads.
// Hello mod: a Tier 2 Node mod with a window built by the ui builder.
// It runs in the REDnode host, outside the game. Edit this file while the game
// runs with hot reload on, and the mod restarts with the change.
import { game, ui } from 'rednode';
const VERSION = 'first version';
const where = ui.text('Waiting for the game...');
const state = ui.text('', { tone: 'dim' });
const loads = ui.text('Saves loaded since the mod started: 0', { tone: 'dim' });
let follow = true;
let presses = 0;
ui.window({
title: 'Hello mod',
anchor: 'right',
x: -40,
y: 0,
width: 360,
height: 340,
children: [
ui.heading('Where is V'),
where,
state,
ui.row(
ui.button('Play sound', async () => {
presses += 1;
await game.audio.play('ui_menu_onpress');
console.log(`Sound button pressed ${presses} time(s)`);
}),
ui.toggle('Follow', (on) => {
follow = on;
console.log(`Follow ${on ? 'on' : 'off'}`);
}, { checked: true }),
),
loads,
ui.text(`Mod code: ${VERSION}, loaded at ${new Date().toLocaleTimeString()}`, { tone: 'accent' }),
],
});
// The same call the Coords HUD app makes, so both show the same position.
setInterval(async () => {
if (!follow) return;
try {
const p = await game.player.position();
where.set({ text: `X ${p.x.toFixed(1)} Y ${p.y.toFixed(1)} Z ${p.z.toFixed(1)}`, tone: 'normal' });
} catch (err) {
where.set({ text: err.message, tone: 'dim' });
}
}, 500);
game.onState((s) => {
const shown = [s.menu && 'menu', s.photoMode && 'photo mode', s.scene && 'scene', s.mainMenu && 'main menu'].filter(Boolean);
state.set({ text: s.connected ? `Game: ${shown.join(', ') || 'gameplay'} · overlay ${s.overlay}` : 'Game: not linked' });
});
let loaded = 0;
game.on('PlayerPuppet.OnGameAttached', (e) => {
loaded += 1;
loads.set({ text: `Saves loaded since the mod started: ${loaded}` });
console.log(`PlayerPuppet.OnGameAttached on frame ${e.frame}`);
});
console.log(`Hello mod started (${VERSION})`);
REDnode runs JavaScript. The rednode package also has TypeScript types, so an editor checks these calls; compile TypeScript to .mjs first.
{
"id": "hello-mod",
"name": "Hello mod",
"version": "0.1.0",
"author": "Edgemodders",
"description": "A Node mod with a window built by the rednode ui builder: V's position, a sound button and a counter. No HTML.",
"main": "mod.mjs",
"open": true
}
The manifest names the package and says how REDnode runs it. Its fields are in Overlay apps.
How it works
A Node mod is a JavaScript module named by main in its manifest. REDnode runs it in Node beside the game, so it can use npm packages, files and timers, and a mistake in it cannot crash the game.
The window is a tree of nodes. ui.text(...) makes a node and returns it; keep the ones that change, then call set() on them. REDnode draws the tree with the UI kit and sends clicks back to your handlers.
Every half second the mod asks for V's position and shows it. game.onState reports menus, photo mode and scenes; game.on("PlayerPuppet.OnGameAttached") fires on each save load.
What it uses
ui.window ui.text ui.button ui.toggle node.set game.player.position game.onState game.on game.audio.play
Try this
- Change
VERSIONand save: with hot reload on, the mod restarts and the window shows the new text. - Add a
ui.sliderthat sets how often the position updates.
Use it
Copy the files into red4ext/plugins/REDnode/apps/hello-mod/ in the game folder, or start your own package from them: change id and name in the manifest first. The whole folder is on GitHub.