chore: initial commit
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
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<object, CommandTree>();
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user