diff --git a/zenno/src/lib/data/affinity.ts b/zenno/src/lib/data/affinity.ts new file mode 100644 index 0000000..8146ed6 --- /dev/null +++ b/zenno/src/lib/data/affinity.ts @@ -0,0 +1,35 @@ +import globalJSON from '../../../../global/affinity.json'; + +/** + * Precomputed character pair and trio affinity. + */ +export interface Affinity { + /** + * First character in the relation. + */ + chara_a: number; + /** + * Second character in the relation. + * chara_a < chara_b is an invariant. + */ + chara_b: number; + /** + * Third character in the relation, if it is a trio relation. + * If defined, chara_b < chara_c is an invariant. + */ + chara_c?: number; + /** + * Total base compatibility between characters in the relation. + */ + affinity: number; +} + +export const affinity = { + global: globalJSON as Affinity[], +} as const; + +export function lookup(aff: Affinity[], chara_a: number, chara_b: number, chara_c?: number): number { + const [a, b, c] = [chara_a, chara_b, chara_c ?? Infinity].sort((a, b) => a - b); + const r = aff.find((v) => v.chara_a === a && v.chara_b === b && (v.chara_c ?? Infinity) === c); + return r?.affinity ?? 0; +} diff --git a/zenno/src/lib/data/uma.ts b/zenno/src/lib/data/uma.ts new file mode 100644 index 0000000..7863457 --- /dev/null +++ b/zenno/src/lib/data/uma.ts @@ -0,0 +1,75 @@ +import globalJSON from '../../../../global/uma.json'; + +/** + * Uma or character card definitions. + */ +export interface Uma { + /** + * Uma ID. + */ + chara_card_id: number; + /** + * Character ID that the Uma is a variant of. + */ + chara_id: number; + /** + * Regional name of the Uma, comprised of the variant name and the character name. + * E.g. "[Special Dreamer] Special Week". + */ + name: string; + /** + * Regional variant name. + * E.g. "[Special Dreamer]". + */ + variant: string; + + sprint: AptitudeLevel; + mile: AptitudeLevel; + medium: AptitudeLevel; + long: AptitudeLevel; + front: AptitudeLevel; + pace: AptitudeLevel; + late: AptitudeLevel; + end: AptitudeLevel; + turf: AptitudeLevel; + dirt: AptitudeLevel; + + /** + * ID of the Uma's unique skill. + */ + unique: number; + /** + * ID of the Uma's first built-in skill. + */ + skill1: number; + /** + * ID of the Uma's second built-in skill. + */ + skill2: number; + /** + * ID of the Uma's third built-in skill. + */ + skill3: number; + /** + * ID of the skill unlocked at potential level 2. + */ + skill_pl2: number; + /** + * ID of the skill unlocked at potential level 3. + */ + skill_pl3: number; + /** + * ID of the skill unlocked at potential level 4. + */ + skill_pl4: number; + /** + * ID of the skill unlocked at potential level 5. + */ + skill_pl5: number; +} + +export type AptitudeLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + +export const uma = { + global: globalJSON as Uma[], +}; diff --git a/zenno/src/lib/legacy.ts b/zenno/src/lib/legacy.ts new file mode 100644 index 0000000..2defc00 --- /dev/null +++ b/zenno/src/lib/legacy.ts @@ -0,0 +1,72 @@ +import { affinity, lookup } from './data/affinity'; +import { uma, type Uma } from './data/uma'; + +/** + * A legacy, or parents and grandparents. + * Defined as an applicative over generic types to reduce the API surface. + * @see mapLegacy + */ +export interface Legacy { + p1: T; + s11: T; + s12: T; + p2: T; + s21: T; + s22: T; +} + +/** + * Applicative fmap for legacies. + * @param l Input legacy + * @param f Mapping function + * @returns Transformed legacy + */ +export function mapLegacy(l: Legacy, f: (u: U) => V): Legacy { + return { + p1: f(l.p1), + s11: f(l.s11), + s12: f(l.s12), + p2: f(l.p2), + s21: f(l.s21), + s22: f(l.s22), + } +} + +/** + * A veteran, or the result of a completed career. + */ +export interface Veteran { + uma: number; + sparks: number[]; + saddles: number[]; +} + +function findUma(umas: Uma[], u: number): Uma | null { + return umas.find((v) => v.chara_card_id === u) ?? null; +} + +/** + * Compute individual affinities for a legacy using the global region ruleset. + * @param trainee Uma (not character) ID of the trainee Uma + * @param legacy Legacy veterans + * @returns Individual affinities + */ +export function globalAffinity(trainee: number, legacy: Legacy): Legacy { + const t = findUma(uma.global, trainee)?.chara_id ?? 0; + const charas = mapLegacy(legacy, (u) => findUma(uma.global, u.uma)?.chara_id ?? 0); + const saddles = mapLegacy(legacy, (u) => new Set(u.saddles)); + const aff = affinity.global; + const pp = lookup(aff, charas.p1, charas.p2); + const s11 = lookup(aff, t, charas.p1, charas.s11) + saddles.p1.intersection(saddles.s11).size; + const s12 = lookup(aff, t, charas.p1, charas.s12) + saddles.p1.intersection(saddles.s12).size; + const s21 = lookup(aff, t, charas.p2, charas.s21) + saddles.p2.intersection(saddles.s21).size; + const s22 = lookup(aff, t, charas.p2, charas.s22) + saddles.p2.intersection(saddles.s22).size; + return { + p1: lookup(aff, t, charas.p1) + pp + s11 + s12, + s11, + s12, + p2: lookup(aff, t, charas.p2) + pp + s21 + s22, + s21, + s22, + }; +}