From 0d8ef6a7ac82b951a5a58484a6fb5005bc995181 Mon Sep 17 00:00:00 2001 From: Tobias Berger Date: Sat, 28 Dec 2024 17:37:22 +0100 Subject: [PATCH] Initial Commit --- .envrc | 1 + .gitattributes | 1 + .gitignore | 5 ++ bun.lockb | Bin 0 -> 2693 bytes data.json.sample | 25 ++++++ flake.lock | 48 ++++++++++ flake.nix | 103 ++++++++++++++++++++++ main.ts | 223 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 22 +++++ treefmt.nix | 8 ++ 10 files changed, 436 insertions(+) create mode 100644 .envrc create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100755 bun.lockb create mode 100644 data.json.sample create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 main.ts create mode 100644 package.json create mode 100644 treefmt.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..81c05ed --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.lockb binary diff=lockb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2ea23d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.direnv +node_modules +data.json +out.json +result diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..1ad6968481c7719efe08076c0068edc98214b915 GIT binary patch literal 2693 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_p*}GcF%* z4Ep-1^X(0#F0T6U)vL4(n2#OQz433!?uj!VUi``lR0ITU5Q>2Vjc$PQ^I!_Vd?5yg z1|VkO0@6W18fZwvd?3vYq@Mt39w0ppD*lu)Yx@(yZSzjCY3}tlX#7(BCjD%O&}*fM z`cb`VTWu{l^KFj33Y@ij@#z_r^Rq5xG6MBc0zke70U01R1G)ob9tiURF);w9KN_gm z8cDw@F`9vDVER`9*;YXPB0w4#9wgiU0jS3jsGk)`O93&6CI-OlHw1dx1gIYrqaeM! z#At@9frS}}2AK!KEIt{}&JyxOZ z$aHLi|E{2oSM1HZQZo{JL}uM7S#rqiLUFy_>XHn_K!c-<6Au^L_0PyOVYZvbT3#5X zJqyWPP<+7vi&{XB_49=#PF$-33nwp<4{Hz!%P@X>)cTD5!>?r}tIbzzRD2dN)$go| z{@#`uk(I15`W-c!CMkY@xZhhe?N!whBy(Z$4>Fr&(P8_>gkYN$?BPeB_`Q5y#d1;U z^wC#6YV~vM)Go+d^I6}#m0Ol8J0<(a5$!5z7Qq7D=^?M*yZC>};Swm=J7EIQ+@S)7 zkW;A`q#K~+{t+n6Wm8;~te2TrT#}fRqX(;W^g@bKQ|%Ot3>1npt5WmRbQDY!5_2-s z^YYVxRRjaWzyA;bVuRAp2dHT*>>v?fJz`U8Y-9)2gk3whd;#SXSUF?>)#pKkeo(xD z^nvUJnE}cdAi4soR|Hz+aoI#0nZ=e`0KH)bcZVUu8?gEgR*!Ko#u@4Xp$P-5287jY zOpG82V{--uSRDwf+n5;ZEc8tE3=J4ywI!?;1j-m0=ouR6S-|Q~SiK08G1W6N(=)-M zM+@i=5Qu~N0Y;<7QgLc-S!$7xvA;Oy$aBp@%O?nG$7XV`h z7$SyP0;Ie$FRvssr&zZ ({ + x: prev.x + cur.x, + y: prev.y + cur.y, + z: prev.z + cur.z, + })); + return { + x: Math.round(sum.x / locations.length), + y: Math.round(sum.y / locations.length), + z: Math.round(sum.z / locations.length), + }; +} + +function groupLocationItems( + items: T[], + itemsAreSimilar: (valA: T, valB: T) => boolean, + getClusterName: (cluster: T[]) => string, + getClusterIcon: (cluster: T[]) => string, +) { + let clusterId = 0; + for (let rootIdx = 0; rootIdx < items.length; rootIdx++) { + const clusterRoot = items[rootIdx]; + if (clusterRoot.location.clusterId === undefined) { + clusterRoot.location.clusterId = clusterId++; + } + + for (let idx = rootIdx + 1; idx < items.length; idx++) { + if ( + itemsAreSimilar(clusterRoot, items[idx]) + ) { + let originalClusterId = items[idx].location.clusterId; + if ( + originalClusterId !== undefined && + originalClusterId !== clusterRoot.location.clusterId + ) { + for ( + const itemWithOriginalClusterId of items.filter((item) => + item.location.clusterId === originalClusterId + ) + ) { + itemWithOriginalClusterId.location.clusterId = + clusterRoot.location.clusterId; + } + } else { + items[idx].location.clusterId = clusterRoot.location.clusterId; + } + } + } + } + + const clusteredItems: Record = {}; + for (const item of items) { + const groupByClusterid = item.location.clusterId; + if (groupByClusterid === undefined) { + throw new Error("clusterId should not be undefined here"); + } + + if (clusteredItems[groupByClusterid] === undefined) { + clusteredItems[groupByClusterid] = []; + } + + clusteredItems[groupByClusterid].push(item); + } + const clusterPoints: Record< + number, + { + name: string; + icon: string; + color: "#ffffffff"; + visibility: "default"; + location: MapLocation; + } + > = {}; + for (const [clusterId, cluster] of Object.entries(clusteredItems)) { + clusterPoints[clusterId] = { + name: getClusterName(cluster), + color: "#ffffffff", + icon: getClusterIcon(cluster), + visibility: "default", + location: middle( + ...cluster.map((item) => item.location), + ), + }; + } + return clusterPoints; +} + +function professionNodeLocationsAreSimilar( + nodeA: ProfessionNodeLocation, + nodeB: ProfessionNodeLocation, +): boolean { + if ( + nodeA.materialType !== nodeB.materialType || + nodeA.sourceMaterial.name !== nodeB.sourceMaterial.name + ) return false; + + const MAX_DISTANCE = nodeA.professionType === "woodcutting" ? 64 : 16; + return getDistance(nodeA.location, nodeB.location) <= MAX_DISTANCE; +} +function professionNodeLocationsClusterName( + cluster: ProfessionNodeLocation[], +): string { + if ( + cluster.reduce( + (prev, cur) => prev === cur.name ? prev : undefined, + cluster[0].name as string | undefined, + ) === undefined + ) { + console.error( + "Can't find label", + cluster, + ); + } + return cluster[0].name.replace("Node", "") + "x" + cluster.length; +} +function professionNodeLocationsClusterIcon( + cluster: ProfessionNodeLocation[], +): string { + if ( + cluster.reduce( + (prev, cur) => prev === cur.professionType ? prev : undefined, + cluster[0].professionType as string | undefined, + ) === undefined + ) { + console.error( + "Can't find icon", + cluster, + ); + } + return cluster[0].professionType; +} + +async function main() { + const dataPath = Bun.argv[2]; + if (dataPath === undefined) { + console.error("Data file path not specified."); + console.log( + "Usage: " + Bun.file(Bun.argv[1]).name + + " /.../.minecraft/wynntils/storage/{uuid}.data.json", + ); + return; + } + console.debug(Bun.argv[2]); + const data = JSON.parse(await (Bun.file(dataPath)).text()); + const { + professionNodeLocations, + }: { + professionNodeLocations: ProfessionNodeLocation[]; + } = data["manager.crowdSourcedData.collectedData"].data.version210Beta2; + + const clusteredProfessionNodeLocations = groupLocationItems( + professionNodeLocations, + professionNodeLocationsAreSimilar, + professionNodeLocationsClusterName, + professionNodeLocationsClusterIcon, + ); + Bun.write( + "out.json", + JSON.stringify(Object.values(clusteredProfessionNodeLocations), null, 2), + ); +} + +main(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..30cbe12 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "wynntils-waypoints", + "version": "1.0.0", + "description": "Turn locally gathered crowdsource data into waypoints for Wynntils", + "main": "main.ts", + "scripts": {}, + "repository": { + "type": "git", + "url": "forgejo@git.tobot.dev:toby/wynntils-waypoints" + }, + "author": { + "github": "Toby222", + "githubId": 14962962, + "name": "Toby", + "email": "wynntils@tobot.dev", + "url": "https://tobot.dev/" + }, + "license": "CC0-1.0", + "devDependencies": { + "@types/bun": "^1.1.14" + } +} diff --git a/treefmt.nix b/treefmt.nix new file mode 100644 index 0000000..15e8bd2 --- /dev/null +++ b/treefmt.nix @@ -0,0 +1,8 @@ +{ ... }: +{ + projectRootFile = "flake.nix"; + programs = { + nixfmt.enable = true; + deno.enable = true; + }; +}