/// import { EventEmitter } from 'events'; import { Socket, Server as NetServer } from 'net'; // ═══════════════════════════════════════════════════════════════════════════ // Version // ═══════════════════════════════════════════════════════════════════════════ export const VERSION: string; export function getVersion(): string; // ═══════════════════════════════════════════════════════════════════════════ // Protocol constants // ═══════════════════════════════════════════════════════════════════════════ export const BLOCKS: Readonly<{ AIR: 0; STONE: 1; GRASS: 2; DIRT: 3; COBBLESTONE: 4; WOOD_PLANKS: 5; SAPLING: 6; BEDROCK: 7; WATER_FLOWING: 8; WATER: 9; LAVA_FLOWING: 10; LAVA: 11; SAND: 12; GRAVEL: 13; GOLD_ORE: 14; IRON_ORE: 15; COAL_ORE: 16; LOG: 17; LEAVES: 18; SPONGE: 19; GLASS: 20; RED_CLOTH: 21; ORANGE_CLOTH: 22; YELLOW_CLOTH: 23; LIME_CLOTH: 24; GREEN_CLOTH: 25; TEAL_CLOTH: 26; AQUA_CLOTH: 27; CYAN_CLOTH: 28; BLUE_CLOTH: 29; INDIGO_CLOTH: 30; VIOLET_CLOTH: 31; MAGENTA_CLOTH: 32; PINK_CLOTH: 33; BLACK_CLOTH: 34; GRAY_CLOTH: 35; WHITE_CLOTH: 36; DANDELION: 37; ROSE: 38; BROWN_MUSHROOM: 39; RED_MUSHROOM: 40; GOLD_BLOCK: 41; IRON_BLOCK: 42; DOUBLE_SLAB: 43; SLAB: 44; BRICK: 45; TNT: 46; BOOKSHELF: 47; MOSSY_COBBLE: 48; OBSIDIAN: 49; }>; export const BLOCK_NAMES: Readonly>; export const BLOCK_MODE: Readonly<{ DESTROY: 0x00; CREATE: 0x01 }>; export const USER_TYPE: Readonly<{ NORMAL: 0x00; OP: 0x64 }>; /** Send as `unused` byte in client identification to signal CPE support. */ export const CPE_MAGIC: 0x42; // ═══════════════════════════════════════════════════════════════════════════ // Client // ═══════════════════════════════════════════════════════════════════════════ export interface ClientOptions { host?: string; port?: number; /** Milliseconds before giving up on TCP connect (default: 10 000) */ connectTimeout?: number; /** Milliseconds between emitting 'ping' events; 0 = disabled (default: 0) */ pingInterval?: number; } export class ClassiCubeClient extends EventEmitter { constructor(options?: ClientOptions); socket: Socket | null; connected: boolean; options: ClientOptions; connect(host: string, port: number, timeout?: number): Promise; disconnect(): void; writeBuffer(buf: Buffer): void; sendIdentification(username: string, verificationKey?: string, unused?: number): void; sendSetBlock(x: number, y: number, z: number, mode: number, blockType: number): void; /** Send position using raw FShort coordinates. */ sendPosition(x: number, y: number, z: number, yaw: number, pitch: number): void; /** Send position using block coordinates (auto-converts to FShort). */ sendPositionBlocks(bx: number, by: number, bz: number, yaw: number, pitch: number): void; sendMessage(message: string): void; on(event: 'connect', listener: () => void): this; on(event: 'packet', listener: (packet: AnyPacket) => void): this; on(event: 'level', listener: (level: LevelData) => void): this; on(event: 'levelProgress', listener: (percent: number) => void): this; on(event: 'ping', listener: () => void): this; on(event: 'end', listener: () => void): this; on(event: 'error', listener: (error: Error) => void): this; on(event: string, listener: (...args: any[]) => void): this; } export function createClient(options?: ClientOptions): ClassiCubeClient; // ═══════════════════════════════════════════════════════════════════════════ // Server // ═══════════════════════════════════════════════════════════════════════════ export interface ServerOptions { port?: number; host?: string; /** Set false to call listen() manually (default: true when port is given) */ autoListen?: boolean; /** Reject connections beyond this limit; 0 = unlimited (default: 0) */ maxClients?: number; /** ms between automatic server→all pings; 0 = disabled (default: 0) */ pingInterval?: number; } export class ClientConnection extends EventEmitter { constructor(socket: Socket, id: number); socket: Socket; id: number; username: string | null; state: 'login' | 'level' | 'play'; /** Arbitrary data bag for host application use. */ data: Record; readonly remoteAddress: string; readonly remotePort: number; readonly isConnected: boolean; writeBuffer(buf: Buffer): void; sendIdentification(serverName: string, motd: string, userType?: number): void; sendPing(): void; sendLevelInitialize(): void; sendLevelDataChunk(chunkData: Buffer, percentComplete: number): void; sendLevelFinalize(xSize: number, ySize: number, zSize: number): void; /** Full level send in one call. Compresses and chunks automatically. */ sendLevel(blocks: Buffer, xSize: number, ySize: number, zSize: number, zlibOpts?: object): Promise; sendSetBlock(x: number, y: number, z: number, blockType: number): void; sendSpawnPlayer(playerId: number, playerName: string, x: number, y: number, z: number, yaw?: number, pitch?: number): void; /** Spawn at block coordinates (auto-converts to FShort). */ spawnAt(playerId: number, playerName: string, bx: number, by: number, bz: number, yaw?: number, pitch?: number): void; sendPosition(playerId: number, x: number, y: number, z: number, yaw?: number, pitch?: number): void; sendPositionOrientation(playerId: number, dx: number, dy: number, dz: number, yaw: number, pitch: number): void; sendPositionUpdate(playerId: number, dx: number, dy: number, dz: number): void; sendOrientationUpdate(playerId: number, yaw: number, pitch: number): void; sendDespawnPlayer(playerId: number): void; sendMessage(senderId: number, message: string): void; /** Shorthand: sendMessage(0xFF, message). */ sendServerMessage(message: string): void; sendUpdateUserType(userType: number): void; /** Convenience: promote/demote this connection. */ setOperator(isOp?: boolean): void; disconnect(reason?: string): void; on(event: 'packet', listener: (packet: AnyPacket) => void): this; on(event: 'end', listener: () => void): this; on(event: 'error', listener: (error: Error) => void): this; on(event: string, listener: (...args: any[]) => void): this; } export class ClassiCubeServer extends EventEmitter { constructor(options?: ServerOptions); clients: Map; readonly playerCount: number; listen(port?: number, host?: string): Promise; close(): Promise; /** Disconnect all clients then close the server. */ shutdown(reason?: string): Promise; broadcast(buf: Buffer): void; broadcastMessage(message: string, senderId?: number): void; broadcastExcept(excludeId: number, buf: Buffer): void; /** Relay a player's message to everyone else. */ relayMessage(fromId: number, message: string): void; pingAll(): void; on(event: 'listening', listener: (info: { port: number; host: string }) => void): this; on(event: 'connection', listener: (client: ClientConnection) => void): this; on(event: 'disconnect', listener: (client: ClientConnection) => void): this; on(event: 'clientError', listener: (client: ClientConnection, error: Error) => void): this; on(event: 'error', listener: (error: Error) => void): this; on(event: string, listener: (...args: any[]) => void): this; } export function createServer(options?: ServerOptions): ClassiCubeServer; // ═══════════════════════════════════════════════════════════════════════════ // Encoder // ═══════════════════════════════════════════════════════════════════════════ export namespace encoder { function writeString(buf: Buffer, offset: number, str: string): number; function readString(buf: Buffer, offset: number): string; function toFShort(blockPos: number): number; function fromFShort(fshort: number): number; function encodeServerIdentification(serverName: string, motd: string, userType?: number): Buffer; function encodePing(): Buffer; function encodeLevelInitialize(): Buffer; function encodeLevelDataChunk(chunkData: Buffer, percentComplete: number): Buffer; function encodeLevelFinalize(xSize: number, ySize: number, zSize: number): Buffer; function encodeServerSetBlock(x: number, y: number, z: number, blockType: number): Buffer; function encodeSpawnPlayer(playerId: number, playerName: string, x: number, y: number, z: number, yaw: number, pitch: number): Buffer; function encodePosition(playerId: number, x: number, y: number, z: number, yaw: number, pitch: number): Buffer; function encodePositionOrientation(playerId: number, dx: number, dy: number, dz: number, yaw: number, pitch: number): Buffer; function encodePositionUpdate(playerId: number, dx: number, dy: number, dz: number): Buffer; function encodeOrientationUpdate(playerId: number, yaw: number, pitch: number): Buffer; function encodeDespawnPlayer(playerId: number): Buffer; function encodeServerMessage(playerId: number, message: string): Buffer; function encodeDisconnect(reason: string): Buffer; function encodeUpdateUserType(userType: number): Buffer; function encodeClientIdentification(username: string, verificationKey?: string, unused?: number): Buffer; function encodeClientSetBlock(x: number, y: number, z: number, mode: number, blockType: number): Buffer; function encodeClientPosition(x: number, y: number, z: number, yaw: number, pitch: number): Buffer; function encodeClientMessage(message: string): Buffer; } // ═══════════════════════════════════════════════════════════════════════════ // Decoder // ═══════════════════════════════════════════════════════════════════════════ export class PacketDecoder extends EventEmitter { constructor(direction: 'client' | 'server'); direction: 'client' | 'server'; receive(data: Buffer): void; on(event: 'packet', listener: (packet: AnyPacket) => void): this; on(event: 'error', listener: (error: Error) => void): this; on(event: string, listener: (...args: any[]) => void): this; } // ═══════════════════════════════════════════════════════════════════════════ // Protocol module // ═══════════════════════════════════════════════════════════════════════════ export namespace protocol { const PROTOCOL_VERSION: number; const STRING_LENGTH: number; const CLIENT_PACKETS: Record; const SERVER_PACKETS: Record; const CLIENT_PACKET_SIZES: Record; const SERVER_PACKET_SIZES: Record; const BLOCK_MODE: Readonly<{ DESTROY: 0x00; CREATE: 0x01 }>; const USER_TYPE: Readonly<{ NORMAL: 0x00; OP: 0x64 }>; const CPE_MAGIC: 0x42; const BLOCKS: Record; const BLOCK_NAMES: Record; } // ═══════════════════════════════════════════════════════════════════════════ // Level module // ═══════════════════════════════════════════════════════════════════════════ export interface LevelData { blocks: Buffer; xSize: number; ySize: number; zSize: number; } export namespace level { const CHUNK_SIZE: number; function gzip(buf: Buffer, options?: object): Promise; function gunzip(buf: Buffer): Promise; function compressLevel(blocks: Buffer, opts?: object): Promise; function chunkLevel(gzipped: Buffer): { chunk: Buffer; percent: number }[]; function prepareLevel(blocks: Buffer, opts?: object): Promise<{ chunk: Buffer; percent: number }[]>; function blockIndex(x: number, z: number, y: number, xSize: number, zSize: number): number; function buildMap(xSize: number, ySize: number, zSize: number, fn: (x: number, y: number, z: number) => number): Buffer; function buildFlatMap(xSize: number, ySize: number, zSize: number, groundY?: number): Buffer; function buildSphereMap(xSize: number, ySize: number, zSize: number, radius?: number, shell?: number, blockType?: number): Buffer; function buildCheckerMap(xSize: number, ySize: number, zSize: number): Buffer; class LevelAssembler { reset(): void; push(data: Buffer, length: number): void; decompress(): Promise; readonly byteLength: number; } } // ═══════════════════════════════════════════════════════════════════════════ // UUID helpers // ═══════════════════════════════════════════════════════════════════════════ export namespace jugadorUUID { function generarUUID(username: string): string; function validarUUID(uuid: string): boolean; function uuidToHex(uuid: string): string; function hexToUUID(hex: string): string; } // ═══════════════════════════════════════════════════════════════════════════ // Packet union types // ═══════════════════════════════════════════════════════════════════════════ export type AnyPacket = | IdentificationPacket | PingPacket | LevelInitializePacket | LevelDataChunkPacket | LevelFinalizePacket | SetBlockPacket | SpawnPlayerPacket | PositionPacket | PositionOrientationPacket | PositionUpdatePacket | OrientationUpdatePacket | DespawnPlayerPacket | MessagePacket | DisconnectPacket | UpdateUserTypePacket | UnknownPacket; export interface IdentificationPacket { name: 'identification'; id: number; protocolVersion: number; username?: string; verificationKey?: string; serverName?: string; motd?: string; userType?: number; unused?: number; } export interface PingPacket { name: 'ping'; id: number; } export interface LevelInitializePacket { name: 'levelInitialize'; id: number; } export interface LevelDataChunkPacket { name: 'levelDataChunk'; id: number; chunkLength: number; chunkData: Buffer; percentComplete: number; } export interface LevelFinalizePacket { name: 'levelFinalize'; id: number; xSize: number; ySize: number; zSize: number; } export interface SetBlockPacket { name: 'setBlock'; id: number; x: number; y: number; z: number; blockType: number; mode?: number; } export interface SpawnPlayerPacket { name: 'spawnPlayer'; id: number; playerId: number; playerName: string; x: number; y: number; z: number; yaw: number; pitch: number; } export interface PositionPacket { name: 'position'; id: number; playerId: number; x: number; y: number; z: number; yaw: number; pitch: number; } export interface PositionOrientationPacket { name: 'positionOrientation'; id: number; playerId: number; dx: number; dy: number; dz: number; yaw: number; pitch: number; } export interface PositionUpdatePacket { name: 'positionUpdate'; id: number; playerId: number; dx: number; dy: number; dz: number; } export interface OrientationUpdatePacket{ name: 'orientationUpdate'; id: number; playerId: number; yaw: number; pitch: number; } export interface DespawnPlayerPacket { name: 'despawnPlayer'; id: number; playerId: number; } export interface MessagePacket { name: 'message'; id: number; playerId?: number; message: string; unused?: number; } export interface DisconnectPacket { name: 'disconnect'; id: number; reason: string; } export interface UpdateUserTypePacket { name: 'updateUserType'; id: number; userType: number; } export interface UnknownPacket { name: 'unknown'; id: number; raw: Buffer; } // ═══════════════════════════════════════════════════════════════════════════ // Auth // ═══════════════════════════════════════════════════════════════════════════ // ── Static helpers ──────────────────────────────────────────────────────── /** * Generate a cryptographically random salt string. * @param length Number of characters (default 16). */ export function generateSalt(length?: number): string; /** * Compute the MPPass a player should send: lowercase MD5 hex of (salt + username). */ export function computeMPPass(salt: string, username: string): string; /** * Verify a player's MPPass. Case-insensitive, matching original ClassiCube behaviour. */ export function verifyMPPass(salt: string, username: string, mppass: string): boolean; // ── ClassiCubeAuth ──────────────────────────────────────────────────────── export interface ClassiCubeAuthOptions { /** Server name shown in the classicube.net server list. */ name: string; /** TCP port the server listens on. */ port: number; /** Maximum players (default 20). */ maxPlayers?: number; /** Show in the public server list (default true). */ public?: boolean; /** Server software name (optional). */ software?: string; /** Set true if the server is accessible via the ClassiCube web client. */ web?: boolean; /** Provide your own salt; otherwise one is auto-generated. */ salt?: string; } export interface HeartbeatResult { /** The classicube.net play URL for this server. */ url: string; /** Non-null if the heartbeat request failed. */ error: string | null; } /** * Server-side auth: MPPass verification + classicube.net heartbeat. * * @example * const auth = new ClassiCubeAuth({ name: 'My Server', port: 25565 }); * const url = await auth.startHeartbeat(() => server.playerCount); * // on player connect: * if (!auth.verify(username, mppass)) client.disconnect('Bad login'); */ export class ClassiCubeAuth { constructor(options: ClassiCubeAuthOptions); /** The random salt used for MPPass verification. Include in heartbeats. */ readonly salt: string; /** The last play URL returned by classicube.net, or null before first heartbeat. */ readonly serverUrl: string | null; /** Verify a player's MPPass against this server's salt. */ verify(username: string, mppass: string): boolean; /** Compute the MPPass you expect from a specific player (for debugging / invites). */ computeExpected(username: string): string; /** Send a single heartbeat. Returns the server URL and any error. */ sendHeartbeat(playerCount?: number): Promise; /** * Start periodic heartbeats (every 45 s). * Resolves with the play URL after the first successful heartbeat. * @param getPlayerCount Called each interval to fetch the current player count. */ startHeartbeat(getPlayerCount?: () => number): Promise; /** Stop the periodic heartbeat. */ stopHeartbeat(): void; } // ── ClassiCubeAccount ───────────────────────────────────────────────────── export interface ClassiCubeServerInfo { /** Short hash ID (used in play URLs and API calls). */ hash: string; /** Display name. */ name: string; /** IP address to connect to. */ ip: string; /** TCP port. */ port: number; /** * Your MPPass for this server. * Pass as `verificationKey` in `sendIdentification()`. */ mppass: string; /** Server software name. */ software: string; /** Current player count. */ players: number; /** Maximum player count. */ maxPlayers: number; /** Whether the server is currently online. */ online: boolean; /** Full classicube.net play URL. */ playUrl: string; } /** * Client/Bot-side: log into a real ClassiCube account and obtain MPPasses. * * @example * const acct = new ClassiCubeAccount(); * await acct.login('BotName', 'mypassword'); * const server = await acct.getServer('abc123hash'); * await client.connect(server.ip, server.port); * client.sendIdentification(acct.username, server.mppass); */ export class ClassiCubeAccount { constructor(); /** Username after successful login, null before. */ username: string | null; /** True after a successful login() call. */ loggedIn: boolean; /** * Authenticate with classicube.net (two-step CSRF login). * @throws If credentials are wrong or network fails. */ login(username: string, password: string): Promise; /** Fetch the full server list including your MPPass for each entry. */ getServers(): Promise; /** * Fetch info + MPPass for one specific server by its hash. * @param hash The short hash in the play URL (e.g. 'abc123'). */ getServer(hash: string): Promise; /** * Find the first server whose name contains the query (case-insensitive). * @throws If no matching server is found. */ findServer(nameQuery: string): Promise; } // ── auth namespace ──────────────────────────────────────────────────────── export namespace auth { export { ClassiCubeAuth, ClassiCubeAccount, generateSalt, computeMPPass, verifyMPPass }; export const HEARTBEAT_URL: string; export const HEARTBEAT_INTERVAL: number; }