Appearance
Example: dungeon
From the runnable examples. Lint, build and run it from servers/inventery/:
sh
node plot/sdk/lint.mjs docs/developers/examples/dungeon
node plot/sdk/build.mjs docs/developers/examples/dungeon
node docs/developers/examples/run.mjs docs/developers/examples/dungeonplot.json
json
{
"kind": "runtime",
"namespace": "crypt",
"id": "ana.crypt",
"version": "1.0.0",
"name": "The Crypt",
"apiVersion": 1,
"code": "dist/logic.js",
"stashItems": true,
"lobbies": { "size": 4, "area": { "min": [0, 0, 0], "max": [8, 6, 8] } },
"rating": "everyone",
"tags": ["story", "adventure", "co-op"]
}logic.ts
ts
import { content, lobbies, players, scene, storage, time, ui, world } from "@inventery/host";
import type { EntityId, PlayerId, UseEvent } from "@inventery/host";
import { entities } from "@inventery/minecraft";
import { defineLobbyGame } from "@inventery/lobby";
const DOOR: [number, number, number] = [8, 1, 4];
// No base mob AI runs in plots: a plot moves and fights with its entities itself. This boss just stands guard.
const SKELETON = entities.add("skeleton", { model: "inventery:skeleton", health: 10 });
interface State { keeper: EntityId; boss: EntityId; doorOpen: boolean; bossDown: boolean }
const game = defineLobbyGame<State>({
// A new party's copy of the crypt: its own NPC, door and boss.
setup: () => ({ keeper: 0, boss: 0, doorOpen: false, bossDown: false }),
onLobbyLoad(s) {
if (!s.keeper) {
s.keeper = scene.create({
Transform: { pos: [4, 1, 6] },
Model: { asset: "inventery:villager" },
Nametag: { text: "The Keeper", color: "gold" },
});
}
},
onPlayerEnter(s, p: PlayerId) {
// Their progress is this plot's own data, kept across visits and parties.
const chapter = storage.getPlayerProfile<number | null>(p, "chapter") ?? 1;
ui.showTitle(p, "The Crypt", { subtitle: `Chapter ${chapter}`, color: "gold" });
},
onUse(s, ev: UseEvent) {
if (ev.entity === s.boss && !s.bossDown) {
content.damageEntity(ev.entity, 2, { by: ev.by }); // a tap is a hit: hurting plot entities is the plot's
return true;
}
if (ev.entity === s.keeper) {
ui.showDialogue(ev.by, s.keeper, "The door opens for those who listen.", { name: "The Keeper", color: "gold" });
if (!s.doorOpen) {
s.doorOpen = true;
lobbies.lock(); // the party is set: nobody new joins mid-story
world.setBlock(DOOR, world.blockId("air")); // this party's copy only
time.startTimer(2, "boss");
}
}
return true;
},
onTimer(s, name) {
if (name === "boss") s.boss = entities.spawn(SKELETON, [12, 1, 4]);
},
onEntityDeath(s, ev) {
if (ev.type !== SKELETON || s.bossDown) return;
s.bossDown = true;
for (const p of players.list()) {
storage.setPlayerProfile(p, "chapter", 2);
ui.showToast(p, { text: "Chapter 1 complete", icon: "star" });
}
},
});
export const { onLobbyLoad, onPlayerEnter, onUse, onTimer, onEntityDeath } = game;