Initial Commit
This commit is contained in:
commit
0d8ef6a7ac
10 changed files with 436 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
@ -0,0 +1 @@
|
|||
use flake
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.lockb binary diff=lockb
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.direnv
|
||||
node_modules
|
||||
data.json
|
||||
out.json
|
||||
result
|
BIN
bun.lockb
Executable file
BIN
bun.lockb
Executable file
Binary file not shown.
25
data.json.sample
Normal file
25
data.json.sample
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"manager.crowdSourcedData.collectedData": {
|
||||
"data": {
|
||||
"version210Beta2": {
|
||||
"professionNodeLocations": [
|
||||
{
|
||||
"sourceMaterial": {
|
||||
"name": "Acacia",
|
||||
"level": 30
|
||||
},
|
||||
"materialType": "log",
|
||||
"professionType": "woodcutting",
|
||||
"label": "§6Acacia\n§a✔§f Ⓒ§7 Woodcutting Lv. Min: §f30\n\n§8Left-Click for Wood\nRight-Click for Paper",
|
||||
"name": "Acacia Node",
|
||||
"location": {
|
||||
"x": 567,
|
||||
"y": 71,
|
||||
"z": -1761
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
48
flake.lock
Normal file
48
flake.lock
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1735291276,
|
||||
"narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "634fd46801442d760e09493a794c4f15db2d0cbb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1735135567,
|
||||
"narHash": "sha256-8T3K5amndEavxnludPyfj3Z1IkcFdRpR23q+T0BVeZE=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "9e09d30a644c57257715902efbb3adc56c79cf28",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
103
flake.nix
Normal file
103
flake.nix
Normal file
|
@ -0,0 +1,103 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
|
||||
treefmt-nix = {
|
||||
url = "github:numtide/treefmt-nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
treefmt-nix,
|
||||
}:
|
||||
let
|
||||
# System types to support.
|
||||
supportedSystems = [
|
||||
"x86_64-linux"
|
||||
"x86_64-darwin"
|
||||
"aarch64-linux"
|
||||
"aarch64-darwin"
|
||||
];
|
||||
|
||||
forEachSupportedSystem =
|
||||
f:
|
||||
nixpkgs.lib.genAttrs supportedSystems (
|
||||
system:
|
||||
f ({
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ self.overlays.default ];
|
||||
};
|
||||
})
|
||||
);
|
||||
in
|
||||
{
|
||||
formatter = forEachSupportedSystem (
|
||||
{ pkgs, ... }: (treefmt-nix.lib.evalModule pkgs ./treefmt.nix).config.build.wrapper
|
||||
);
|
||||
devShells = forEachSupportedSystem (
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
default = pkgs.mkShell {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
bun
|
||||
typescript-language-server
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
packages = forEachSupportedSystem (
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
default = pkgs.wynntils-waypoints;
|
||||
}
|
||||
);
|
||||
overlays.default = final: prev: {
|
||||
wynntils-waypoints =
|
||||
with final;
|
||||
stdenv.mkDerivation (
|
||||
let
|
||||
packageJSON = builtins.fromJSON (builtins.readFile ./package.json);
|
||||
in
|
||||
{
|
||||
pname = "wynntils-waypoints";
|
||||
inherit (packageJSON) version;
|
||||
|
||||
meta = {
|
||||
inherit (packageJSON) description;
|
||||
platforms = supportedSystems;
|
||||
license = lib.licensesSpdx.${packageJSON.license};
|
||||
maintainers = [
|
||||
packageJSON.author
|
||||
];
|
||||
};
|
||||
|
||||
nativeBuildInputs = [
|
||||
bun
|
||||
];
|
||||
|
||||
src = ./.;
|
||||
|
||||
buildPhase = ''
|
||||
bun build ./main.ts --minify --sourcemap --bytecode --compile --outfile wynntils-waypoints
|
||||
'';
|
||||
dontStrip = true;
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin/
|
||||
install ./wynntils-waypoints $out/bin/
|
||||
'';
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
nixosModules.wynntils-waypoints =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
nixpkgs.overlays = [ self.overlay ];
|
||||
};
|
||||
};
|
||||
}
|
223
main.ts
Normal file
223
main.ts
Normal file
|
@ -0,0 +1,223 @@
|
|||
type MapLocation = {
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
clusterId?: number;
|
||||
};
|
||||
|
||||
type NPCLocation = {
|
||||
icon: string | null;
|
||||
description: string | null;
|
||||
label: string;
|
||||
name: string;
|
||||
location: MapLocation;
|
||||
};
|
||||
|
||||
type ProfessionNodeLocation =
|
||||
& {
|
||||
sourceMaterial: {
|
||||
name: string;
|
||||
level: number;
|
||||
};
|
||||
label: string;
|
||||
name: string;
|
||||
location: MapLocation;
|
||||
}
|
||||
& ({
|
||||
materialType: "ore";
|
||||
professionType: "mining";
|
||||
} | {
|
||||
materialType: "crop";
|
||||
professionType: "farming";
|
||||
} | {
|
||||
materialType: "log";
|
||||
professionType: "woodcutting";
|
||||
} | {
|
||||
materialType: "fish";
|
||||
professionType: "fishing";
|
||||
});
|
||||
|
||||
type ProfessionCraftingStationLocation = {
|
||||
name: string;
|
||||
label: string;
|
||||
professionType:
|
||||
| "alchemism"
|
||||
| "armouring"
|
||||
| "cooking"
|
||||
| "jeweling"
|
||||
| "scribing"
|
||||
| "tailoring"
|
||||
| "weaponsmithing"
|
||||
| "woodworking";
|
||||
location: MapLocation;
|
||||
};
|
||||
|
||||
function getDistance(locationA: MapLocation, locationB: MapLocation): number {
|
||||
return Math.sqrt(
|
||||
(locationB.x - locationA.x) ** 2 + (locationB.y - locationA.y) ** 2 +
|
||||
(locationB.z - locationA.z) ** 2,
|
||||
);
|
||||
}
|
||||
function middle(...locations: MapLocation[]): MapLocation {
|
||||
const sum = locations.reduce((prev, cur) => ({
|
||||
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<T extends { location: MapLocation }>(
|
||||
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<number, T[]> = {};
|
||||
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();
|
22
package.json
Normal file
22
package.json
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
8
treefmt.nix
Normal file
8
treefmt.nix
Normal file
|
@ -0,0 +1,8 @@
|
|||
{ ... }:
|
||||
{
|
||||
projectRootFile = "flake.nix";
|
||||
programs = {
|
||||
nixfmt.enable = true;
|
||||
deno.enable = true;
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue