Write your first resource
Write a small resource in plain JavaScript, load it without restarting, and make its two halves talk.
Build a resource called hello in plain JavaScript, with no build step: a welcome message, a /roll
command, a key that shows your health, and a question the server answers. Each step works on its own.
You need a running server from Install and run a server. Leave the gamemode where it is; resources live side by side.
1. Make the folder
Section titled “1. Make the folder”A resource is a folder under the server’s resources/ with a package.json that says which scripts
to run.
server/ resources/ kcdc-gamemode/ hello/ package.json server/main.js{ "name": "hello", "version": "1.0.0", "mafiahub": { "serverScripts": ["server/main.js"] }}name identifies the resource in the console, logs and events. Keep it the same as the folder name:
lowercase, with dashes if needed.
2. Write the server half
Section titled “2. Write the server half”console.log("hello is running");
Events.on("playerSpawned", (player) => { Chat.sendToPlayer(player, `Hello, ${player.nickname}. Try /roll.`);});
Events.on("playerCommand", (player, command, args) => { if (command !== "roll") return;
const sides = Number(args[0] ?? 6); if (!Number.isInteger(sides) || sides < 2 || sides > 1000) { Chat.sendToPlayer(player, "Usage: /roll [sides], from 2 to 1000."); return; }
const result = 1 + Math.floor(Math.random() * sides); Chat.sendToAll(`${player.nickname} rolls a d${sides}: ${result}`);});There is nothing to import: Events, Chat, Player and the rest of the
Server API are globals.
playerSpawnedfires once a joining player stands in the world, the first moment a chat line reaches them.playerConnectfires earlier, on the loading screen.playerCommandfires for every chat line starting with/.commandhas no slash;argsis the rest of the line split on spaces.
3. Load it
Section titled “3. Load it”No restart needed. Type into the server console:
refreshstart hellorefresh finds new resource folders; start runs one. You should see:
[hello] hello is runningJoin (or type /roll 20 if you are in already). After every edit, reload with:
ensure hello4. Add a client half
Section titled “4. Add a client half”A client script runs in each player’s game. It cannot change the world, but it can read what the player sees, bind keys and draw UI.
-
Add it to the manifest.
fileslists what the server sends to players:resources/hello/package.json {"name": "hello","version": "1.0.0","mafiahub": {"serverScripts": ["server/main.js"],"clientScripts": ["client/main.js"],"files": ["client/**"]}} -
Write it:
resources/hello/client/main.js Key.bind("f8", "down", () => {const me = LocalPlayer;if (!me) return;Hud.showInfoText(`${Math.round(me.health)} / ${Math.round(me.maxHealth)} health`, 3000);}); -
Run
ensure hello. Connected players get the new files at once. Press F8 in game.
LocalPlayer is null for a moment after connecting, before the body exists, so read it on each
press rather than once at the top of the file.
5. Make the halves talk
Section titled “5. Make the halves talk”The client only knows what its player can see; for anything else it asks the server. Here F10 asks who is online, and the server answers that one player.
Key.bind("f8", "down", () => { const me = LocalPlayer; if (!me) return; Hud.showInfoText(`${Math.round(me.health)} / ${Math.round(me.maxHealth)} health`, 3000);});
// Ask the server; it answers with an event of our own.Key.bind("f10", "down", () => { Events.emitServer("hello:who");});
Events.on("hello:online", (names) => { Hud.showInfoText(`Online: ${names.join(", ")}`, 5000);});// A client asked. The first argument is always the player who sent it.Events.onClient("hello:who", (sender) => { const names = Player.all().map((player) => player.nickname); sender.emit("hello:online", JSON.stringify(names));});
console.log("hello is running");// ...the rest as beforeThree rules hold in every resource:
- Client events have their own handlers.
Events.onClienthears only what clients send withEvents.emitServer. A client can never trigger a serverEvents.onhandler, so it cannot fakeplayerSpawned. player.emittakes a JSON string. The client parses it, sonamesarrives as an array. A string that is not JSON gets the event dropped.- Prefix event names with the resource name (
hello:who). All resources share one event bus.
What you have
Section titled “What you have”resources/hello/ package.json server/main.js client/main.jsA command, a key bind and a round trip, reloaded without restarting the server. Every resource has this shape, however big.
Related
Section titled “Related”- Use TypeScript: the next step; turns
hellointo a typed project so your editor catchesplayer.nickame. - Send data between server and client: payloads, validation, broadcasting.
- Events and handlers: every event both halves can hear.
- Resource manifest and lifecycle: every
package.jsonfield.