From 8f9667780655c78d930b20892e97969aa0145497 Mon Sep 17 00:00:00 2001 From: Nathan93705 <99233134+Nathan93705@users.noreply.github.com> Date: Fri, 12 Sep 2025 23:45:22 -0700 Subject: [PATCH 1/2] Refactor and extend target selector parsing logic Introduces a TargetSelectorQuery enum and refactors the TargetEnum class to support more robust and extensible target selector parsing, including support for additional selector types, query keys, and improved error handling. The new implementation allows for more flexible and maintainable command target resolution. --- .../core/src/commands/enums/valid/target.ts | 658 +++++++++++------- 1 file changed, 413 insertions(+), 245 deletions(-) diff --git a/packages/core/src/commands/enums/valid/target.ts b/packages/core/src/commands/enums/valid/target.ts index 75795f062..b0f40c79e 100644 --- a/packages/core/src/commands/enums/valid/target.ts +++ b/packages/core/src/commands/enums/valid/target.ts @@ -1,3 +1,5 @@ +import { Vector3f } from "@serenityjs/protocol"; + import { Entity, Player } from "../../../entity"; import { ValidEnum } from "."; @@ -5,6 +7,18 @@ import { ValidEnum } from "."; import type { Dimension } from "../../../world"; import type { CommandArgumentPointer } from "../../execution-state"; +enum TargetSelectorQuery { + Name = "name", + Tag = "tag", + Range = "r", + RangeMin = "rm", + Count = "c", + Type = "type", + Mode = "m", + Level = "l", + LevelMin = "lm", +} + class TargetEnum extends ValidEnum { /** * The type of the enum. @@ -26,6 +40,11 @@ class TargetEnum extends ValidEnum { this.result = result; } + /** + * Validates the target enum. + * @param error Whether to throw an error if the validation fails. + * @returns Whether the target enum is valid. + */ public validate(error = false): boolean { // Check if we should throw an error if the result is null. if (error && !this.result) @@ -38,6 +57,11 @@ class TargetEnum extends ValidEnum { return true; } + /** + * Extracts a target enum from the command argument pointer. + * @param pointer The command argument pointer. + * @returns The extracted target enum or null if no valid target was found. + */ public static extract(pointer: CommandArgumentPointer): TargetEnum | null { // Peek the next value from the pointer. const target = pointer.next() as string; @@ -51,276 +75,420 @@ class TargetEnum extends ValidEnum { // Check if the target can be a boolean. if (target === "true" || target === "false") return new TargetEnum(null); - // Get the origin of the command. - const origin = + // Get the dimension where the command is being executed. + const dimension = pointer.state.origin instanceof Entity ? pointer.state.origin.dimension : (pointer.state.origin as Dimension); + // Get an array of all players in the dimension. + const dimensionPlayers = dimension.getPlayers(); + // Check if the target is a player or starts with @. - if ( - !target.startsWith("@") && - !origin.getPlayers().some((x) => x.username === target) - ) - return new TargetEnum(null); - - // Check if the target starts with @. - // This means we are querying for a target. - if (target.startsWith("@")) { - // Get the query symbol. (e.g. a, e, p, r, s) - const symbol = target.slice(1)[0]; - const query = target.slice(2); - - // Check if the query is valid. - if (query.length > 0 && (!query.startsWith("[") || !query.endsWith("]"))) - throw new Error("Invalid query."); // TODO: more specific error - - // Parse the query. - const queries = - query.length > 0 - ? query - .slice(1, -1) - .split(",") - .flatMap((data) => { - const [key, value] = data.split("="); - return { key, value }; - }) - : []; - - // Check if the symbol is a valid query. - switch (symbol) { - // Get all players. - case "a": { - const players = origin.getPlayers().filter((player) => { - // Check if there are any queries. - if (queries.length === 0) return true; - - // Check if the player matches the query. - for (const { key, value } of queries) { - switch (key) { - // Check if the player name matches the query. - case "name": { - // Get the name query. - let name = value as string; - - // Check if the query is negated. - const negate = name.startsWith("!"); - if (negate) name = name.slice(1); - - // Check if the player name matches the query. - if ( - negate ? player.username === name : player.username !== name - ) - return false; - break; - } - - // Check if the player tag matches the query. - case "tag": { - // Get the tag query. - let tag = value as string; - - // Check if the query is negated. - const negate = tag.startsWith("!"); - if (negate) tag = tag.slice(1); - - // Check if the player has the tag. - if (negate ? player.hasTag(tag) : !player.hasTag(tag)) - return false; - break; - } - - default: { - throw new TypeError(`Invalid query key "${key}"`); - } - } - } + const isSelector = target.startsWith("@"); + const isPlayer = dimensionPlayers.some((x) => x.username === target); - return true; - }); + // If the target is neither a selector nor a player name, return null. + if (!isSelector && !isPlayer) return new TargetEnum(null); - return new TargetEnum(players); - } + if (isPlayer) { + // Filter players by username. + const players = dimensionPlayers.filter( + (player) => player.username === target + ); + + // Check if any players were found. + if (players.length === 0) + throw new Error( + `Player "${target}" was not found in the current dimension.` + ); + + // Return the filtered players. + return new TargetEnum(players); + } else if (isSelector) return this.parseSelector(target, pointer); + else return new TargetEnum(null); + } + + /** + * Parses a target selector string. + * @param selector The target selector string. + * @param pointer The command argument pointer. + * @returns The parsed target enum. + */ + public static parseSelector( + selector: string, + pointer: CommandArgumentPointer + ): TargetEnum { + // Get the dimension where the command is being executed. + const dimension = + pointer.state.origin instanceof Entity + ? pointer.state.origin.dimension + : (pointer.state.origin as Dimension); + + // Get the query symbol. (e.g. a, e, p, r, s) + const symbol = selector.charAt(1); + const query = selector.slice(2); + + // Validate the query format. + if (query.length > 0) + if (!query.startsWith("[") || !query.endsWith("]")) + throw new Error( + `Invalid selector query. Expected format: [key=value,...]` + ); + + // Determine if the selector contains any queries. + const hasQuery = query.length > 2; // At least "[]" is required for a valid query. + + // Parse the queries from the selector. + const queries: Array<{ key: string; value: string }> = query + .slice(1, -1) // Remove the enclosing brackets. + .split(",") // Split by comma to get individual key-value pairs. + .filter((data) => data.includes("=")) // Ensure valid key-value pairs. + .map((data) => { + const [key, value] = data.split("="); + if (!key || !value) + throw new Error( + `Invalid query format: "${data}". Expected key=value.` + ); + + return { key: key.trim(), value: value.trim() }; + }); + + switch (symbol) { + // Handle "all players" selector. + case "a": { + // Get all players in the dimension. + const players = dimension.getPlayers(); + + // If there are no queries, return all players. + if (!hasQuery) return new TargetEnum(players); + + // Parse the queries. + const results = this.parseQueries(queries, players, pointer); + return new TargetEnum(results); + } + + // Handle "all entities" selector. + case "e": { + // Get all entities in the dimension. + const entities = dimension.getEntities(); + + // If there are no queries, return all entities. + if (queries.length === 0) return new TargetEnum(entities); + + if (!hasQuery) return new TargetEnum(entities); + + // Parse the queries. + const results = this.parseQueries(queries, entities, pointer); + return new TargetEnum(results); + } + + // Handle "nearest player" selector. + case "p": { + const commandPosition = new Vector3f(0, 0, 0); + if (pointer.state.origin instanceof Entity) + commandPosition.set(pointer.state.origin.position); + + // Get all players in the dimension. + const players = dimension.getPlayers(); + + // Get the nearest player to the command origin. + const nearest = players.reduce((prev, curr) => { + const prevPos = prev.position; + const currPos = curr.position; + const prevDist = Math.sqrt( + (prevPos.x - commandPosition.x) ** 2 + + (prevPos.y - commandPosition.y) ** 2 + + (prevPos.z - commandPosition.z) ** 2 + ); + const currDist = Math.sqrt( + (currPos.x - commandPosition.x) ** 2 + + (currPos.y - commandPosition.y) ** 2 + + (currPos.z - commandPosition.z) ** 2 + ); + + if (prevDist === currDist) return Math.random() < 0.5 ? prev : curr; + return prevDist < currDist ? prev : curr; + }); + + if (!hasQuery) return new TargetEnum(nearest ? [nearest] : null); + + // Parse the queries. + const results = this.parseQueries(queries, [nearest], pointer); + return new TargetEnum(results); + } + + // Handle "nearest entity" selector. + case "n": { + const commandPosition = new Vector3f(0, 0, 0); + if (pointer.state.origin instanceof Entity) + commandPosition.set(pointer.state.origin.position); + + // Get all entities in the dimension. + const entities = dimension.getEntities(); + + // Get the nearest entity to the command origin. + const nearest = entities.reduce((prev, curr) => { + const prevPos = prev.position; + const currPos = curr.position; + const prevDist = Math.sqrt( + (prevPos.x - commandPosition.x) ** 2 + + (prevPos.y - commandPosition.y) ** 2 + + (prevPos.z - commandPosition.z) ** 2 + ); + const currDist = Math.sqrt( + (currPos.x - commandPosition.x) ** 2 + + (currPos.y - commandPosition.y) ** 2 + + (currPos.z - commandPosition.z) ** 2 + ); + + if (prevDist === currDist) return Math.random() < 0.5 ? prev : curr; + return prevDist < currDist ? prev : curr; + }); + + if (!hasQuery) return new TargetEnum(nearest ? [nearest] : null); + + // Parse the queries. + const results = this.parseQueries(queries, [nearest], pointer); + return new TargetEnum(results); + } + + // Parse "random player" selector. + case "r": { + // Get all players in the dimension. + const players = dimension.getPlayers(); + + // If there are no queries, return all players. + if (queries.length === 0) return new TargetEnum(players); - // Get all entities. - case "e": { - // Filter entities by query. - const entities = origin.getEntities().filter((entity) => { - // Check if there are any queries. - if (queries.length === 0) return true; - - // Check if the entity matches the query. - for (const { key, value } of queries) { - switch (key) { - // Check if the entity name matches the query. - case "name": { - // Get the name from the query. - let name = value as string; - - const negate = name.startsWith("!"); - if (negate) name = name.slice(1); - - // Check if the nametag matches the query. - if ( - negate ? entity.nameTag === name : entity.nameTag !== name - ) - return false; - break; - } - - // Check if the entity type matches the query. - case "type": { - // Get the type query. - let type = value as string; - - // Check if the query is negated. - const negate = type.startsWith("!"); - if (negate) type = type.slice(1); - - // Parse the entity type. - const parsed = type.includes(":") - ? type - : `minecraft:${type}`; - - // Check if the entity type matches the query. - if ( - negate - ? parsed == entity.type.identifier - : parsed != entity.type.identifier - ) - return false; - break; - } - - // Check if the entity tag matches the query. - case "tag": { - // Get the tag query. - let tag = value as string; - - // Check if the query is negated. - const negate = tag.startsWith("!"); - if (negate) tag = tag.slice(1); - - // Check if the player has the tag. - if (negate ? entity.hasTag(tag) : !entity.hasTag(tag)) - return false; - break; - } - - default: { - throw new TypeError(`Invalid query key "${key}"`); - } + // Get a random player from the list. + const randomPlayer = + players[Math.floor(Math.random() * players.length)]; + if (!randomPlayer) return new TargetEnum(null); + + if (!hasQuery) return new TargetEnum([randomPlayer]); + + // Parse the queries. + const results = this.parseQueries(queries, [randomPlayer], pointer); + return new TargetEnum(results); + } + + // Parse "self" selector. + case "s": { + if (pointer.state.origin instanceof Entity) { + if (!hasQuery) return new TargetEnum([pointer.state.origin]); + + // Parse the queries. + const results = this.parseQueries( + queries, + [pointer.state.origin], + pointer + ); + return new TargetEnum(results); + } else + throw new TypeError( + "Command source is not an entity and cannot be used as a target selector in this context." + ); + } + + // Handle invalid selector symbols. + default: { + throw new Error(`Invalid selector symbol "@${symbol}"`); + } + } + } + + /** + * Parses the target selector queries. + * @param queries The queries to parse. + * @param entities The entities to filter. + * @param pointer The command argument pointer. + * @returns The filtered entities. + */ + public static parseQueries( + queries: Array<{ key: string; value: string }>, + entities: Array, + pointer: CommandArgumentPointer + ): Array { + const results: Array = []; + + // Get the position of the command execution. + const commandPosition = new Vector3f(0, 0, 0); + if (pointer.state.origin instanceof Entity) + commandPosition.set(pointer.state.origin.position); + + for (const entity of entities) { + let match = true; + for (const { key, value } of queries) { + // If already not a match, break early. + if (!match) break; + + const isNegated = value.startsWith("!"); + const queryValue = isNegated ? value.slice(1) : value; + + // Loop through each query and check if the entity matches. + switch (key) { + // Handle name query. + case TargetSelectorQuery.Name: + if (entity instanceof Player) { + if (isNegated) { + if (entity.username === queryValue) match = false; + } else { + if (entity.username !== queryValue) match = false; + } + } else { + if (isNegated) { + if (entity.nameTag === queryValue) match = false; + } else { + if (entity.nameTag !== queryValue) match = false; } } + break; + + // Handle tag query. + case TargetSelectorQuery.Tag: + if (isNegated) { + if (entity.hasTag(queryValue)) match = false; + } else { + if (!entity.hasTag(queryValue)) match = false; + } + break; - return true; - }); + // Handle range query. + case TargetSelectorQuery.Range: { + const range = Number.parseFloat(queryValue); + const entityPos = entity.position; - return new TargetEnum(entities); - } + const distance = Math.sqrt( + (entityPos.x - commandPosition.x) ** 2 + + (entityPos.y - commandPosition.y) ** 2 + + (entityPos.z - commandPosition.z) ** 2 + ); - // Get the nearest player. - case "p": { - if (pointer.state.origin instanceof Player) { - return new TargetEnum([pointer.state.origin]); - } else { - throw new TypeError( - "Nearest player is not available in this context." + // If entity is out of range, it's not a match. + if (distance > range) match = false; + break; + } + + // Handle minimum range query. + case TargetSelectorQuery.RangeMin: { + const range = Number.parseFloat(queryValue); + const entityPos = entity.position; + const distance = Math.sqrt( + (entityPos.x - commandPosition.x) ** 2 + + (entityPos.y - commandPosition.y) ** 2 + + (entityPos.z - commandPosition.z) ** 2 ); + // If entity is within minimum range, it's not a match. + if (distance < range) match = false; + break; } - } - // Get a random player. - case "r": { - // Get all players that match the query. - const players = origin.getPlayers().filter((player) => { - // Check if there are any queries. - if (queries.length === 0) return true; - - // Check if the player matches the query. - for (const { key, value } of queries) { - switch (key) { - // Check if the player name matches the query. - case "name": { - // Get the name query. - let name = value as string; - - // Check if the query is negated. - const negate = name.startsWith("!"); - if (negate) name = name.slice(1); - - // Check if the player name matches the query. - if ( - negate ? player.username === name : player.username !== name - ) - return false; - break; - } - - // Check if the entity tag matches the query. - case "tag": { - // Get the tag query. - let tag = value as string; - - // Check if the query is negated. - const negate = tag.startsWith("!"); - if (negate) tag = tag.slice(1); - - // Check if the player has the tag. - if (negate ? player.hasTag(tag) : !player.hasTag(tag)) - return false; - break; - } - - default: { - throw new TypeError(`Invalid query key "${key}"`); - } - } - } + // Handle count query. + case TargetSelectorQuery.Count: { + const count = Number.parseInt(queryValue); + if (results.length >= count) match = false; + break; + } - return true; - }); + // Handle type query. + case TargetSelectorQuery.Type: { + const type = queryValue; + const entityType = entity.type.identifier; - // Get a random player from the list. - const player = players[ - Math.floor(Math.random() * players.length) - ] as Player; + if (isNegated) { + if (entityType === type) match = false; + } else { + if (entityType !== type) match = false; + } + break; + } - // Return the random player. - return new TargetEnum([player]); - } + // Handle mode query. + case TargetSelectorQuery.Mode: { + if (!(entity instanceof Player)) + throw new SyntaxError( + `Mode query can only be used with players.` + ); + + // List of valid gamemodes and their corresponding numeric values. + const gamemodeMap: Record = { + survival: 0, + s: 0, + creative: 1, + c: 1, + adventure: 2, + a: 2, + spectator: 3, + sp: 3, + }; + + // Get the expected gamemode from the map. + const expectedGamemode = gamemodeMap[queryValue.toLowerCase()]; + if (expectedGamemode === undefined) + throw new SyntaxError( + `Invalid mode value "${queryValue}". Expected one of: ${Object.keys( + gamemodeMap + ).join(", ")}.` + ); + + // Check if the player's gamemode matches the expected gamemode. + if (isNegated) { + if (entity.gamemode === expectedGamemode) match = false; + } else { + if (entity.gamemode !== expectedGamemode) match = false; + } + break; + } - // Get the source player. - case "s": { - if (pointer.state.origin instanceof Entity) { - return new TargetEnum([pointer.state.origin]); - } else { - throw new TypeError( - "Source player is not available in this context." - ); + // Handle level query. + case TargetSelectorQuery.Level: { + if (!(entity instanceof Player)) + throw new SyntaxError( + `Level query can only be used with players.` + ); + + // Get the player's level. + const level = entity.getLevel(); + const expectedLevel = Number.parseInt(queryValue); + + if (isNegated) { + if (level === expectedLevel) match = false; + } else { + if (level !== expectedLevel) match = false; + } + break; } - } - } - } else { - // Filter players by username. - const players = origin - .getPlayers() - .filter((player) => player.username === target); - // Check if the player was found. - if (players.length === 0) { - throw new Error( - `Player "${target}" was not found in the current dimension.` - ); + // Handle minimum level query. + case TargetSelectorQuery.LevelMin: { + if (!(entity instanceof Player)) + throw new SyntaxError( + `LevelMin query can only be used with players.` + ); + + // Get the player's level. + const level = entity.getLevel(); + const expectedLevel = Number.parseInt(queryValue); + + if (isNegated) { + if (level >= expectedLevel) match = false; + } else { + if (level < expectedLevel) match = false; + } + break; + } + + // Default case for invalid query keys. + default: + throw new SyntaxError(`Invalid query key "${key}"`); + } } - // Return the player. - return new TargetEnum(players); + if (match) results.push(entity); } - - // Return null if no target was found - return new TargetEnum(null); + return results; } } -export { TargetEnum }; +export { TargetEnum, TargetSelectorQuery }; From 8c5455470d9b94b3944e4c46e57c9a4a48fcf558 Mon Sep 17 00:00:00 2001 From: Nathan93705 <99233134+Nathan93705@users.noreply.github.com> Date: Sat, 13 Sep 2025 10:55:42 -0700 Subject: [PATCH 2/2] Refactor target selector queries and distance calculation Removed the TargetSelectorQuery enum and replaced its usage with string literals for selector queries. Extracted repeated distance calculation logic into a new static method, calculateDistance, in TargetEnum for improved code reuse and readability. --- .../core/src/commands/enums/valid/target.ts | 86 +++++++++---------- 1 file changed, 39 insertions(+), 47 deletions(-) diff --git a/packages/core/src/commands/enums/valid/target.ts b/packages/core/src/commands/enums/valid/target.ts index b0f40c79e..15ad9b04b 100644 --- a/packages/core/src/commands/enums/valid/target.ts +++ b/packages/core/src/commands/enums/valid/target.ts @@ -7,18 +7,6 @@ import { ValidEnum } from "."; import type { Dimension } from "../../../world"; import type { CommandArgumentPointer } from "../../execution-state"; -enum TargetSelectorQuery { - Name = "name", - Tag = "tag", - Range = "r", - RangeMin = "rm", - Count = "c", - Type = "type", - Mode = "m", - Level = "l", - LevelMin = "lm", -} - class TargetEnum extends ValidEnum { /** * The type of the enum. @@ -106,7 +94,6 @@ class TargetEnum extends ValidEnum { // Return the filtered players. return new TargetEnum(players); } else if (isSelector) return this.parseSelector(target, pointer); - else return new TargetEnum(null); } /** @@ -154,6 +141,7 @@ class TargetEnum extends ValidEnum { return { key: key.trim(), value: value.trim() }; }); + // Handle the selector based on its symbol. switch (symbol) { // Handle "all players" selector. case "a": { @@ -196,15 +184,13 @@ class TargetEnum extends ValidEnum { const nearest = players.reduce((prev, curr) => { const prevPos = prev.position; const currPos = curr.position; - const prevDist = Math.sqrt( - (prevPos.x - commandPosition.x) ** 2 + - (prevPos.y - commandPosition.y) ** 2 + - (prevPos.z - commandPosition.z) ** 2 + const prevDist = TargetEnum.calculateDistance( + prevPos, + commandPosition ); - const currDist = Math.sqrt( - (currPos.x - commandPosition.x) ** 2 + - (currPos.y - commandPosition.y) ** 2 + - (currPos.z - commandPosition.z) ** 2 + const currDist = TargetEnum.calculateDistance( + currPos, + commandPosition ); if (prevDist === currDist) return Math.random() < 0.5 ? prev : curr; @@ -231,15 +217,13 @@ class TargetEnum extends ValidEnum { const nearest = entities.reduce((prev, curr) => { const prevPos = prev.position; const currPos = curr.position; - const prevDist = Math.sqrt( - (prevPos.x - commandPosition.x) ** 2 + - (prevPos.y - commandPosition.y) ** 2 + - (prevPos.z - commandPosition.z) ** 2 + const prevDist = TargetEnum.calculateDistance( + prevPos, + commandPosition ); - const currDist = Math.sqrt( - (currPos.x - commandPosition.x) ** 2 + - (currPos.y - commandPosition.y) ** 2 + - (currPos.z - commandPosition.z) ** 2 + const currDist = TargetEnum.calculateDistance( + currPos, + commandPosition ); if (prevDist === currDist) return Math.random() < 0.5 ? prev : curr; @@ -329,7 +313,7 @@ class TargetEnum extends ValidEnum { // Loop through each query and check if the entity matches. switch (key) { // Handle name query. - case TargetSelectorQuery.Name: + case "name": if (entity instanceof Player) { if (isNegated) { if (entity.username === queryValue) match = false; @@ -346,7 +330,7 @@ class TargetEnum extends ValidEnum { break; // Handle tag query. - case TargetSelectorQuery.Tag: + case "tag": if (isNegated) { if (entity.hasTag(queryValue)) match = false; } else { @@ -355,14 +339,13 @@ class TargetEnum extends ValidEnum { break; // Handle range query. - case TargetSelectorQuery.Range: { + case "r": { const range = Number.parseFloat(queryValue); const entityPos = entity.position; - const distance = Math.sqrt( - (entityPos.x - commandPosition.x) ** 2 + - (entityPos.y - commandPosition.y) ** 2 + - (entityPos.z - commandPosition.z) ** 2 + const distance = TargetEnum.calculateDistance( + entityPos, + commandPosition ); // If entity is out of range, it's not a match. @@ -371,13 +354,12 @@ class TargetEnum extends ValidEnum { } // Handle minimum range query. - case TargetSelectorQuery.RangeMin: { + case "rm": { const range = Number.parseFloat(queryValue); const entityPos = entity.position; - const distance = Math.sqrt( - (entityPos.x - commandPosition.x) ** 2 + - (entityPos.y - commandPosition.y) ** 2 + - (entityPos.z - commandPosition.z) ** 2 + const distance = TargetEnum.calculateDistance( + entityPos, + commandPosition ); // If entity is within minimum range, it's not a match. if (distance < range) match = false; @@ -385,14 +367,14 @@ class TargetEnum extends ValidEnum { } // Handle count query. - case TargetSelectorQuery.Count: { + case "c": { const count = Number.parseInt(queryValue); if (results.length >= count) match = false; break; } // Handle type query. - case TargetSelectorQuery.Type: { + case "type": { const type = queryValue; const entityType = entity.type.identifier; @@ -405,7 +387,7 @@ class TargetEnum extends ValidEnum { } // Handle mode query. - case TargetSelectorQuery.Mode: { + case "m": { if (!(entity instanceof Player)) throw new SyntaxError( `Mode query can only be used with players.` @@ -442,7 +424,7 @@ class TargetEnum extends ValidEnum { } // Handle level query. - case TargetSelectorQuery.Level: { + case "l": { if (!(entity instanceof Player)) throw new SyntaxError( `Level query can only be used with players.` @@ -461,7 +443,7 @@ class TargetEnum extends ValidEnum { } // Handle minimum level query. - case TargetSelectorQuery.LevelMin: { + case "lm": { if (!(entity instanceof Player)) throw new SyntaxError( `LevelMin query can only be used with players.` @@ -489,6 +471,16 @@ class TargetEnum extends ValidEnum { } return results; } + + /** + * Calculates the Euclidean distance between two points in 3D space. + * @param a The first point. + * @param b The second point. + * @returns The distance between the two points. + */ + private static calculateDistance(a: Vector3f, b: Vector3f): number { + return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2 + (a.z - b.z) ** 2); + } } -export { TargetEnum, TargetSelectorQuery }; +export { TargetEnum };