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; export class NetworkSystem extends System { move_timer = 0; update(world: ClientWorld, delta: number): void { const connection = world.connection; 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) { case "player_join": connection.add_player(message.player); break; case "player_leave": connection.players.delete(message.id); break; case "player_move": { const player = connection.players.get(message.id); if (player) { player.x = message.x; player.y = message.y; player.z = message.z; player.yaw = message.yaw; player.pitch = message.pitch; } 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); break; 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) { show_fatal_error("Lost connection to the server"); return; } const t = Math.min(1, delta * REMOTE_PLAYER_SMOOTHING); for (const player of connection.players.values()) { player.display_x += (player.x - player.display_x) * t; player.display_y += (player.y - player.display_y) * t; player.display_z += (player.z - player.display_z) * t; } this.move_timer += delta; if (this.move_timer >= MOVE_SEND_INTERVAL) { this.move_timer = 0; const [player] = world.get_tag("player")!; const position = player.get(Position)!; const camera = player.get(Camera)!; connection.send({ type: "move", x: position.x, y: position.y, z: position.z, yaw: camera.yaw, pitch: camera.pitch, }); } } }