import { System } from "$/common/ecs/mod.ts"; import { Velocity } from "$/common/components/velocity.ts"; import { InputManager } from "../input_manager.ts"; import { ClientWorld } from "../client_world.ts"; import { PlayerControls } from "../components/player_controls.ts"; import { Camera } from "../components/camera.ts"; import { Position } from "../../common/components/position.ts"; 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"; export class PlayerControlsSystem extends System { constructor() { super(); } update(world: ClientWorld, delta: number): void { const [player] = world.get_tag("player")!; const velocity = player.get(Velocity)!; const controls = player.get(PlayerControls)!; 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; let input_z = 0; if (InputManager.is_key_down(controls.move_left)) { input_x -= 1; } if (InputManager.is_key_down(controls.move_right)) { input_x += 1; } if (InputManager.is_key_down(controls.move_forward)) { input_z -= 1; } if (InputManager.is_key_down(controls.move_backwards)) { input_z += 1; } const size = Math.hypot(input_x, input_z); if (size > 0) { input_x /= size; input_z /= size; } const sin = Math.sin(camera.yaw); const cos = Math.cos(camera.yaw); const forwardX = sin; const forwardZ = cos; const rightX = cos; const rightZ = -sin; const speed_modifier = InputManager.is_key_down(controls.sprint_key) ? 1.75 : 1; velocity.vx = (forwardX * input_z + rightX * input_x) * controls.move_speed * speed_modifier; velocity.vz = (forwardZ * input_z + rightZ * input_x) * controls.move_speed * speed_modifier; const cuboid = player.get(CollisionCuboid); if (InputManager.is_key_down("Space") && cuboid?.colliding_y === 1) { velocity.vy += controls.jump_force; } } if (InputManager.is_key_pressed(controls.open_inventory)) { if (player_component.screens.length === 0) { player_component.screens.push(new GuiPlayerInventory(player_component.inventories, send)); } else if (player_component.screens.at(-1) instanceof GuiInventoryScreen) { player_component.pop_screen(); } } if (InputManager.is_key_pressed(controls.open_chat)) { if (player_component.screens.length === 0) { player_component.screens.push(new GuiChat(world)); } } if (InputManager.is_key_pressed("Escape")) { player_component.pop_screen(); } if (InputManager.is_key_pressed(controls.open_debug)) { world.debugging = !world.debugging; } if (InputManager.is_key_pressed("F11")) { InputManager.toggle_fullscreen(); } camera.x = position.x; camera.y = position.y + 1.69; camera.z = position.z; if (InputManager.is_mouse_grabbed()) { const mouse_delta = InputManager.get_mouse_delta(); camera.yaw += -mouse_delta.x * 0.001; camera.pitch += -mouse_delta.y * 0.001; const limit = Math.PI / 2 - 0.01; camera.pitch = Math.max(-limit, Math.min(limit, camera.pitch)); } InputManager.set_mouse_grabbed(player_component.screens.length === 0); if (InputManager.is_mouse_down(0) && player_component.screens.length === 0) { player_component.breaking_block = { x: 0, y: 9999, z: 0 }; } else { player_component.breaking_block = undefined; player_component.break_progress_max = 0; player_component.break_progress = 0; } const block = world.dimension.get_looked_block(world.dimension, camera); const inventories = player_component.inventories; const hotbar_slot = inventories.inventory.get_slot(inventories.hotbar_selected); const holding_item_info = EverythingRegistry.get("items", hotbar_slot.type_id ?? ""); if (block && player_component.screens.length === 0) { const block_info = EverythingRegistry.get_by_id("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; let multiplier = 1; if (holding_item_info?.tool_type === block_info.tool_to_break) { multiplier *= 2; } player_component.break_progress += delta * multiplier; if (player_component.break_progress >= player_component.break_progress_max) { // 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)) { 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("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; } } } else { 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) { const previous = inventories.hotbar_selected; const scroll = InputManager.get_wheel_delta(); if (scroll > 0) { inventories.hotbar_selected = Math.min(8, inventories.hotbar_selected + 1); } else if (scroll < 0) { inventories.hotbar_selected = Math.max(0, inventories.hotbar_selected - 1); } 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 }); } } } }