345 lines
9.0 KiB
TypeScript
345 lines
9.0 KiB
TypeScript
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<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,
|
||
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<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.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<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 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<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'),
|
||
[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> = {
|
||
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;
|