414 lines
12 KiB
TypeScript
414 lines
12 KiB
TypeScript
/**
|
||
* 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<Record<DurationScale, string>> = {
|
||
[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<Record<AbilityType, (v: number) => 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<Record<AbilityValueUsage, string>> = {
|
||
[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<Record<Target, (v?: number) => 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<Skill[]> {
|
||
const resp = await fetch('/api/global/skill');
|
||
return resp.json();
|
||
}
|
||
|
||
export async function skillGroups(): Promise<SkillGroup[]> {
|
||
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;
|