Actually implement mod code into server
This commit is contained in:
@@ -394,6 +394,7 @@ interface ServerContext {
|
||||
world: ServerWorld;
|
||||
players: PlayerList;
|
||||
containers: ContainerApi;
|
||||
items: ItemApi;
|
||||
recipes: RecipeApi;
|
||||
ui: ServerUi; // see GUIs
|
||||
net: ServerNet; // see Mod channels
|
||||
@@ -477,6 +478,7 @@ interface ServerWorld {
|
||||
get_state(x: number, y: number, z: number, name: string): number | undefined;
|
||||
set_state(x: number, y: number, z: number, name: string, value: number): boolean;
|
||||
get_block_data<T>(x: number, y: number, z: number): T | undefined;
|
||||
drop_item(x: number, y: number, z: number, item: ItemStack): void; // pops out of the block, like drops
|
||||
is_loaded(x: number, z: number): boolean;
|
||||
readonly seed: string;
|
||||
}
|
||||
@@ -612,8 +614,8 @@ code sees it.
|
||||
### 2. Container screens (server only)
|
||||
|
||||
For inventories and machines. The server owns the container's contents and syncs them to every player viewing it. Clicks
|
||||
go to the server, which applies them. The chest and furnace would be rebuilt this way instead of their hand-written
|
||||
`GuiChest` / `GuiFurnace` classes.
|
||||
go to the server, which applies them. The base game's chest and furnace are built this way, see
|
||||
`mods/bworld/scripts/server.ts`.
|
||||
|
||||
```ts
|
||||
ctx.components.register_block("copper_tools:smelter", {
|
||||
@@ -623,36 +625,50 @@ ctx.components.register_block("copper_tools:smelter", {
|
||||
on_interact(block, _params, player) {
|
||||
const container = ctx.containers.get(block.data.container)!;
|
||||
const screen = ctx.ui.open_container(player, {
|
||||
title: "Smelter",
|
||||
container,
|
||||
layout: [
|
||||
{ slot: 0, x: 3, y: 0, filter: "smeltable" },
|
||||
{ slot: 1, x: 3, y: 2, filter: (item) => ctx.recipes.is_fuel(item.id) },
|
||||
{ slot: 2, x: 5, y: 1, output_only: true },
|
||||
],
|
||||
container,
|
||||
player_inventory: true,
|
||||
bars: [{ id: "progress", x: 4, y: 1, texture: "bworld:arrow" }],
|
||||
bars: [{
|
||||
x: 4,
|
||||
y: 1,
|
||||
value: "progress",
|
||||
max: "progress_max",
|
||||
direction: "right",
|
||||
empty_texture: "bworld:arrow_empty",
|
||||
full_texture: "bworld:arrow_full",
|
||||
}],
|
||||
});
|
||||
screen.set_property("progress", block.data.progress);
|
||||
screen.set_property("progress_max", 200);
|
||||
return true;
|
||||
},
|
||||
on_break(block) {
|
||||
ctx.containers.delete(block.data.container);
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
- `x` / `y` are in slot units on a grid. The client scales and centers the screen, and draws the player's inventory
|
||||
below it when `player_inventory` is true.
|
||||
- The screen is drawn below the player's inventory and hotbar. `x` / `y` are in slot units in the screen's own area and
|
||||
can be fractional. `rows` sets its height, by default it fits the slots and bars.
|
||||
- `filter` is a function `(item) => boolean` or one of the built-in filters `"smeltable"` (has a furnace recipe) and
|
||||
`"fuel"`. It and `output_only` run **on the server**, so players can't put the wrong items in by editing their client.
|
||||
- `bars` are progress bars filled from 0 to 1 by `screen.set_property(id, value)`. Properties can also be shown as text
|
||||
with `labels: [{ x, y, property }]`.
|
||||
- The screen handle has `set_property`, `close()` and `on_close(fn)`. Changes to the container (from scripts, hoppers,
|
||||
`output_only` slots can only be taken from, all at once, like the furnace result.
|
||||
- `bars` are progress bars filled with `value / max`, both names of properties set with `screen.set_property(id, n)`.
|
||||
`direction` is `"up"` (like the furnace's fire) or `"right"` (like its arrow). Properties are synced when they change,
|
||||
so a component can update them every tick.
|
||||
- The screen handle has `player`, `open`, `set_property`, `close()` and `on_close(fn)`. It closes when the player closes
|
||||
it, leaves, or opens another screen, and when its container is deleted. Changes to the container (from scripts,
|
||||
other players) show up for everyone viewing it.
|
||||
- Containers are saved with the world until they're deleted, so a block that has one deletes it in `on_break`.
|
||||
|
||||
```ts
|
||||
interface ContainerApi {
|
||||
create(size: number): Container; // saved with the world
|
||||
create(size: number): Container; // 1 to 256 slots, saved with the world
|
||||
get(id: string): Container | undefined;
|
||||
delete(id: string): void;
|
||||
delete(id: string): void; // closes screens showing it
|
||||
}
|
||||
|
||||
interface Container {
|
||||
@@ -663,8 +679,15 @@ interface Container {
|
||||
add(item: ItemStack): ItemStack | undefined; // returns what didn't fit
|
||||
on_change(fn: (slot: number) => void): () => void;
|
||||
}
|
||||
|
||||
interface ItemApi {
|
||||
exists(id: string): boolean;
|
||||
max_stack(id: string): number;
|
||||
}
|
||||
```
|
||||
|
||||
Items handed to scripts are live: setting `count` changes the stack, and a stack counted down to 0 is gone.
|
||||
|
||||
### 3. Custom screens (client code)
|
||||
|
||||
For anything forms and containers can't do: maps, skill trees, minigames, custom layouts. The client script registers a
|
||||
@@ -1036,15 +1059,21 @@ export function setup(ctx: ServerContext) {
|
||||
on_interact(block, _params, player) {
|
||||
const data = block.data as SmelterData;
|
||||
ctx.ui.open_container(player, {
|
||||
title: "Smelter",
|
||||
container: ctx.containers.get(data.container)!,
|
||||
layout: [
|
||||
{ slot: 0, x: 3, y: 0, filter: "smeltable" },
|
||||
{ slot: 1, x: 3, y: 2, filter: "fuel" },
|
||||
{ slot: 2, x: 5, y: 1, output_only: true },
|
||||
],
|
||||
player_inventory: true,
|
||||
bars: [{ id: "progress", x: 4, y: 1, texture: "bworld:arrow" }],
|
||||
bars: [{
|
||||
x: 4,
|
||||
y: 1,
|
||||
value: "progress",
|
||||
max: "progress_max",
|
||||
direction: "right",
|
||||
empty_texture: "bworld:arrow_empty",
|
||||
full_texture: "bworld:arrow_full",
|
||||
}],
|
||||
}).set_property("progress", data.progress);
|
||||
return true;
|
||||
},
|
||||
@@ -1126,10 +1155,10 @@ anything the base game does.
|
||||
|
||||
Not moved:
|
||||
|
||||
- **Crops** (`common/blocks/crops.ts`) are unfinished and mostly commented out. They stay where they are and get rebuilt
|
||||
as a component once server scripts exist, as a good first test of tile data plus ticking.
|
||||
- **The watering can's lore** (`get_lore`) isn't shown anywhere yet, because the game has no item tooltips. It moves
|
||||
once tooltips exist.
|
||||
- **Carrots, potatoes, tomatoes and pumpkins** (`common/blocks/crops.ts`) are unfinished and mostly commented out.
|
||||
Wheat uses the `bworld:crop` component instead, and they can too once they have blocks.
|
||||
- **The watering can's lore** moved into its component's `get_lore`, but it isn't shown anywhere yet, because the game
|
||||
has no item tooltips.
|
||||
|
||||
### What stays in the engine
|
||||
|
||||
@@ -1150,7 +1179,8 @@ Worlds saved before the move must load afterwards with nothing changed:
|
||||
3. **Tile data keeps its shape.** Chests store their items in `containers.main`, and furnaces keep `containers.main`
|
||||
plus `progress`, `progress_max`, `fuel` and `fuel_max` in `data`. The components read exactly those, so tiles saved
|
||||
by the current code load into them.
|
||||
4. **The save format and protocol don't change.** Clients receive `mods/bworld` like any other mod.
|
||||
4. **The protocol doesn't change, and old saves load.** Clients receive `mods/bworld` like any other mod. The save
|
||||
format may change when it has to, as long as older saves are upgraded when loading (phase 2 did, to version 3).
|
||||
|
||||
### Phases
|
||||
|
||||
@@ -1180,14 +1210,17 @@ loading mods (the base game and the template), their scripts and worldgen, saves
|
||||
- Delete `common/blocks/` and `common/items/`.
|
||||
- Check: the registry built from JSON equals `registry.json`, and all tests pass.
|
||||
|
||||
**Phase 2: behavior** (after step 6, and container screens from step 7).
|
||||
**Phase 2: behavior** (after step 6, and container screens from step 7). _Done._
|
||||
|
||||
- `mods/bworld/scripts/server.ts` registers `bworld:hoeable`, `bworld:storage`, `bworld:furnace` and
|
||||
`bworld:watering_can`. The chest and furnace open their screens with `ctx.ui.open_container`, whose layouts are the
|
||||
`ScreenLayout`s the server sends now.
|
||||
- The block JSON lists the components. Delete `server/game/blocks.ts`.
|
||||
- Check: the server logic tests (hoeing, chest, furnace smelting, breaking a chest gives its contents back) and
|
||||
`world_v2.json` still pass.
|
||||
- `mods/bworld/scripts/server.ts` registers `bworld:hoeable`, `bworld:storage`, `bworld:furnace`, `bworld:crop` (bone
|
||||
meal on wheat) and `bworld:watering_can`. The chest and furnace open their screens with `ctx.ui.open_container`, with
|
||||
the same layouts as before.
|
||||
- The block and item JSON lists the components. `server/game/blocks.ts` is deleted.
|
||||
- Saves went to version 3: tiles only keep `mod_data`, and containers are saved by id. Version 2 chests and furnaces are
|
||||
upgraded when loaded: their `data` fields stay and their `containers.main` becomes a container whose id is in
|
||||
`container`, which is the shape the components use.
|
||||
- Check: `tests/base_game_test.ts` (hoeing, chest, furnace smelting, breaking a chest gives its contents back, bone meal,
|
||||
the watering can) and `tests/fixtures/world_v2.json`, saved by the engine code before the move, pass.
|
||||
|
||||
**Phase 3: world generation** (after step 9).
|
||||
|
||||
@@ -1239,10 +1272,11 @@ working:
|
||||
joining. Add the confirm screen for cross-origin servers and CORS headers on the server. _Done._
|
||||
6. **Server scripts.** Components, events, commands, system, storage and `ctx.recipes`. Move `FURNACE_RECIPES` and
|
||||
`FUEL_VALUES` into the recipe registry. _Done_ (`server/game/mod_runtime.ts`, with the loop in
|
||||
`server/game/game_loop.ts`). `ctx.containers`, `ctx.ui` and `ctx.net` throw until steps 7 and 8, and so do the
|
||||
client's `ctx.ui`, `ctx.hud`, `ctx.input` and `ctx.net`.
|
||||
`server/game/game_loop.ts`). `ctx.net` throws until step 8, and so do the client's `ctx.ui`, `ctx.hud`,
|
||||
`ctx.input` and `ctx.net`.
|
||||
7. **GUIs.** Forms, then container screens (rebuild chest and furnace with them), then custom screens, the `Graphics`
|
||||
API (built on the existing renderer and debug UI widgets) and the HUD.
|
||||
API (built on the existing renderer and debug UI widgets) and the HUD. _Container screens done_ (`ctx.containers`,
|
||||
`ctx.ui.open_container`), forms and custom screens throw until they're built.
|
||||
8. **Mod channels** and keybinds.
|
||||
9. **Worldgen mods.** Worldgen URLs and mod ores go into the chunk worker `init` message. Workers must finish importing
|
||||
before generating anything. _Done for features and ores._ `register_terrain` throws until phase 3 moves the base
|
||||
|
||||
Reference in New Issue
Block a user