From 297cc561a5eed5d91e3759df0023e63acecd6dd1 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 21 Jun 2024 08:59:25 +0100 Subject: [PATCH] 3 things (desc) - put invitable function into utils, accepting base town and nation so it can be reused for squaremap - add test for Towns.get on squaremap - nicer exports and imports in some areas --- src/api/dynmap/Towns.ts | 42 +++++++++++------------ src/api/squaremap/Towns.ts | 5 +-- src/types/nation.ts | 18 ++++++---- src/utils/functions.ts | 63 +++++++++++++---------------------- tests/squaremap/towns.test.ts | 15 +++++---- 5 files changed, 66 insertions(+), 77 deletions(-) diff --git a/src/api/dynmap/Towns.ts b/src/api/dynmap/Towns.ts index c27c4a3..1232d38 100644 --- a/src/api/dynmap/Towns.ts +++ b/src/api/dynmap/Towns.ts @@ -1,6 +1,11 @@ import striptags from 'striptags' -import * as fn from '../../utils/functions.js' +import { + formatString, asBool, + calcArea, hypot, range, + getExisting, + isInvitable +} from '../../utils/functions.js' import { FetchError, InvalidError, NotFoundError } from "../../utils/errors.js" import { Nation, Town } from '../../types.js' @@ -35,7 +40,7 @@ class Towns implements EntityApi { const towns = await this.all() if (!towns) throw new FetchError('Error fetching towns! Please try again.') - const existing = fn.getExisting(towns, townList, 'name') + const existing = getExisting(towns, townList, 'name') return existing.length > 1 ? Promise.all(existing) : Promise.resolve(existing[0]) } @@ -67,7 +72,7 @@ class Towns implements EntityApi { split = (split[2] ?? split[1]).slice(0, -1) const residents = info[2].slice(9).split(", ") - const capital = fn.asBool(info[9]?.slice(9)) + const capital = asBool(info[9]?.slice(9)) let nationName = split let wikiPage = null @@ -82,25 +87,25 @@ class Towns implements EntityApi { const home = nationName != "" ? markerset.markers[`${town.label}__home`] : null const [townX, townZ] = [town.x, town.z] - const area = fn.calcArea(townX, townZ, townX.length) + const area = calcArea(townX, townZ, townX.length) const currentTown: Town = { - name: fn.formatString(town.label, removeAccents), - nation: nationName == "" ? "No Nation" : fn.formatString(nationName.trim(), removeAccents), + name: formatString(town.label, removeAccents), + nation: nationName == "" ? "No Nation" : formatString(nationName.trim(), removeAccents), mayor, area, - x: home?.x ?? fn.range(townX), - z: home?.z ?? fn.range(townZ), + x: home?.x ?? range(townX), + z: home?.z ?? range(townZ), bounds: { x: townX.map(num => Math.round(num)), z: townZ.map(num => Math.round(num)) }, residents: residents, flags: { - pvp: fn.asBool(info[4]?.slice(5)), - mobs: fn.asBool(info[5]?.slice(6)), - public: fn.asBool(info[6]?.slice(8)), - explosion: fn.asBool(info[7]?.slice(11)), - fire: fn.asBool(info[8]?.slice(6)), + pvp: asBool(info[4]?.slice(5)), + mobs: asBool(info[5]?.slice(6)), + public: asBool(info[6]?.slice(8)), + explosion: asBool(info[7]?.slice(11)), + fire: asBool(info[8]?.slice(6)), capital: capital }, colours: { @@ -154,9 +159,7 @@ class Towns implements EntityApi { if (!towns) return null } - return towns.filter(t => - fn.hypot(t.x, [xInput, xRadius]) && - fn.hypot(t.z, [zInput, zRadius])) + return towns.filter(t => hypot(t.x, [xInput, xRadius]) && hypot(t.z, [zInput, zRadius])) } readonly invitable = async (nationName: string, includeBelonging = false) => { @@ -166,15 +169,10 @@ class Towns implements EntityApi { const towns = await this.all() if (!towns) throw new FetchError('An error occurred fetching towns!') - return towns.filter(t => invitable(t, nation, this.map.inviteRange, includeBelonging)) + return towns.filter(t => isInvitable(t, nation, this.map.inviteRange, includeBelonging)) } } -const invitable = (town: Town, nation: Nation, range: number, belonging: boolean) => { - const sqr = fn.sqr(town, nation.capital, range) && town.nation != nation.name - return belonging ? sqr : sqr && town.nation == "No Nation" -} - export { Towns, Towns as default diff --git a/src/api/squaremap/Towns.ts b/src/api/squaremap/Towns.ts index 6b3ed42..42c6f7e 100644 --- a/src/api/squaremap/Towns.ts +++ b/src/api/squaremap/Towns.ts @@ -1,10 +1,11 @@ import Squaremap from "./Squaremap.js" import { EntityApi } from "../../helpers/EntityApi.js" -import { FetchError, NotFoundError } from "../../utils/errors.js" import { SquaremapTown } from "../../types.js" import { parseTowns } from "./parser.js" -import { getExisting } from "src/utils/functions.js" + +import { FetchError, NotFoundError } from "../../utils/errors.js" +import { getExisting } from "../../utils/functions.js" class Towns implements EntityApi { #map: Squaremap diff --git a/src/types/nation.ts b/src/types/nation.ts index 8875538..cdc0d3d 100644 --- a/src/types/nation.ts +++ b/src/types/nation.ts @@ -4,17 +4,21 @@ import { RawEntityStats, RawEntityStatus } from "../types.js" +import {Prettify} from "./util.js" -export type Nation = { +export type BaseNation = { name: string - uuid?: string - board?: string - wiki?: string king: string towns: string[] residents: string[] area: number capital: NationCapital +} + +export type Nation = Prettify -export type NationCapital = Point2D & { +export type NationCapital = Prettify \ No newline at end of file diff --git a/src/utils/functions.ts b/src/utils/functions.ts index c921d94..999d76d 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -1,29 +1,29 @@ import striptags from 'striptags' import { removeDiacritics } from "modern-diacritics" -import { RawPlayer, Player, Town, Point2D } from '../types.js' +import { RawPlayer, Player, Town, Point2D, BaseTown, BaseNation } from '../types.js' import { NotFound } from './errors.js' -const removeDuplicates = (arr: T[]) => [...new Set(arr)] +export const removeDuplicates = (arr: T[]) => [...new Set(arr)] -const stripInvalidChars = (str: string) => { +export const stripInvalidChars = (str: string) => { return str.replace(/((")|(&\w[a-z0-9].|&[0-9kmnola-z]));/g, "") .replace(/"|'/g, '"') } -function formatString(str: string, removeAccents = false) { +export function formatString(str: string, removeAccents = false) { str = stripInvalidChars(str) return removeAccents ? removeDiacritics(str) : str } -function editPlayerProps(props: RawPlayer[]) { +export function editPlayerProps(props: RawPlayer[]) { if (!props) throw new ReferenceError("Can't edit player props! The parameter is null or undefined.") if (props instanceof Array) return props.length > 0 ? props.map(p => editPlayerProp(p)) : [] throw new TypeError("Can't edit player props! Type isn't of object or array.") } -const editPlayerProp = (player: RawPlayer): Player => ({ +export const editPlayerProp = (player: RawPlayer): Player => ({ name: player.account, nickname: striptags(player.name), x: player.x, y: player.y, z: player.z, @@ -34,7 +34,7 @@ const editPlayerProp = (player: RawPlayer): Player => ({ export const roundToNearest16 = (num: number) => Math.round(num / 16) * 16 -function calcArea(X: number[], Z: number[], numPoints: number, divisor = 256) { +export function calcArea(X: number[], Z: number[], numPoints: number, divisor = 256) { let i = 0, j = numPoints - 1, area = 0 for (; i < numPoints; i++) { @@ -45,12 +45,12 @@ function calcArea(X: number[], Z: number[], numPoints: number, divisor = 256) { return Math.abs(area / 2) / divisor } -function averageNationPos(name: string, towns: Town[]) { +export function averageNationPos(name: string, towns: Town[]) { const nationTowns = towns.filter(t => t.nation?.toLowerCase() == name.toLowerCase()) return getAveragePos(nationTowns) } -function getAveragePos(arr: Point2D[]) { +export function getAveragePos(arr: Point2D[]) { if (!arr) return "Error getting average position: 'towns' parameter not defined!" return { @@ -59,43 +59,43 @@ function getAveragePos(arr: Point2D[]) { } } -const safeParseInt = (num: number | string) => typeof num === "number" ? num : parseInt(num) -const asBool = (str: string) => str == "true" -const range = (args: number[]) => Math.round((Math.max(...args) + Math.min(...args)) / 2) +export const safeParseInt = (num: number | string) => typeof num === "number" ? num : parseInt(num) +export const asBool = (str: string) => str == "true" +export const range = (args: number[]) => Math.round((Math.max(...args) + Math.min(...args)) / 2) -const sqr = (a: Point2D, b: Point2D, range: number) => Math.hypot( +export const sqr = (a: Point2D, b: Point2D, range: number) => Math.hypot( safeParseInt(a.x) - safeParseInt(b.x), safeParseInt(a.z) - safeParseInt(b.z) ) <= range -const average = (nums: Point2D[], key: keyof Point2D) => { +export const average = (nums: Point2D[], key: keyof Point2D) => { const sum = nums.map(obj => obj[key]).reduce((a, b) => safeParseInt(a) + safeParseInt(b)) return safeParseInt(sum) / nums.length } // TODO: Ensure this is returning T[] and not a string of names. -const getExisting = (a1: T[], a2: string[], key: keyof T) => { +export const getExisting = (a1: T[], a2: string[], key: keyof T) => { return a2.flat().map(x => a1.find(e => x?.toLowerCase() == String(e[key])?.toLowerCase() ) ?? NotFound(x)) } -const hypot = (num: number, args: [input: number, radius: number]) => { +export const hypot = (num: number, args: [input: number, radius: number]) => { const [input, radius] = args return num <= (input + radius) && num >= (input - radius) } -const manhattan = (x1: number, z1: number, x2: number, z2: number) => +export const manhattan = (x1: number, z1: number, x2: number, z2: number) => Math.abs(x2 - x1) + Math.abs(z2 - z1) -const euclidean = (x1: number, z1: number, x2: number, z2: number) => +export const euclidean = (x1: number, z1: number, x2: number, z2: number) => Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(z2 - z1, 2)) // Used as alternative to `!` as it considers 0 to be falsy. -const strictFalsy = (val: any) => val === undefined || val === null +export const strictFalsy = (val: any) => val === undefined || val === null -const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' -function genRandomString(maxAmount = 20) { +export const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' +export function genRandomString(maxAmount = 20) { let token = '' const len = validChars.length @@ -107,22 +107,7 @@ function genRandomString(maxAmount = 20) { return token } -export { - sqr, - range, - hypot, - asBool, - getExisting, - formatString, - editPlayerProps, - calcArea, - removeDuplicates, - stripInvalidChars, - getAveragePos, - averageNationPos, - euclidean, - manhattan, - strictFalsy, - genRandomString, - safeParseInt +export const isInvitable = (town: BaseTown, nation: BaseNation, range: number, belonging: boolean) => { + const val = sqr(town, nation.capital, range) && town.nation != nation.name + return belonging ? val : val && town.nation == "No Nation" } \ No newline at end of file diff --git a/tests/squaremap/towns.test.ts b/tests/squaremap/towns.test.ts index af4882e..4bc77ef 100644 --- a/tests/squaremap/towns.test.ts +++ b/tests/squaremap/towns.test.ts @@ -1,6 +1,7 @@ import { describe, it, - assertType + assertType, + expect } from 'vitest' import { SquaremapTown } from '../../src/types' @@ -11,13 +12,13 @@ describe('[Squaremap/Aurora] Towns', () => { assertType(towns) }) - // it('can get single town', async () => { - // const town = await globalThis.Nova.Towns.get('kraftier') + it('can get single town', async () => { + const town = await globalThis.Nova.Towns.get('kraftier') - // expect(town).toBeTruthy() - // expect(town).toBeDefined() - // assertType(town) - // }) + expect(town).toBeTruthy() + expect(town).toBeDefined() + assertType(town) + }) // it('can get towns invitable to specified nation', async () => { // const invitableTowns = await globalThis.Aurora.Towns.invitable('sudan')