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
-44
View File
@@ -1,44 +0,0 @@
import { System } from "$/common/ecs/mod.ts";
import { ClientWorld } from "../client_world.ts";
import { Dimension } from "../components/dimension.ts";
import { TICK_DELTA } from "$/common/constants.ts";
import { BlockRegistry, EverythingRegistry } from "$/common/everything_registry.ts";
export class DimensionLogicSystem extends System {
update(world: ClientWorld, delta: number): void {
const dimension = world.dimension;
dimension.second_timer += delta;
dimension.tick_timer += delta;
if (dimension.second_timer >= 1) {
this.handle_second(world, dimension);
}
if (dimension.tick_timer >= TICK_DELTA) {
this.handle_tick(world, dimension);
}
}
handle_second(world: ClientWorld, dimension: Dimension) {
for (const chunk of dimension.chunks.values()) {
for (const tickable of chunk.blocks_data) {
const tile_info = EverythingRegistry.get<BlockRegistry>("blocks", tickable.id);
if (tile_info && tile_info.on_second) {
tile_info.on_second(world.dimension, tickable, dimension.second_timer);
}
}
}
dimension.second_timer = 0;
}
handle_tick(world: ClientWorld, dimension: Dimension) {
for (const chunk of dimension.chunks.values()) {
for (const tickable of chunk.blocks_data) {
const tile_info = EverythingRegistry.get<BlockRegistry>("blocks", tickable.id);
if (tile_info && tile_info.on_tick) {
tile_info.on_tick(world.dimension, tickable, dimension.tick_timer);
}
}
}
dimension.tick_timer = 0;
}
}
+43 -5
View File
@@ -2,6 +2,10 @@ import { System } from "$/common/ecs/mod.ts";
import { Position } from "$/common/components/position.ts";
import { ClientWorld } from "../client_world.ts";
import { Camera } from "../components/camera.ts";
import { Container, ItemStack } from "$/common/inventory.ts";
import { PlayerComponent } from "../player.ts";
import { GuiContainer } from "../gui/gui_container.ts";
import { show_fatal_error } from "../fatal.ts";
const MOVE_SEND_INTERVAL = 1 / 10;
const REMOTE_PLAYER_SMOOTHING = 12;
@@ -11,9 +15,9 @@ export class NetworkSystem extends System {
update(world: ClientWorld, delta: number): void {
const connection = world.connection;
if (!connection) {
return;
}
const [local_player] = world.get_tag("player")!;
const player_component = local_player.get(PlayerComponent)!;
const inventories = player_component.inventories;
for (const message of connection.incoming) {
switch (message.type) {
@@ -41,13 +45,47 @@ export class NetworkSystem extends System {
case "chat":
world.add_chat(message.from ? `<${message.from}> ${message.text}` : message.text);
break;
case "container": {
let container = message.container === "screen"
? inventories.screen
: inventories[message.container];
if (message.container === "screen" && container?.size !== message.items.length) {
container = inventories.screen = new Container(message.items.length);
}
container?.load(message.items);
break;
}
case "cursor":
inventories.cursor.item = message.item ? ItemStack.from_data(message.item) : undefined;
break;
case "open_screen": {
// replace anything open locally without telling the server, it just opened this one
player_component.screens.length = 0;
const size = Math.max(0, ...message.layout.slots.map((slot) => slot.index + 1));
inventories.screen = new Container(size);
player_component.screens.push(
new GuiContainer(inventories, (m) => connection.send(m), message.layout, message.properties),
);
break;
}
case "screen_properties": {
const screen = player_component.screens.at(-1);
if (screen instanceof GuiContainer) {
screen.properties = message.properties;
}
break;
}
case "close_screen":
// closed by the server (the block broke), it already put everything back
player_component.screens = player_component.screens.filter((s) => !(s instanceof GuiContainer));
inventories.screen = undefined;
break;
}
}
connection.incoming.length = 0;
if (connection.closed) {
world.add_chat("Lost connection to the server");
world.connection = undefined;
show_fatal_error("Lost connection to the server");
return;
}
+42 -63
View File
@@ -9,6 +9,8 @@ import { PlayerComponent } from "../player.ts";
import { GuiPlayerInventory } from "../gui/gui_player_inventory.ts";
import { CollisionCuboid } from "../components/collision.ts";
import { BlockRegistry, EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
import { AIR, FACE_OFFSETS } from "$/common/constants.ts";
import { ClientMessage } from "$/common/protocol.ts";
import { GuiChat } from "../gui/gui_chat.ts";
import { GuiInventoryScreen } from "../gui/gui_screen.ts";
@@ -24,6 +26,7 @@ export class PlayerControlsSystem extends System {
const player_component = player.get(PlayerComponent)!;
const position = player.get(Position)!;
const camera = player.get(Camera)!;
const send = (message: ClientMessage) => world.connection.send(message);
if (player_component.screens.length === 0) {
let input_x = 0;
@@ -71,7 +74,7 @@ export class PlayerControlsSystem extends System {
if (InputManager.is_key_pressed(controls.open_inventory)) {
if (player_component.screens.length === 0) {
player_component.screens.push(new GuiPlayerInventory(player_component.player_inventory));
player_component.screens.push(new GuiPlayerInventory(player_component.inventories, send));
} else if (player_component.screens.at(-1) instanceof GuiInventoryScreen) {
player_component.pop_screen();
}
@@ -119,10 +122,9 @@ export class PlayerControlsSystem extends System {
}
const block = world.dimension.get_looked_block(world.dimension, camera);
const holding_item = player_component.player_inventory.container.get_item(
player_component.player_inventory.hotbar_selected,
);
const holding_item_info = EverythingRegistry.get<ItemRegistry>("items", holding_item?.type_id ?? "");
const inventories = player_component.inventories;
const hotbar_slot = inventories.inventory.get_slot(inventories.hotbar_selected);
const holding_item_info = EverythingRegistry.get<ItemRegistry>("items", hotbar_slot.type_id ?? "");
if (block && player_component.screens.length === 0) {
const block_info = EverythingRegistry.get_by_id<BlockRegistry>("blocks", block.block)!;
@@ -135,47 +137,24 @@ export class PlayerControlsSystem extends System {
}
player_component.break_progress += delta * multiplier;
if (player_component.break_progress >= player_component.break_progress_max) {
const drop_item = !block_info.requires_tool ||
holding_item_info?.tool_type === block_info.tool_to_break;
world.dimension.break_block(block.x, block.y, block.z, drop_item);
world.dimension.sync_block(block.x, block.y, block.z);
// show it right away, the server decides drops and corrects us if it disagrees
world.dimension.break_block(block.x, block.y, block.z);
send({ type: "break_block", x: block.x, y: block.y, z: block.z });
player_component.break_progress_max = 0;
player_component.break_progress = 0;
}
} else if (InputManager.is_mouse_pressed(2)) {
// interact if possible
const interacted = block_info?.on_interact?.(world.dimension, {
x: block.x,
y: block.y,
z: block.z,
id: block_info.id,
});
if (!interacted) {
const inventory = player_component.player_inventory;
const hotbar_slot = inventory.container.get_slot(inventory.hotbar_selected);
if (hotbar_slot && hotbar_slot.has_item()) {
const item = hotbar_slot.get_item()!;
const item_info = EverythingRegistry.get<ItemRegistry>("items", item.type_id);
if (item_info && item_info.block_id) {
const FACE_OFFSETS = {
top: { x: 0, y: 1, z: 0 },
bottom: { x: 0, y: -1, z: 0 },
north: { x: 0, y: 0, z: -1 },
south: { x: 0, y: 0, z: 1 },
west: { x: -1, y: 0, z: 0 },
east: { x: 1, y: 0, z: 0 },
};
const offset = FACE_OFFSETS[block.face];
world.dimension.add_block({
x: block.x + offset.x,
y: block.y + offset.y,
z: block.z + offset.z,
id: item_info.block_id,
});
world.dimension.sync_block(block.x + offset.x, block.y + offset.y, block.z + offset.z);
hotbar_slot.amount! -= 1;
}
}
send({ type: "use_block", x: block.x, y: block.y, z: block.z, face: block.face });
// guess that it places the held block, unless the block does something when used
const offset = FACE_OFFSETS[block.face];
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 });
hotbar_slot.amount = hotbar_slot.amount! - 1;
}
}
} else {
@@ -185,32 +164,32 @@ export class PlayerControlsSystem extends System {
}
if (player_component.screens.length === 0) {
const player_inventory = player_component.player_inventory;
const previous = inventories.hotbar_selected;
const scroll = InputManager.get_wheel_delta();
if (scroll > 0) {
player_inventory.hotbar_selected = Math.min(8, player_inventory.hotbar_selected + 1);
inventories.hotbar_selected = Math.min(8, inventories.hotbar_selected + 1);
} else if (scroll < 0) {
player_inventory.hotbar_selected = Math.max(0, player_inventory.hotbar_selected - 1);
inventories.hotbar_selected = Math.max(0, inventories.hotbar_selected - 1);
}
if (InputManager.is_key_pressed(controls.hotbar_1)) {
player_inventory.hotbar_selected = 0;
} else if (InputManager.is_key_pressed(controls.hotbar_2)) {
player_inventory.hotbar_selected = 1;
} else if (InputManager.is_key_pressed(controls.hotbar_3)) {
player_inventory.hotbar_selected = 2;
} else if (InputManager.is_key_pressed(controls.hotbar_4)) {
player_inventory.hotbar_selected = 3;
} else if (InputManager.is_key_pressed(controls.hotbar_5)) {
player_inventory.hotbar_selected = 4;
} else if (InputManager.is_key_pressed(controls.hotbar_6)) {
player_inventory.hotbar_selected = 5;
} else if (InputManager.is_key_pressed(controls.hotbar_7)) {
player_inventory.hotbar_selected = 6;
} else if (InputManager.is_key_pressed(controls.hotbar_8)) {
player_inventory.hotbar_selected = 7;
} else if (InputManager.is_key_pressed(controls.hotbar_9)) {
player_inventory.hotbar_selected = 8;
const hotbar_keys = [
controls.hotbar_1,
controls.hotbar_2,
controls.hotbar_3,
controls.hotbar_4,
controls.hotbar_5,
controls.hotbar_6,
controls.hotbar_7,
controls.hotbar_8,
controls.hotbar_9,
];
const pressed = hotbar_keys.findIndex((key) => InputManager.is_key_pressed(key));
if (pressed !== -1) {
inventories.hotbar_selected = pressed;
}
if (inventories.hotbar_selected !== previous) {
send({ type: "select_slot", slot: inventories.hotbar_selected });
}
}
}
+1 -1
View File
@@ -61,7 +61,7 @@ export class RenderSystem extends System {
const player_component = entity.get(PlayerComponent);
if (player_component) {
render_player_hotbar(player_component.player_inventory);
render_player_hotbar(player_component.inventories);
render_player_crosshair();
}
}
+6 -6
View File
@@ -1,6 +1,6 @@
import { SLOT_SIZE, TEXTURE_SIZE } from "$/common/constants.ts";
import { AssetManager } from "$/client/assets.ts";
import { PlayerInventory } from "../../inventory.ts";
import { ClientInventories } from "../../inventory.ts";
import { draw_item, draw_nine_slice } from "./render_utils.ts";
import {
canvas,
@@ -14,11 +14,11 @@ import {
Texture,
} from "$/client/renderer/mod.ts";
import { PlayerComponent } from "../../player.ts";
import { get_sprite_region } from "../../../common/utils.ts";
import { get_sprite_region } from "$/client/sprites.ts";
const PADDING = 10;
export function render_player_hotbar(player_inventory: PlayerInventory) {
export function render_player_hotbar(inventories: ClientInventories) {
const ui = AssetManager.instance.get<Texture>("bworld:ui");
const hotbar_width = PADDING * 2 + SLOT_SIZE * 9;
@@ -46,8 +46,8 @@ export function render_player_hotbar(player_inventory: PlayerInventory) {
for (let index = 0; index < 9; index += 1) {
draw_nine_slice(
ui,
player_inventory.hotbar_selected === index ? 19 * 16 : 160 + 32,
player_inventory.hotbar_selected === index ? 16 : 0,
inventories.hotbar_selected === index ? 19 * 16 : 160 + 32,
inventories.hotbar_selected === index ? 16 : 0,
16,
16,
4,
@@ -62,7 +62,7 @@ export function render_player_hotbar(player_inventory: PlayerInventory) {
}
for (let index = 0; index < 9; index += 1) {
const item = player_inventory.container.get_item(index);
const item = inventories.inventory.get_item(index);
if (item) {
draw_item(item, x + PADDING + index * SLOT_SIZE, y + PADDING);
}
+2 -2
View File
@@ -1,8 +1,8 @@
import { SLOT_SIZE, TEXTURE_SIZE } from "$/common/constants.ts";
import { get_sprite_region } from "$/common/utils.ts";
import { get_sprite_region } from "$/client/sprites.ts";
import { BlockRegistry, EverythingRegistry, ItemRegistry } from "$/common/everything_registry.ts";
import { AssetManager } from "$/client/assets.ts";
import { ItemStack } from "../../inventory.ts";
import { ItemStack } from "$/common/inventory.ts";
import { draw_text, draw_texture_region, draw_texture_region_skewed, Texture } from "$/client/renderer/mod.ts";
export function draw_nine_slice(