sendou.ink/app/features/build-stats/build-stats-utils.ts
Kalle 8dc92140fc
Some checks failed
E2E Tests / e2e (push) Has been cancelled
Tests and checks on push / run-checks-and-tests (push) Has been cancelled
Updates translation progress / update-translation-progress-issue (push) Has been cancelled
Optimize builds loaders (#3076)
2026-05-17 16:21:13 +03:00

119 lines
3.3 KiB
TypeScript

import { abilities } from "~/modules/in-game-lists/abilities";
import type { Ability } from "~/modules/in-game-lists/types";
import invariant from "~/utils/invariant";
import { roundToNDecimalPlaces } from "~/utils/number";
import { MAX_AP } from "../build-analyzer/analyzer-constants";
import { isStackableAbility } from "../build-analyzer/core/utils";
import type {
AverageAbilityPointsResult,
PopularBuildsRow,
} from "../builds/BuildRepository.server";
const toBuildsCount = (counts: AverageAbilityPointsResult[]) =>
counts.reduce((acc, cur) => acc + cur.abilityPointsSum, 0) / MAX_AP;
const toAbilityPoints = (
abilities: AverageAbilityPointsResult[],
ability: Ability,
) =>
abilities.find((current) => current.ability === ability)?.abilityPointsSum ??
0;
export function abilityPointCountsToAverages({
allAbilities,
weaponAbilities,
}: {
allAbilities: AverageAbilityPointsResult[];
weaponAbilities: AverageAbilityPointsResult[];
}) {
const allBuildsCount = toBuildsCount(allAbilities);
const weaponBuildsCount = toBuildsCount(weaponAbilities);
invariant(
allBuildsCount > weaponBuildsCount,
"allBuildsCount < weaponBuildsCount",
);
const mainOnlyAbilities = abilities
.filter((ability) => ability.type !== "STACKABLE")
.map((ability) => {
const allBuildsAbilityPoints = toAbilityPoints(
allAbilities,
ability.name,
);
const weaponBuildsAbilityPoints = toAbilityPoints(
weaponAbilities,
ability.name,
);
// main only abilities always take place of 10AP
const allBuildsWithTheAbility = allBuildsAbilityPoints / 10;
const weaponBuildsWithTheAbility = weaponBuildsAbilityPoints / 10;
return {
name: ability.name,
percentage: {
all: roundToNDecimalPlaces(
(allBuildsWithTheAbility / allBuildsCount) * 100,
),
weapon: roundToNDecimalPlaces(
(weaponBuildsWithTheAbility / weaponBuildsCount) * 100,
),
},
};
})
.sort((a, b) => b.percentage.weapon - a.percentage.weapon);
const stackableAbilities = abilities
.filter((ability) => ability.type === "STACKABLE")
.map((ability) => {
const allBuildsAbilityPoints = toAbilityPoints(
allAbilities,
ability.name,
);
const weaponBuildsAbilityPoints = toAbilityPoints(
weaponAbilities,
ability.name,
);
return {
name: ability.name,
apAverage: {
all: roundToNDecimalPlaces(allBuildsAbilityPoints / allBuildsCount),
weapon: roundToNDecimalPlaces(
weaponBuildsAbilityPoints / weaponBuildsCount,
),
},
};
})
.sort((a, b) => b.apAverage.weapon - a.apAverage.weapon);
return {
mainOnlyAbilities,
stackableAbilities,
weaponBuildsCount,
};
}
// ---
export function popularBuilds(rows: Array<PopularBuildsRow>) {
let previousCount: number;
return rows.map(({ abilitiesSignature, count }) => {
const abilities = abilitiesSignature.split(",").map((serializedAbility) => {
const [ability, points] = serializedAbility.split("_");
invariant(ability, "ability is not defined");
invariant(points, "count is not defined");
return {
ability: ability as Ability,
count: isStackableAbility(ability as Ability)
? Number(points)
: undefined,
};
});
const displayCount = previousCount === count ? null : count;
previousCount = count;
return { abilities, count: displayCount, id: abilitiesSignature };
});
}