mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-04-13 16:50:26 -05:00
108 lines
3.7 KiB
TypeScript
108 lines
3.7 KiB
TypeScript
import { abilities } from "~/modules/in-game-lists/abilities";
|
|
import type {
|
|
AbilityWithUnknown,
|
|
BuildAbilitiesTupleWithUnknown,
|
|
} from "~/modules/in-game-lists/types";
|
|
import type { AbilityChunks } from "../analyzer-types";
|
|
|
|
// Reference for Ability Chunks numbers: https://splatoonwiki.org/wiki/Ability_chunk#Splatoon_3
|
|
const MAIN_REQUIRED_ABILITY_CHUNKS_COUNT = 45;
|
|
const PRIMARY_SLOT_ONLY_REQUIRED_ABILITY_CHUNKS_COUNT = 15;
|
|
const SUB_REQUIRED_ABILITY_CHUNKS_COUNT = 10;
|
|
|
|
// Ability Doubler: cost of adding a non-duplicate secondary ability only costs 3 ability chunks
|
|
// Reference: https://splatoonwiki.org/wiki/Splatfest_Tee#Splatoon_3_2
|
|
const SUB_WITH_ABILITY_DOUBLER_REQUIRED_ABILITY_CHUNKS_COUNT = 3;
|
|
|
|
export const ABILITIES_WITHOUT_CHUNKS = new Set(["UNKNOWN", "AD"]);
|
|
|
|
// From a given build, create a map of <Ability, number>, then return it as an Array after sorting by value, descending.
|
|
// The data structure describes the number of Ability chunks required for any given build.
|
|
export function getAbilityChunksMapAsArray(
|
|
build: BuildAbilitiesTupleWithUnknown,
|
|
) {
|
|
const abilityChunksMap: AbilityChunks = new Map<AbilityWithUnknown, number>();
|
|
updateAbilityChunksMap(abilityChunksMap, build);
|
|
|
|
// Sort by value (number, descending) first, then sort by name (string, ascending)
|
|
return Array.from(abilityChunksMap).sort(
|
|
(a, b) => b[1] - a[1] || a[0].localeCompare(b[0]),
|
|
);
|
|
}
|
|
|
|
function updateAbilityChunksMap(
|
|
abilityChunksMap: AbilityChunks,
|
|
build: BuildAbilitiesTupleWithUnknown,
|
|
) {
|
|
let buildIndex = 0;
|
|
|
|
for (const gear of build) {
|
|
let hasAbilityDoubler = false;
|
|
|
|
// Handles the incremental amount of ability chunks required for the same ability for 1 piece of gear
|
|
const abilityChunksMapForGear = new Map<AbilityWithUnknown, number>();
|
|
|
|
for (const [index, selectedAbility] of gear.entries()) {
|
|
if (!selectedAbility) continue;
|
|
if (ABILITIES_WITHOUT_CHUNKS.has(selectedAbility)) {
|
|
// Detect the presence of Ability Doubler in the Clothing gear slot
|
|
if (selectedAbility === "AD" && buildIndex === 1) {
|
|
hasAbilityDoubler = true;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Ability is in main slot
|
|
if (index === 0) {
|
|
const primarySlotOnlyAbilityRef = abilities.filter(
|
|
(a) =>
|
|
a.name === selectedAbility &&
|
|
a.abilityChunkTypesRequired.length > 0,
|
|
);
|
|
|
|
// Extra processing is required for Main abilities that are primary slot-only abilities,
|
|
// as they are comprised of 3 stackable ability chunks at a lower ability chunk count than usual.
|
|
if (primarySlotOnlyAbilityRef.length === 1) {
|
|
const primaryAbility = primarySlotOnlyAbilityRef[0];
|
|
if (!primaryAbility) continue;
|
|
|
|
for (const ability of primaryAbility.abilityChunkTypesRequired) {
|
|
abilityChunksMap.set(
|
|
ability,
|
|
(abilityChunksMap.get(ability) ?? 0) +
|
|
PRIMARY_SLOT_ONLY_REQUIRED_ABILITY_CHUNKS_COUNT,
|
|
);
|
|
}
|
|
} else {
|
|
abilityChunksMap.set(
|
|
selectedAbility,
|
|
(abilityChunksMap.get(selectedAbility) ?? 0) +
|
|
MAIN_REQUIRED_ABILITY_CHUNKS_COUNT,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Ability is in a sub slot
|
|
else {
|
|
// 1 Ability in sub slot = 10 chunks, 2 abilities = 20 chunks, 3 abilities = 30 chunks
|
|
// Also handle the edge case for when the piece of gear has Ability Doubler (3/6/9 chunks for 1/2/3 of the same ability)
|
|
abilityChunksMapForGear.set(
|
|
selectedAbility,
|
|
(abilityChunksMapForGear.get(selectedAbility) ?? 0) +
|
|
(hasAbilityDoubler
|
|
? SUB_WITH_ABILITY_DOUBLER_REQUIRED_ABILITY_CHUNKS_COUNT
|
|
: SUB_REQUIRED_ABILITY_CHUNKS_COUNT),
|
|
);
|
|
|
|
abilityChunksMap.set(
|
|
selectedAbility,
|
|
(abilityChunksMap.get(selectedAbility) ?? 0) +
|
|
(abilityChunksMapForGear.get(selectedAbility) ?? 0),
|
|
);
|
|
}
|
|
}
|
|
|
|
buildIndex += 1;
|
|
}
|
|
}
|