Server authority

This commit is contained in:
2026-09-24 19:34:07 -03:00
parent 1bef94ce0c
commit 79faa556de
75 changed files with 2247 additions and 1632 deletions
+19 -75
View File
@@ -1,4 +1,5 @@
import { Component } from "$/common/ecs/mod.ts";
import { chunk_key } 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";
@@ -6,19 +7,9 @@ import { AssetManager } from "../assets.ts";
import { ClientWorld } from "../client_world.ts";
import { ChunkWorkerPool } from "../chunk_workers.ts";
import type { FromChunkWorker } from "../workers/chunk_messages.ts";
import { ItemStack } from "../inventory.ts";
import { PlayerComponent } from "../player.ts";
import { create_vertex_buffer, destroy_vertex_buffer, Texture } from "../renderer/mod.ts";
import { Camera } from "./camera.ts";
export interface BlockData<T = unknown> {
id: string;
x: number;
y: number;
z: number;
data: T;
}
export interface Block {
id: string;
x: number;
@@ -32,7 +23,6 @@ export interface Chunk {
x: number;
z: number;
blocks: Uint32Array;
blocks_data: BlockData[];
generated: boolean;
dirty: boolean;
// bumped on every mesh request so late results from older requests get ignored
@@ -43,10 +33,7 @@ export interface Chunk {
transparent_vertex_count?: number;
}
// numeric so looking chunks up doesnt allocate a string every time, fine for |x|, |z| < 32768
export function chunk_key(x: number, z: number) {
return (x + 32768) * 65536 + (z + 32768);
}
export { chunk_key };
const NEIGHBOR_OFFSETS = [[-1, 0], [1, 0], [0, -1], [0, 1]] as const;
@@ -99,7 +86,6 @@ export class Dimension extends Component {
x,
z,
blocks,
blocks_data: [],
dirty: true,
generated: false,
mesh_version: 0,
@@ -120,7 +106,7 @@ export class Dimension extends Component {
chunk = this.add_chunk(block_chunk_x, block_chunk_z);
}
const [nid, block_info] = EverythingRegistry.get_full<BlockRegistry>("blocks", block.id)!;
const nid = EverythingRegistry.get_id("blocks", block.id)!;
const lx = block.x - block_chunk_x * CHUNK_SIZE;
const lz = block.z - block_chunk_z * CHUNK_SIZE;
@@ -130,9 +116,7 @@ export class Dimension extends Component {
chunk.blocks[index] = nid;
chunk.dirty = true;
if (block_info?.on_create) {
block_info?.on_create(this, block);
}
this.#mark_border_neighbors_dirty(block_chunk_x, block_chunk_z, lx, lz);
}
get_block(x: number, y: number, z: number) {
@@ -154,33 +138,8 @@ export class Dimension extends Component {
return chunk.blocks[index] & ID_MASK;
}
add_block_data(block_data: BlockData) {
const block_chunk_x = Math.floor(block_data.x / CHUNK_SIZE);
const block_chunk_z = Math.floor(block_data.z / CHUNK_SIZE);
let chunk = this.get_chunk(block_chunk_x, block_chunk_z);
if (!chunk) {
chunk = this.add_chunk(block_chunk_x, block_chunk_z);
}
chunk.blocks_data.push(block_data);
}
get_block_data<T>(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 undefined;
}
return chunk.blocks_data.find((block_data) =>
block_data.x === x && block_data.y === y && block_data.z === z
) as BlockData<T>;
}
break_block(x: number, y: number, z: number, drop_item: boolean = true) {
// only changes what this client shows, drops and everything else happen on the server
break_block(x: number, y: number, z: number) {
const block_chunk_x = Math.floor(x / CHUNK_SIZE);
const block_chunk_z = Math.floor(z / CHUNK_SIZE);
const chunk = this.get_chunk(block_chunk_x, block_chunk_z);
@@ -188,14 +147,6 @@ export class Dimension extends Component {
return;
}
const block_info = EverythingRegistry.get_by_id<BlockRegistry>("blocks", this.get_block(x, y, z))!;
if (drop_item && block_info.drop_table) {
const [player] = this.world.get_tag("player")!;
const player_component = player.get(PlayerComponent)!;
player_component.player_inventory.container.add_item(new ItemStack(block_info.drop_table));
}
const lx = x - block_chunk_x * CHUNK_SIZE;
const lz = z - block_chunk_z * CHUNK_SIZE;
const ly = y;
@@ -203,7 +154,11 @@ export class Dimension extends Component {
const index = ly * CHUNK_SIZE * CHUNK_SIZE + lz * CHUNK_SIZE + lx;
chunk.blocks[index] = AIR;
chunk.dirty = true;
this.#mark_border_neighbors_dirty(block_chunk_x, block_chunk_z, lx, lz);
}
// a block on a chunk's edge changes which faces its neighbor shows
#mark_border_neighbors_dirty(block_chunk_x: number, block_chunk_z: number, lx: number, lz: number) {
if (lx === 0) {
const n = this.get_chunk(block_chunk_x - 1, block_chunk_z);
if (n) {
@@ -226,10 +181,6 @@ export class Dimension extends Component {
n.dirty = true;
}
}
if (block_info?.on_break) {
block_info.on_break(this, { x, y, z, id: block_info.id });
}
}
index_to_xyz(index: number) {
@@ -252,18 +203,7 @@ export class Dimension extends Component {
chunk_changes.set(`${x},${y},${z}`, [x, y, z, id]);
}
// call after the player changes a block so it gets saved and sent to the server
sync_block(x: number, y: number, z: number) {
const numeric_id = this.get_block(x, y, z);
const id = numeric_id === AIR ? AIR_ID : EverythingRegistry.get_by_id<BlockRegistry>("blocks", numeric_id)?.id;
if (!id) {
return;
}
this.record_change(x, y, z, id);
this.world.connection?.send({ type: "set_block", x, y, z, id });
}
// set a block without drops or syncing, for changes that came from somewhere else
// set a block the server told us about
apply_change(x: number, y: number, z: number, id: string) {
const chunk = this.get_chunk(Math.floor(x / CHUNK_SIZE), Math.floor(z / CHUNK_SIZE));
if (!chunk || !chunk.generated) {
@@ -273,7 +213,7 @@ export class Dimension extends Component {
const current = this.get_block(x, y, z);
if (id === AIR_ID) {
if (current !== AIR) {
this.break_block(x, y, z, false);
this.break_block(x, y, z);
}
return;
}
@@ -423,7 +363,8 @@ export class Dimension extends Component {
chunk.transparent_vertex_count = message.transparent_count / 9;
}
// sets a block from generation, no hooks, makes a placeholder chunk like add_block does
// a neighbor's leaves, only fill air so the result doesn't depend on which chunk loaded first.
// the server builds chunks the same way (server/game/world.ts)
#set_block_raw(x: number, y: number, z: number, nid: number) {
if (y < 0 || y >= CHUNK_HEIGHT) {
return;
@@ -433,8 +374,11 @@ export class Dimension extends Component {
const chunk = this.get_chunk(chunk_x, chunk_z) ?? this.add_chunk(chunk_x, chunk_z);
const lx = x - chunk_x * CHUNK_SIZE;
const lz = z - chunk_z * CHUNK_SIZE;
chunk.blocks[y * CHUNK_AREA + lz * CHUNK_SIZE + lx] = nid;
chunk.dirty = true;
const index = y * CHUNK_AREA + lz * CHUNK_SIZE + lx;
if (chunk.blocks[index] === AIR) {
chunk.blocks[index] = nid;
chunk.dirty = true;
}
}
delete_chunk_mesh(chunk: Chunk) {