interface CommandNode { flags?: {command_node_type?: number}; children?: number[]; redirectNode?: number; extraNodeData?: {name?: string; parser?: string; properties?: {type?: number | string}}; } interface CommandTree {nodes: CommandNode[]; rootIndex: number} const trees = new WeakMap(); export function remember_command_tree(instance: object, packet: unknown): void { const tree = packet as CommandTree; if (tree && Array.isArray(tree.nodes) && Number.isSafeInteger(tree.rootIndex) && tree.nodes.length <= 100_000 && tree.nodes[tree.rootIndex]) trees.set(instance, tree); } export function local_command_completion(instance: object, input: string, players: string[]): {matches: string[]; start: number; length: number} { const start = input.search(/\S*$/); const offset = start === 0 && input.startsWith('/') ? 1 : start; const result = {matches: [] as string[], start: offset, length: input.length - offset}; const tree = trees.get(instance); if (!tree || !input.startsWith('/')) return result; // Walk complete arguments; only the final (possibly empty) token is being completed. const tokens = input.slice(1, start || 1).trim().split(/\s+/).filter(Boolean); let parents = [tree.rootIndex]; const children = (index: number): number[] => { const node = tree.nodes[index]; const redirect = node?.redirectNode; return Number.isSafeInteger(redirect) ? tree.nodes[redirect!]?.children ?? [] : node?.children ?? []; }; for (const token of tokens) { const next: number[] = []; for (const parent of parents) { for (const index of children(parent)) { const node = tree.nodes[index]; if (node?.flags?.command_node_type === 1 && node.extraNodeData?.name === token) next.push(index); else if (node?.flags?.command_node_type === 2) { const parser = node.extraNodeData?.parser; const kind = node.extraNodeData?.properties?.type; // Quoted and greedy strings need Brigadier parsing; leave their suggestions to the server. if (token.includes('"') || parser === 'minecraft:message' || parser === 'brigadier:string' && (kind === 2 || kind === 'greedy_phrase')) continue; next.push(index); } } } parents = [...new Set(next)].slice(0, 256); } const prefix = input.slice(offset).toLowerCase(); for (const parent of parents) { for (const index of children(parent)) { const node = tree.nodes[index]; const data = node?.extraNodeData; const suggestions = node?.flags?.command_node_type === 1 ? [data?.name] : data?.parser === 'brigadier:bool' ? ['true', 'false'] : data?.parser === 'minecraft:entity' || data?.parser === 'minecraft:game_profile' ? players : []; for (const match of suggestions) { if (typeof match === 'string' && match.length && match.length <= 256 && match.toLowerCase().startsWith(prefix) && !result.matches.includes(match)) result.matches.push(match); if (result.matches.length >= 100) return result; } } } return result; }