// messages sent between the client and the server, as json over a websocket import type { Faces } from "./constants.ts"; import type { ItemData } from "./inventory.ts"; import type { AtlasListing, ModListing } from "./mod_loader.ts"; export const AIR_ID = "bworld:air"; // bump when a client and server of different versions can't play together. // the server rejects a different version before the client downloads anything export const PROTOCOL_VERSION = 2; export interface PlayerInfo { id: string; name: string; x: number; y: number; z: number; yaw: number; pitch: number; } // an entity that isn't a player, as the server first sends it. players have their own messages export interface EntityInfo { kind: "item"; id: string; x: number; y: number; z: number; item: ItemData; } // x, y, z, block id, and its state bits when they aren't 0 export type BlockChange = [number, number, number, string, number?]; // the containers a client can see and click. "screen" is whatever the open server screen shows export type ContainerKey = "inventory" | "crafting" | "screen"; export const CRAFTING_RESULT_SLOT = 9; // a screen the server opens, drawn below the player's inventory and hotbar export interface ScreenLayout { // height of the screen's own area, in slots rows: number; // x and y in slots, can be fractional // output slots can only be taken from, like the furnace result slots: { index: number; x: number; y: number; output?: boolean }[]; // progress bars filled from properties[value] / properties[max] bars: { x: number; y: number; value: string; max: string; direction: "up" | "right"; empty_texture: string; full_texture: string; }[]; } // clients send what the player is trying to do, the server decides what happens export type ClientMessage = | { type: "hello"; name: string; protocol: number } // sent once the client has downloaded, checked and loaded everything in welcome | { type: "ready" } | { type: "move"; x: number; y: number; z: number; yaw: number; pitch: number } | { type: "break_block"; x: number; y: number; z: number } // the player started hitting a block, for on_click | { type: "hit_block"; x: number; y: number; z: number } // right click without looking at a block, for items' on_use | { type: "use_item" } // right click on a block: interact with it, or place the held block against `face` | { type: "use_block"; x: number; y: number; z: number; face: Faces } | { type: "select_slot"; slot: number } | { type: "click"; container: ContainerKey; index: number; button: number } // q on a slot (the held one when no screen is open) throws one item, or the whole stack with ctrl | { type: "drop_item"; container: ContainerKey; index: number; all: boolean } // closes the open screen, including the player's own inventory screen | { type: "close_screen" } | { type: "chat"; text: string }; export type ServerMessage = // what to download before joining, see "Delivery to clients" in MODS.md | { type: "welcome"; protocol: number; seed: string; atlas: AtlasListing; // in load order, the client loads them before joining the world mods: ModListing[]; } // the connection is closed right after | { type: "rejected"; reason: string } // answers ready, the player is in the world from here | { type: "join"; id: string; // the server may change the name asked for, like alice to alice2 name: string; players: PlayerInfo[]; // items on the ground entities: EntityInfo[]; changes: BlockChange[]; spawn: { x: number; y: number; z: number; yaw: number; pitch: number }; selected_slot: number; } | { type: "player_join"; player: PlayerInfo } | { type: "player_leave"; id: string } | { type: "player_move"; id: string; x: number; y: number; z: number; yaw: number; pitch: number } | { type: "add_entity"; entity: EntityInfo } | { type: "move_entity"; id: string; x: number; y: number; z: number } // a dropped item's stack changed, like when two stacks merge | { type: "set_entity_item"; id: string; item: ItemData } | { type: "remove_entity"; id: string } // a player picked up an item, it flies to them and goes away | { type: "take_entity"; id: string; player: string } // also sent to the player who caused it, which corrects anything their client predicted wrong | { type: "set_block"; x: number; y: number; z: number; id: string; state?: number } | { type: "chat"; from?: string; text: string } | { type: "teleport"; x: number; y: number; z: number } | { type: "container"; container: ContainerKey; items: (ItemData | null)[] } | { type: "cursor"; item: ItemData | null } | { type: "open_screen"; layout: ScreenLayout; properties: Record } | { type: "screen_properties"; properties: Record } | { type: "close_screen" }; export const MAX_NAME_LENGTH = 16; export const MAX_CHAT_LENGTH = 256;