mirror of
https://github.com/Kvan7/Exiled-Exchange-2.git
synced 2025-12-08 09:05:44 +00:00
217 lines
6.0 KiB
TypeScript
217 lines
6.0 KiB
TypeScript
import { CLIENT_STRINGS as _$ } from "@/assets/data";
|
|
import type { ParsedStat } from "./stat-translations";
|
|
import { ModifierType } from "./modifiers";
|
|
import { removeLinesEnding } from "./Parser";
|
|
|
|
export const SCOURGE_LINE = " (scourge)";
|
|
export const RUNE_LINE = " (rune)";
|
|
export const ADDED_RUNE_LINE = " (added rune)";
|
|
export const ENCHANT_LINE = " (enchant)";
|
|
export const IMPLICIT_LINE = " (implicit)";
|
|
export const DESECRATED_LINE = " (desecrated)";
|
|
const CRAFTED_LINE = " (crafted)";
|
|
const FRACTURED_LINE = " (fractured)";
|
|
|
|
export interface ParsedModifier {
|
|
info: ModifierInfo;
|
|
stats: ParsedStat[];
|
|
}
|
|
|
|
export interface ModifierInfo {
|
|
type: ModifierType;
|
|
generation?: "suffix" | "prefix" | "corrupted" | "eldritch";
|
|
name?: string;
|
|
tier?: number;
|
|
rank?: number;
|
|
tags: string[];
|
|
rollIncr?: number;
|
|
hybridWithRef?: Set<string>;
|
|
}
|
|
|
|
export function parseModInfoLine(
|
|
line: string,
|
|
type: ModifierType,
|
|
): ModifierInfo {
|
|
const [modText, xText2, xText3] = line
|
|
.slice(1, -1)
|
|
.split("\u2014")
|
|
.map((_) => _.trim());
|
|
|
|
let generation: ModifierInfo["generation"];
|
|
let name: ModifierInfo["name"];
|
|
let tier: ModifierInfo["tier"];
|
|
let rank: ModifierInfo["rank"];
|
|
|
|
if (_$.EATER_IMPLICIT.test(modText) || _$.EXARCH_IMPLICIT.test(modText)) {
|
|
const match =
|
|
modText.match(_$.EATER_IMPLICIT) ?? modText.match(_$.EXARCH_IMPLICIT)!;
|
|
generation = "eldritch";
|
|
switch (match.groups!.rank) {
|
|
case _$.ELDRITCH_MOD_R1:
|
|
rank = 1;
|
|
break;
|
|
case _$.ELDRITCH_MOD_R2:
|
|
rank = 2;
|
|
break;
|
|
case _$.ELDRITCH_MOD_R3:
|
|
rank = 3;
|
|
break;
|
|
case _$.ELDRITCH_MOD_R4:
|
|
rank = 4;
|
|
break;
|
|
case _$.ELDRITCH_MOD_R5:
|
|
rank = 5;
|
|
break;
|
|
case _$.ELDRITCH_MOD_R6:
|
|
rank = 6;
|
|
break;
|
|
}
|
|
} else {
|
|
const match = modText.match(_$.MODIFIER_LINE);
|
|
if (!match) {
|
|
throw new Error("Invalid regex for mod info line");
|
|
}
|
|
|
|
switch (match.groups!.type) {
|
|
case _$.PREFIX_MODIFIER:
|
|
case _$.CRAFTED_PREFIX:
|
|
generation = "prefix";
|
|
break;
|
|
case _$.SUFFIX_MODIFIER:
|
|
case _$.CRAFTED_SUFFIX:
|
|
generation = "suffix";
|
|
break;
|
|
case _$.CORRUPTED_IMPLICIT:
|
|
generation = "corrupted";
|
|
break;
|
|
}
|
|
|
|
name = match.groups!.name || undefined;
|
|
tier = Number(match.groups!.tier) || undefined;
|
|
rank = Number(match.groups!.rank) || undefined;
|
|
}
|
|
|
|
let tags: ModifierInfo["tags"];
|
|
let rollIncr: ModifierInfo["rollIncr"];
|
|
{
|
|
const incrText =
|
|
xText3 !== undefined
|
|
? xText3
|
|
: xText2 !== undefined && _$.MODIFIER_INCREASED.test(xText2)
|
|
? xText2
|
|
: undefined;
|
|
|
|
const tagsText =
|
|
xText2 !== undefined && incrText !== xText2 ? xText2 : undefined;
|
|
|
|
tags = tagsText ? tagsText.split(", ") : [];
|
|
rollIncr = incrText
|
|
? Number(_$.MODIFIER_INCREASED.exec(incrText)![1])
|
|
: undefined;
|
|
}
|
|
|
|
return { type, generation, name, tier, rank, tags, rollIncr };
|
|
}
|
|
|
|
export function isModInfoLine(line: string): boolean {
|
|
return line.startsWith("{") && line.endsWith("}");
|
|
}
|
|
|
|
interface GroupedModLines {
|
|
modLine: string;
|
|
statLines: string[];
|
|
}
|
|
|
|
export function* groupLinesByMod(
|
|
lines: string[],
|
|
): Generator<GroupedModLines, void> {
|
|
if (!lines.length || !isModInfoLine(lines[0])) {
|
|
return;
|
|
}
|
|
|
|
let last: GroupedModLines | undefined;
|
|
for (const line of lines) {
|
|
if (!isModInfoLine(line)) {
|
|
last!.statLines.push(line);
|
|
} else {
|
|
if (last) {
|
|
yield last;
|
|
}
|
|
last = { modLine: line, statLines: [] };
|
|
}
|
|
}
|
|
yield last!;
|
|
}
|
|
|
|
export function parseModType(lines: string[]): {
|
|
modType: ModifierType;
|
|
lines: string[];
|
|
} {
|
|
let modType: ModifierType;
|
|
if (lines[0] === _$.VEILED_PREFIX || lines[0] === _$.VEILED_SUFFIX) {
|
|
modType = ModifierType.Veiled;
|
|
} else if (lines.some((line) => line.endsWith(SCOURGE_LINE))) {
|
|
modType = ModifierType.Scourge;
|
|
lines = removeLinesEnding(lines, SCOURGE_LINE);
|
|
} else if (lines.some((line) => line.endsWith(ENCHANT_LINE))) {
|
|
modType = ModifierType.Enchant;
|
|
lines = removeLinesEnding(lines, ENCHANT_LINE);
|
|
} else if (lines.some((line) => line.endsWith(IMPLICIT_LINE))) {
|
|
modType = ModifierType.Implicit;
|
|
lines = removeLinesEnding(lines, IMPLICIT_LINE);
|
|
} else if (lines.some((line) => line.endsWith(FRACTURED_LINE))) {
|
|
modType = ModifierType.Fractured;
|
|
lines = removeLinesEnding(lines, FRACTURED_LINE);
|
|
} else if (lines.some((line) => line.endsWith(CRAFTED_LINE))) {
|
|
modType = ModifierType.Crafted;
|
|
lines = removeLinesEnding(lines, CRAFTED_LINE);
|
|
} else if (lines.some((line) => line.endsWith(RUNE_LINE))) {
|
|
modType = ModifierType.Rune;
|
|
lines = removeLinesEnding(lines, RUNE_LINE);
|
|
} else if (lines.some((line) => line.endsWith(ADDED_RUNE_LINE))) {
|
|
modType = ModifierType.AddedRune;
|
|
lines = removeLinesEnding(lines, ADDED_RUNE_LINE);
|
|
} else if (lines.some((line) => line.endsWith(DESECRATED_LINE))) {
|
|
modType = ModifierType.Desecrated;
|
|
lines = removeLinesEnding(lines, DESECRATED_LINE);
|
|
} else {
|
|
modType = ModifierType.Explicit;
|
|
}
|
|
|
|
return { modType, lines };
|
|
}
|
|
|
|
// stat values internally stored as ints,
|
|
// this is the most common formatter
|
|
const DIV_BY_100 = 2;
|
|
|
|
export function applyIncr(
|
|
mod: ModifierInfo,
|
|
parsed: ParsedStat,
|
|
): ParsedStat | null {
|
|
const { rollIncr } = mod;
|
|
const { roll } = parsed;
|
|
|
|
if (!rollIncr || !roll || roll.unscalable) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
stat: parsed.stat,
|
|
translation: parsed.translation,
|
|
roll: {
|
|
unscalable: roll.unscalable,
|
|
dp: roll.dp,
|
|
value: incrRoll(roll.value, rollIncr, roll.dp ? DIV_BY_100 : 0),
|
|
min: incrRoll(roll.min, rollIncr, roll.dp ? DIV_BY_100 : 0),
|
|
max: incrRoll(roll.max, rollIncr, roll.dp ? DIV_BY_100 : 0),
|
|
},
|
|
};
|
|
}
|
|
|
|
export function incrRoll(value: number, p: number, dp: number): number {
|
|
const res = value + (value * p) / 100;
|
|
const rounding = Math.pow(10, dp);
|
|
return Math.trunc((res + Number.EPSILON) * rounding) / rounding;
|
|
}
|