Skip to content

Commit

Permalink
3 things (desc)
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
Owen3H committed Jun 21, 2024
1 parent 2df2ff3 commit 297cc56
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 77 deletions.
42 changes: 20 additions & 22 deletions src/api/dynmap/Towns.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -35,7 +40,7 @@ class Towns implements EntityApi<Town | NotFoundError> {
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])
}

Expand Down Expand Up @@ -67,7 +72,7 @@ class Towns implements EntityApi<Town | NotFoundError> {
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
Expand All @@ -82,25 +87,25 @@ class Towns implements EntityApi<Town | NotFoundError> {

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: {
Expand Down Expand Up @@ -154,9 +159,7 @@ class Towns implements EntityApi<Town | NotFoundError> {
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) => {
Expand All @@ -166,15 +169,10 @@ class Towns implements EntityApi<Town | NotFoundError> {
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
Expand Down
5 changes: 3 additions & 2 deletions src/api/squaremap/Towns.ts
Original file line number Diff line number Diff line change
@@ -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<SquaremapTown | NotFoundError> {
#map: Squaremap
Expand Down
18 changes: 11 additions & 7 deletions src/types/nation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,30 @@ 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<BaseNation & {
uuid?: string
board?: string
wiki?: string
status?: RawEntityStatus
stats?: RawEntityStats
spawn?: RawEntitySpawn
ranks?: { [key: string]: string[] }
allies?: string[]
enemies?: string[]
mapColorHexCode?: string
}
}>

export type NationCapital = Point2D & {
export type NationCapital = Prettify<Point2D & {
name: string
}
}>
63 changes: 24 additions & 39 deletions src/utils/functions.ts
Original file line number Diff line number Diff line change
@@ -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 = <T>(arr: T[]) => [...new Set(arr)]
export const removeDuplicates = <T>(arr: T[]) => [...new Set(arr)]

const stripInvalidChars = (str: string) => {
export const stripInvalidChars = (str: string) => {
return str.replace(/((&#34)|(&\w[a-z0-9].|&[0-9kmnola-z]));/g, "")
.replace(/&quot;|&#039;/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,
Expand All @@ -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++) {
Expand All @@ -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 {
Expand All @@ -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 = <T>(a1: T[], a2: string[], key: keyof T) => {
export const getExisting = <T>(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

Expand All @@ -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"
}
15 changes: 8 additions & 7 deletions tests/squaremap/towns.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
describe, it,
assertType
assertType,
expect
} from 'vitest'

import { SquaremapTown } from '../../src/types'
Expand All @@ -11,13 +12,13 @@ describe('[Squaremap/Aurora] Towns', () => {
assertType<SquaremapTown[]>(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<SquaremapTown | SquaremapTown[]>(town)
// })
expect(town).toBeTruthy()
expect(town).toBeDefined()
assertType<SquaremapTown | SquaremapTown[]>(town)
})

// it('can get towns invitable to specified nation', async () => {
// const invitableTowns = await globalThis.Aurora.Towns.invitable('sudan')
Expand Down

0 comments on commit 297cc56

Please sign in to comment.