diff --git a/zenno/src/lib/Skill.svelte b/zenno/src/lib/Skill.svelte index 54ce867..ff991b3 100644 --- a/zenno/src/lib/Skill.svelte +++ b/zenno/src/lib/Skill.svelte @@ -1,5 +1,16 @@ -{s.name} +{#snippet condition(disj: string[])} + {#each disj as conj, i (conj)} +
{conj}
+ {#if i !== disj.length - 1} +
or
+ {/if} + {/each} +{/snippet} + +{#snippet abilities(abils: Ability[])} + {#each abils as a (a.type)} +
+ {ABILITY_TYPE_FORMAT[a.type](a.value)} + {ABILITY_SCALE_NAME[a.value_usage]} + {#if a.target && a.target !== Target.Self} + {TARGET_FORMAT[a.target](a.target_value)} + {/if} +
+ {/each} +{/snippet} + +
+ + {s.name} +
diff --git a/zenno/src/lib/data/skill.ts b/zenno/src/lib/data/skill.ts index a054a02..0e20850 100644 --- a/zenno/src/lib/data/skill.ts +++ b/zenno/src/lib/data/skill.ts @@ -57,6 +57,24 @@ export interface Skill { 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. */ @@ -77,7 +95,7 @@ export interface Activation { /** * Special skill duration scaling mode. */ - dur_scale: 1 | 2 | 3 | 4 | 5 | 7; + dur_scale: DurationScale; /** * Skill cooldown in ten thousandths of a second. * A value of 5000000 indicates that the cooldown is forever. @@ -90,6 +108,159 @@ export interface Activation { 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. */ @@ -97,11 +268,11 @@ export interface Ability { /** * Race mechanic affected by the ability. */ - type: 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 13 | 21 | 27 | 28 | 31 | 35; + type: AbilityType; /** * Special scaling type of the skill value. */ - value_usage: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 19 | 20 | 22 | 23 | 24 | 25; + value_usage: AbilityValueUsage; /** * Amount that the skill modifies the race mechanic in ten thousandths of * whatever is the appropriate unit. @@ -110,7 +281,7 @@ export interface Ability { /** * Selector for horses targeted by the ability. */ - target: 1 | 2 | 4 | 7 | 9 | 10 | 11 | 18 | 19 | 20 | 21 | 22 | 23; + target: Target; /** * Argument value for the ability target, when appropriate. */ diff --git a/zenno/src/routes/layout.css b/zenno/src/routes/layout.css index e523c07..5799aa5 100644 --- a/zenno/src/routes/layout.css +++ b/zenno/src/routes/layout.css @@ -15,7 +15,8 @@ html { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif; } -body { +body, +.skill-tip { background-color: light-dark(var(--color-mist-200), var(--color-mist-800)); color: light-dark(var(--color-amber-950), var(--color-amber-50)); }