Server actually ticks

This commit is contained in:
2026-09-25 13:25:25 -03:00
parent 2a2ccae9ce
commit dfabe40e7a
18 changed files with 763 additions and 79 deletions
+9 -5
View File
@@ -1,5 +1,5 @@
import { Alea, create_noise_2d, create_noise_3d, NoiseFunction2D, NoiseFunction3D } from "@paulaboks/rng";
import { AIR, CHUNK_AREA, CHUNK_HEIGHT, CHUNK_SIZE } from "$/common/constants.ts";
import { AIR, CHUNK_AREA, CHUNK_HEIGHT, CHUNK_SIZE, ID_MASK } from "$/common/constants.ts";
import type { FeatureChunk } from "$/common/mod_api/worldgen.ts";
import type { OreJson } from "$/common/mod_data.ts";
@@ -323,6 +323,8 @@ export function generate_raw_chunk(
seed: string,
block_ids: Record<string, number>,
worldgen?: WorldgenSetup,
// the value to store for each numeric id, with its default states. just the id when missing
default_values?: number[],
): RawChunk {
const blocks = new Uint32Array(CHUNK_SIZE * CHUNK_SIZE * CHUNK_HEIGHT);
const spills: number[] = [];
@@ -333,6 +335,7 @@ export function generate_raw_chunk(
if (y < 0 || y >= CHUNK_HEIGHT) {
return;
}
nid = default_values?.[nid] ?? nid;
const block_chunk_x = Math.floor(x / CHUNK_SIZE);
const block_chunk_z = Math.floor(z / CHUNK_SIZE);
if (block_chunk_x !== chunk_x || block_chunk_z !== chunk_z) {
@@ -364,7 +367,7 @@ export function generate_raw_chunk(
);
if (worldgen) {
generate_ores(blocks, chunk_x, chunk_z, seed, block_ids, worldgen.ores);
generate_ores(blocks, chunk_x, chunk_z, seed, block_ids, worldgen.ores, default_values);
generate_features(blocks, heights, biomes, set, chunk_x, chunk_z, seed, block_ids, worldgen.features);
}
@@ -379,6 +382,7 @@ function generate_ores(
seed: string,
block_ids: Record<string, number>,
ores: OreJson[],
default_values: number[] | undefined,
) {
const usable = ores
.map((ore) => ({ ...ore, nid: block_ids[ore.id], replaces_nid: block_ids[ore.replaces] }))
@@ -392,7 +396,7 @@ function generate_ores(
for (let lz = 0; lz < CHUNK_SIZE; lz++) {
for (let lx = 0; lx < CHUNK_SIZE; lx++) {
const index = y * CHUNK_AREA + lz * CHUNK_SIZE + lx;
const current = blocks[index];
const current = blocks[index] & ID_MASK;
if (current === AIR) {
continue;
}
@@ -404,7 +408,7 @@ function generate_ores(
const wx = chunk_x * CHUNK_SIZE + lx;
const wz = chunk_z * CHUNK_SIZE + lz;
if (noises[i](wx * ore.scale, y * ore.scale, wz * ore.scale) > ore.threshold) {
blocks[index] = ore.nid;
blocks[index] = default_values?.[ore.nid] ?? ore.nid;
break;
}
}
@@ -452,7 +456,7 @@ function generate_features(
if (y < 0 || y >= CHUNK_HEIGHT) {
return undefined;
}
const nid = blocks[y * CHUNK_AREA + local(x, z, "get_block")];
const nid = blocks[y * CHUNK_AREA + local(x, z, "get_block")] & ID_MASK;
return nid === AIR ? "bworld:air" : ids_by_nid[nid];
},
set_block(x, y, z, id) {
+7 -3
View File
@@ -19,8 +19,8 @@ export interface PlayerInfo {
pitch: number;
}
// x, y, z, block id
export type BlockChange = [number, number, number, string];
// x, y, z, block id, and its state bits when they aren't 0
export type BlockChange = [number, number, number, string, number?];
// the containers a client can see and click. "screen" is whatever the open server screen shows
export type ContainerKey = "inventory" | "crafting" | "screen";
@@ -53,6 +53,10 @@ export type ClientMessage =
| { type: "ready" }
| { type: "move"; x: number; y: number; z: number; yaw: number; pitch: number }
| { type: "break_block"; x: number; y: number; z: number }
// the player started hitting a block, for on_click
| { type: "hit_block"; x: number; y: number; z: number }
// right click without looking at a block, for items' on_use
| { type: "use_item" }
// right click on a block: interact with it, or place the held block against `face`
| { type: "use_block"; x: number; y: number; z: number; face: Faces }
| { type: "select_slot"; slot: number }
@@ -88,7 +92,7 @@ export type ServerMessage =
| { type: "player_leave"; id: string }
| { type: "player_move"; id: string; x: number; y: number; z: number; yaw: number; pitch: number }
// also sent to the player who caused it, which corrects anything their client predicted wrong
| { type: "set_block"; x: number; y: number; z: number; id: string }
| { type: "set_block"; x: number; y: number; z: number; id: string; state?: number }
| { type: "chat"; from?: string; text: string }
| { type: "teleport"; x: number; y: number; z: number }
| { type: "container"; container: ContainerKey; items: (ItemData | null)[] }
+15 -1
View File
@@ -79,7 +79,21 @@ export function set_state_value(value: number, block_info: BlockRegistry, name:
state_bits = (state_bits & ~s.mask) | (new_value << s.shift);
return (state_bits << 16) | id;
// >>> 0 keeps it unsigned when the top state bit is set
return ((state_bits << 16) | id) >>> 0;
}
// a block's numeric id with its states at their defaults, what placing it should store
export function default_block_value(nid: number, block_info: BlockRegistry | undefined): number {
let value = nid;
for (const state of block_info?.states ?? []) {
value = set_state_value(value, block_info!, state.name, state.default) ?? value;
}
return value;
}
export function block_value(nid: number, state: number): number {
return ((state << STATE_SHIFT) | nid) >>> 0;
}
function compile_block_states(block_info: BlockRegistry) {