/** * Skill data. */ export interface Skill { /** * Skill ID. */ skill_id: number; /** * Regional skill name. */ name: string; /** * Regional skil description. */ description: string; /** * Other skills in the skill group. */ group_skills: GroupSkill[]; /** * 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; /** * Numeric tags applied to the skill, for effects which select skill types. */ tags: number[]; /** * Skill icon ID. */ icon_id: number; } /** * Skill reference in a skill group. */ export interface GroupSkill { /** Skill ID. */ skill_id: number; /** Group rate, as in `Skill`. */ group_rate: Skill['group_rate']; /** Rarity, as in `Skill`. */ rarity: Skill['rarity']; } 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, Runaway = 6, Vision = 8, HP = 9, GateDelay = 10, Frenzy = 13, AddGateDelay = 14, CurrentSpeed = 21, CurrentSpeedWithDecel = 22, TargetSpeed = 27, LaneSpeed = 28, RushedChance = 29, Accel = 31, AllStatus = 32, LaneChange = 35, ActivateRare = 37, } 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.Runaway]: () => 'Enable Runaway', [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.CurrentSpeedWithDecel]: (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.RushedChance]: (v) => plusBonus(v) + '% Rushed Chance', [AbilityType.Accel]: (v) => plusBonus(v) + ' m/s² Acceleration', [AbilityType.AllStatus]: (v) => 'All stats ' + plusBonus(v), [AbilityType.LaneChange]: (v) => 'Target Lane = ' + tenThousandths(v) + ' CW', [AbilityType.ActivateRare]: (v) => 'Activate Rare Skills ×' + tenThousandths(v), } 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, Fans = 12, 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 the career', [AbilityValueUsage.Fans]: 'scaling with fans earned in the career', [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', 'to the runner ahead'), [Target.Behind]: (v) => targetn(v!, 'behind', 'to the runner 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 async function skills(): Promise { const resp = await fetch('/api/global/skill'); return resp.json(); } export async function skillGroups(): Promise { const resp = await fetch('/api/global/skill-group'); return resp.json(); } export const TAGS = [ { name: 'Front Runner', tag: 101 }, { name: 'Pace Chaser', tag: 102 }, { name: 'Late Surger', tag: 103 }, { name: 'End Closer', tag: 104 }, { name: 'Sprint', tag: 201 }, { name: 'Mile', tag: 202 }, { name: 'Medium', tag: 203 }, { name: 'Long', tag: 204 }, { name: 'Early Race', tag: 301 }, { name: 'Mid Race', tag: 302 }, { name: 'Late Race', tag: 303 }, { name: 'Speed Stat or Velocity', tag: 401 }, { name: 'Stamina Stat or HP', tag: 402 }, { name: 'Power Stat, Acceleration, or Lane Movement', tag: 403 }, { name: 'Guts Stat', tag: 404 }, { name: 'Wit Stat or Vision', tag: 405 }, { name: 'Debuff', tag: 406 }, { name: 'Gate Delay', tag: 407 }, { name: 'Turf', tag: 501 }, { name: 'Dirt', tag: 502 }, { name: 'Ground Condition Passive', tag: 601 }, { name: 'Weather Passive', tag: 602 }, { name: 'Season Passive', tag: 603 }, { name: 'Racecourse Passive', tag: 604 }, { name: 'Time of Day Passive', tag: 605 }, { name: 'Exchange Race Passive', tag: 606 }, { name: 'Distance Passive', tag: 607 }, { name: 'Corner-Based Passive', tag: 608 }, { name: 'Gate Position Passive', tag: 609 }, { name: 'Ground Type Passive', tag: 610 }, { name: 'Popularity Passive', tag: 611 }, { name: 'Running Style Passive', tag: 612 }, { name: "Passive Depending on Other Horses' Styles or Skills", tag: 613 }, { name: 'Base Stat Threshold Passive', tag: 614 }, { name: 'Mood Passive', tag: 615 }, { name: 'URA', tag: 801 }, { name: 'Unity Cup', tag: 802 }, { name: 'Trackblazer', tag: 804 }, { name: 'Our Grand Concert', tag: 803 }, // { name: 'Grand Masters', tag: ? }, // { name: "Project L'Arc", tag: ? }, // { name: 'U.A.F.', tag: ? }, // { name: 'Great Food Festival', tag: ? }, // { name: 'Run, Mecha Umamusume!', tag: ? }, // { name: 'The Twinkle Legends', tag: ? }, // { name: 'Design Your Island', tag: ? }, // { name: 'Yukoma Hot Springs', tag: ? }, // { name: 'Beyond Dreams', tag: ? }, // { name: 'Tracen-Ken', tag: ? }, ] as const;