import skillGlobal from '../../../../global/skill.json'; import groupGlobal from '../../../../global/skill-group.json'; /** * Skill data. */ export interface Skill { /** * Skill ID. */ skill_id: number; /** * Regional skill name. */ name: string; /** * Regional skil description. */ description: string; /** * Skill group ID. */ group: number; /** * Skill rarity. 3-5 are uniques for various star levels. */ rarity: 1 | 2 | 3 | 4 | 5; /** * Upgrade position within the skill's group. * -1 is for negative (purple) skills. */ group_rate: 1 | 2 | 3 | -1; /** * Grade value, or the amount of rating gained for having the skill with * appropriate aptitude. */ grade_value?: number; /** * Whether the skill requires a wit check. */ wit_check: boolean; /** * Conditions and results of skill activation. */ activations: Activation[]; /** * Name of the Uma which owns this skill as a unique, if applicable. */ unique_owner?: string; /** * SP cost to purchase the skill, if applicable. */ sp_cost?: number; /** * Skill icon ID. */ icon_id: number; } export enum DurationScale { Direct = 1, FrontDistance = 2, RemainingHP = 3, IncrementPass = 4, MidSideBlock = 5, RemainingHP2 = 7, } export const DURATION_SCALE_NAME: Readonly> = { [DurationScale.Direct]: '', [DurationScale.FrontDistance]: 'scaling with distance from the front', [DurationScale.RemainingHP]: 'scaling with remaining HP', [DurationScale.IncrementPass]: 'increasing with each pass while active', [DurationScale.MidSideBlock]: 'scaling with mid-race phase blocked side time', [DurationScale.RemainingHP2]: 'scaling with remaining HP', } as const; /** * Conditions and results of skill activation. */ export interface Activation { /** * Precondition which must be satisfied before the condition is checked. */ precondition?: string; /** * Activation conditions. */ condition: string; /** * Skill duration in ten thousandths of a second. * Generally undefined for activations which only affect HP. */ duration?: number; /** * Special skill duration scaling mode. */ dur_scale: DurationScale; /** * Skill cooldown in ten thousandths of a second. * A value of 5000000 indicates that the cooldown is forever. * Generally undefined for passive skills. */ cooldown?: number; /** * Results applied when the skill's conditions are met. */ abilities: Ability[]; } export function tenThousandths(v: number): string { const q = v / 10000; if (v % 10000 === 0) { return q.toString(); } const s = q.toFixed(4); const j = /\.?0+$/.exec(s); return s.substring(0, j?.index ?? undefined); } function plusBonus(v: number): string { const s = tenThousandths(v); return v < 0 ? s : '+' + s; } export enum AbilityType { Speed = 1, Stamina = 2, Power = 3, Guts = 4, Wit = 5, GreatEscape = 6, Vision = 8, HP = 9, GateDelay = 10, Frenzy = 13, AddGateDelay = 14, CurrentSpeed = 21, TargetSpeed = 27, LaneSpeed = 28, Accel = 31, LaneChange = 35, } export const ABILITY_TYPE_FORMAT: Readonly string>> = { [AbilityType.Speed]: (v) => 'Speed ' + plusBonus(v), [AbilityType.Stamina]: (v) => 'Stamina ' + plusBonus(v), [AbilityType.Power]: (v) => 'Power ' + plusBonus(v), [AbilityType.Guts]: (v) => 'Guts ' + plusBonus(v), [AbilityType.Wit]: (v) => 'Wit ' + plusBonus(v), [AbilityType.GreatEscape]: () => 'Enable Great Escape', [AbilityType.Vision]: (v) => plusBonus(v) + 'm Vision', [AbilityType.HP]: (v) => (v > 0 ? tenThousandths(v * 100) + '% Recovery' : tenThousandths(v * 100) + '% HP Drain'), [AbilityType.GateDelay]: (v) => tenThousandths(v) + '× Gate Delay', [AbilityType.Frenzy]: (v) => tenThousandths(v) + 's Frenzy', [AbilityType.AddGateDelay]: (v) => plusBonus(v) + 's Gate Delay', [AbilityType.CurrentSpeed]: (v) => plusBonus(v) + ' m/s Current Speed', [AbilityType.TargetSpeed]: (v) => plusBonus(v) + ' m/s Target Speed', [AbilityType.LaneSpeed]: (v) => plusBonus(v) + ' CW/s Lane Change Speed', [AbilityType.Accel]: (v) => plusBonus(v) + ' m/s² Acceleration', [AbilityType.LaneChange]: (v) => 'Target Lane = ' + tenThousandths(v) + ' CW', } as const; export enum AbilityValueUsage { Direct = 1, SkillCount = 2, TeamSpeed = 3, TeamStamina = 4, TeamPower = 5, TeamGuts = 6, TeamWit = 7, Random1 = 8, Random2 = 9, Climax = 10, MaxStat = 13, GreenCount = 14, DistAdd = 19, MidRaceSideBlock = 20, Speed1 = 22, Speed2 = 23, ArcPotential = 24, MaxLead = 25, } export const ABILITY_SCALE_NAME: Readonly> = { [AbilityValueUsage.Direct]: '', [AbilityValueUsage.SkillCount]: 'scaling with the number of skills', [AbilityValueUsage.TeamSpeed]: 'scaling with team Speed', [AbilityValueUsage.TeamStamina]: 'scaling with team Stamina', [AbilityValueUsage.TeamPower]: 'scaling with team Power', [AbilityValueUsage.TeamGuts]: 'scaling with team Guts', [AbilityValueUsage.TeamWit]: 'scaling with team Wit', [AbilityValueUsage.Random1]: 'with a random 0× to 0.04× multiplier', [AbilityValueUsage.Random2]: 'with a random 0× to 0.04× mulitplier', [AbilityValueUsage.Climax]: 'scaling with the number of races won in training', [AbilityValueUsage.MaxStat]: 'scaling with the highest raw stat', [AbilityValueUsage.GreenCount]: 'scaling with the number of Passive skills activated', [AbilityValueUsage.DistAdd]: 'plus extra when far from the lead', [AbilityValueUsage.MidRaceSideBlock]: 'scaling with mid-race phase blocked side time', [AbilityValueUsage.Speed1]: 'scaling with overall speed', [AbilityValueUsage.Speed2]: 'scaling with overall speed', [AbilityValueUsage.ArcPotential]: "scaling with L'Arc global potential", [AbilityValueUsage.MaxLead]: 'scaling with the longest lead obtained in the first ⅔', }; export enum Target { Self = 1, Sympathizers = 2, InView = 4, Frontmost = 7, Ahead = 9, Behind = 10, AllTeammates = 11, Style = 18, RushingAhead = 19, RushingBehind = 20, RushingStyle = 21, Character = 22, Triggering = 23, } function targetn(v: number, n: string, one?: string): string { switch (v) { case 1: return one != null ? one : `to ${n}`; case 18: return `to all ${n}`; default: return `to ${v} others ${n}`; } } function stylename(v: number, infix: string): string { switch (v) { case 1: return `to ${infix} Front Runners`; case 2: return `to ${infix} Pace Chasers`; case 3: return `to ${infix} Late Surgers`; case 4: return `to ${infix} End Closers`; default: return `to all running unknown style ${v}`; } } export const TARGET_FORMAT: Readonly string>> = { [Target.Self]: () => '', [Target.Sympathizers]: () => 'to others with Sympathy', [Target.InView]: (v) => targetn(v!, 'in view'), [Target.Frontmost]: (v) => targetn(v!, 'at the front', 'the frontmost'), [Target.Ahead]: (v) => targetn(v!, 'ahead'), [Target.Behind]: (v) => targetn(v!, 'behind'), [Target.AllTeammates]: () => 'to all teammates', [Target.Style]: (v) => stylename(v!, 'all'), [Target.RushingAhead]: (v) => targetn(v!, 'rushing ahead'), [Target.RushingBehind]: (v) => targetn(v!, 'rushing behind'), [Target.RushingStyle]: (v) => stylename(v!, 'all rushing'), [Target.Character]: (v) => `to character ${v}`, [Target.Triggering]: () => 'to whosoever triggered this skill', } as const; /** * Effects applied when a skill activates. */ export interface Ability { /** * Race mechanic affected by the ability. */ type: AbilityType; /** * Special scaling type of the skill value. */ value_usage: AbilityValueUsage; /** * Amount that the skill modifies the race mechanic in ten thousandths of * whatever is the appropriate unit. */ value: number; /** * Selector for horses targeted by the ability. */ target: Target; /** * Argument value for the ability target, when appropriate. */ target_value?: number; } /** * Skill groups. * Skills in a skill group replace each other when purchased. * * As a special case, horsegen lists both unique skills and their inherited * versions in the skill groups for both. */ export interface SkillGroup { /** * Skill group ID. */ skill_group: number; /** * Base skill in the skill group, if any. * Either a common (white) skill or an Uma's own unique. * * Some skill groups, e.g. for G1 Averseness, have no base skill. */ skill1?: number; /** * First upgraded version of a skill, if any. * A rare (gold) skill, double circle skill, or an inherited unique skill. */ skill2?: number; /** * Highest upgraded version of a skill, if any. * Gold version of a skill with a double circle version. */ skill3?: number; /** * Negative (purple) version of a skill, if any. */ skill_bad?: number; } export const skills = { global: skillGlobal as Skill[], } as const; export const skillGroups = { global: groupGlobal as SkillGroup[], } as const; export const ZERO_SKILL: Readonly = { skill_id: 0, name: 'invalid skill', description: 'an invalid skill was specified', group: 0, rarity: 1, group_rate: 1, wit_check: false, activations: [], icon_id: 0, } as const;