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
+24 -11
View File
@@ -1,5 +1,5 @@
import { Component } from "$/common/ecs/mod.ts";
import { chunk_key } from "$/common/utils.ts";
import { block_value, chunk_key, default_block_value } from "$/common/utils.ts";
import { AIR_ID, BlockChange } from "$/common/protocol.ts";
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
import { AIR, CHUNK_AREA, CHUNK_HEIGHT, CHUNK_SIZE, Faces, ID_MASK, VOID } from "../../common/constants.ts";
@@ -101,7 +101,8 @@ export class Dimension extends Component {
return this.chunks.get(chunk_key(x, z));
}
add_block(block: Block) {
// state is the block's state bits, its defaults when not given
add_block(block: Block, state?: number) {
const block_chunk_x = Math.floor(block.x / CHUNK_SIZE);
const block_chunk_z = Math.floor(block.z / CHUNK_SIZE);
let chunk = this.get_chunk(block_chunk_x, block_chunk_z);
@@ -109,7 +110,7 @@ export class Dimension extends Component {
chunk = this.add_chunk(block_chunk_x, block_chunk_z);
}
const nid = EverythingRegistry.get_id("blocks", block.id)!;
const [nid, info] = EverythingRegistry.get_full<BlockRegistry>("blocks", block.id)!;
const lx = block.x - block_chunk_x * CHUNK_SIZE;
const lz = block.z - block_chunk_z * CHUNK_SIZE;
@@ -117,11 +118,23 @@ export class Dimension extends Component {
const index = ly * CHUNK_SIZE * CHUNK_SIZE + lz * CHUNK_SIZE + lx;
chunk.blocks[index] = nid;
chunk.blocks[index] = state === undefined ? default_block_value(nid, info) : block_value(nid, state);
chunk.dirty = true;
this.#mark_border_neighbors_dirty(block_chunk_x, block_chunk_z, lx, lz);
}
// the id with its state bits, VOID outside loaded chunks
get_block_value(x: number, y: number, z: number) {
const chunk_x = Math.floor(x / CHUNK_SIZE);
const chunk_z = Math.floor(z / CHUNK_SIZE);
const chunk = this.get_chunk(chunk_x, chunk_z);
if (!chunk) {
return VOID;
}
return chunk.blocks[y * CHUNK_AREA + (z - chunk_z * CHUNK_SIZE) * CHUNK_SIZE + (x - chunk_x * CHUNK_SIZE)] ??
AIR;
}
get_block(x: number, y: number, z: number) {
const chunk_x = Math.floor(x / CHUNK_SIZE);
const chunk_z = Math.floor(z / CHUNK_SIZE);
@@ -196,18 +209,18 @@ export class Dimension extends Component {
return [x, y, z];
}
record_change(x: number, y: number, z: number, id: string) {
record_change(x: number, y: number, z: number, id: string, state = 0) {
const chunk_key = `${Math.floor(x / CHUNK_SIZE)},${Math.floor(z / CHUNK_SIZE)}`;
let chunk_changes = this.changes.get(chunk_key);
if (!chunk_changes) {
chunk_changes = new Map();
this.changes.set(chunk_key, chunk_changes);
}
chunk_changes.set(`${x},${y},${z}`, [x, y, z, id]);
chunk_changes.set(`${x},${y},${z}`, [x, y, z, id, state]);
}
// set a block the server told us about
apply_change(x: number, y: number, z: number, id: string) {
apply_change(x: number, y: number, z: number, id: string, state = 0) {
const chunk = this.get_chunk(Math.floor(x / CHUNK_SIZE), Math.floor(z / CHUNK_SIZE));
if (!chunk || !chunk.generated) {
return;
@@ -222,15 +235,15 @@ export class Dimension extends Component {
}
const nid = EverythingRegistry.get_id("blocks", id);
if (nid === undefined || nid === current) {
if (nid === undefined || block_value(nid, state) === this.get_block_value(x, y, z)) {
return;
}
this.add_block({ x, y, z, id });
this.add_block({ x, y, z, id }, state);
}
apply_chunk_changes(cx: number, cz: number) {
for (const [x, y, z, id] of this.changes.get(`${cx},${cz}`)?.values() ?? []) {
this.apply_change(x, y, z, id);
for (const [x, y, z, id, state] of this.changes.get(`${cx},${cz}`)?.values() ?? []) {
this.apply_change(x, y, z, id, state);
}
}
+2 -2
View File
@@ -14,8 +14,8 @@ export function start_game(world: ClientWorld) {
const dimension = new Entity("dimension");
world.dimension?.dispose();
world.dimension = new Dimension(world, world.connection.seed);
for (const [x, y, z, id] of world.connection.initial_changes) {
world.dimension.record_change(x, y, z, id);
for (const [x, y, z, id, state] of world.connection.initial_changes) {
world.dimension.record_change(x, y, z, id, state);
}
dimension.add(world.dimension);
world.add_entity(dimension);
+2 -2
View File
@@ -39,8 +39,8 @@ export class NetworkSystem extends System {
break;
}
case "set_block":
world.dimension.record_change(message.x, message.y, message.z, message.id);
world.dimension.apply_change(message.x, message.y, message.z, message.id);
world.dimension.record_change(message.x, message.y, message.z, message.id, message.state);
world.dimension.apply_change(message.x, message.y, message.z, message.id, message.state);
break;
case "chat":
world.add_chat(message.from ? `<${message.from}> ${message.text}` : message.text);
+12 -3
View File
@@ -128,6 +128,10 @@ export class PlayerControlsSystem extends System {
if (block && player_component.screens.length === 0) {
const block_info = EverythingRegistry.get_by_id<BlockRegistry>("blocks", block.block)!;
if (InputManager.is_mouse_pressed(0)) {
// for mods' on_click, breaking itself is timed here and sent when done
send({ type: "hit_block", x: block.x, y: block.y, z: block.z });
}
if (player_component.breaking_block) {
player_component.breaking_block = { x: block.x, y: block.y, z: block.z };
player_component.break_progress_max = block_info.toughness ?? 9999;
@@ -151,9 +155,11 @@ export class PlayerControlsSystem extends System {
const target = { x: block.x + offset.x, y: block.y + offset.y, z: block.z + offset.z };
const target_id = world.dimension.get_block(target.x, target.y, target.z);
const replaceable = target_id === AIR ||
EverythingRegistry.get_by_id<BlockRegistry>("blocks", target_id)?.id === "bworld:water";
if (!block_info.interactive && holding_item_info?.block_id && replaceable) {
world.dimension.add_block({ ...target, id: holding_item_info.block_id });
EverythingRegistry.get_by_id<BlockRegistry>("blocks", target_id)?.replaceable;
// items with components might do something else on the server, like on_use
const place_id = holding_item_info?.components ? undefined : holding_item_info?.block_id;
if (!block_info.interactive && place_id && replaceable) {
world.dimension.add_block({ ...target, id: place_id });
hotbar_slot.amount = hotbar_slot.amount! - 1;
}
}
@@ -161,6 +167,9 @@ export class PlayerControlsSystem extends System {
player_component.breaking_block = undefined;
player_component.break_progress_max = 0;
player_component.break_progress = 0;
if (!block && player_component.screens.length === 0 && InputManager.is_mouse_pressed(2)) {
send({ type: "use_item" });
}
}
if (player_component.screens.length === 0) {
+5 -1
View File
@@ -6,6 +6,7 @@ import type { Texture } from "../renderer/types.ts";
import type { FromChunkWorker, ToChunkWorker } from "./chunk_messages.ts";
import { generate_raw_chunk, WorldgenSetup } from "$/common/generation.ts";
import { load_worldgen } from "$/common/worldgen_loader.ts";
import { default_block_value } from "$/common/utils.ts";
const pad = 0.5;
@@ -268,6 +269,8 @@ let block_ids: Record<string, number> = {};
let textures_info: TexturesInfo = {};
let image: Texture;
let worldgen: WorldgenSetup | undefined;
// numeric id to what a generated block stores, with its default states. matches the server
let default_values: number[] = [];
// generating has to wait for mods' worldgen scripts, or this worker's terrain wouldn't match the server's
let worldgen_ready: Promise<void> = Promise.resolve();
@@ -279,6 +282,7 @@ self.onmessage = async (event: MessageEvent<ToChunkWorker>) => {
block_ids = message.block_ids;
textures_info = message.textures_info;
image = message.image as Texture;
default_values = blocks_registry.map((block, nid) => default_block_value(nid, block));
worldgen_ready = load_worldgen(message.worldgen_scripts, message.ores).then((setup) => {
worldgen = setup;
});
@@ -319,7 +323,7 @@ function post(message: FromChunkWorker, transfer: Transferable[]) {
}
function generate(chunk_x: number, chunk_z: number, seed: string) {
const { blocks, spills } = generate_raw_chunk(chunk_x, chunk_z, seed, block_ids, worldgen);
const { blocks, spills } = generate_raw_chunk(chunk_x, chunk_z, seed, block_ids, worldgen, default_values);
post({ type: "generated", chunk_x, chunk_z, blocks, spills }, [blocks.buffer, spills.buffer]);
}