zenno/career/ismdata: start of auto career analysis
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import * as support from '$lib/data/support';
|
||||
import type { SvelteHTMLElements } from 'svelte/elements';
|
||||
|
||||
const sizes = [70, 35, 16];
|
||||
|
||||
interface Props {
|
||||
name: keyof typeof support.SPECIALTY_ICON;
|
||||
size: (typeof sizes)[number];
|
||||
}
|
||||
|
||||
let { name, size, ...rest }: Props & SvelteHTMLElements['picture'] = $props();
|
||||
|
||||
const icon = $derived(support.SPECIALTY_ICON[name]);
|
||||
const avifs = $derived(
|
||||
sizes
|
||||
.filter((sz) => sz >= size)
|
||||
.map((sz) => `/img/support/type/${icon}/${sz}x${sz}.avif ${sz / size}x`)
|
||||
.join(', '),
|
||||
);
|
||||
const pngs = $derived(
|
||||
sizes
|
||||
.filter((sz) => sz >= size)
|
||||
.map((sz) => `/img/support/type/${icon}/${sz}x${sz}.png ${sz / size}x`)
|
||||
.join(', '),
|
||||
);
|
||||
const src = $derived(`/img/support/${icon}/${sizes[0]}x${sizes[0]}.png`);
|
||||
</script>
|
||||
|
||||
<picture {...rest}>
|
||||
<source srcset={avifs} type="image/avif" />
|
||||
<source srcset={pngs} type="image/png" />
|
||||
<img {src} alt={name} class="inline" width={size} height={size} loading="lazy" />
|
||||
</picture>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import type { SvelteHTMLElements } from 'svelte/elements';
|
||||
|
||||
const sizes = [128, 64, 32] as const;
|
||||
|
||||
interface Props {
|
||||
icon_id: number | null | undefined;
|
||||
size: (typeof sizes)[number];
|
||||
alt?: string;
|
||||
}
|
||||
|
||||
let { icon_id, size, alt, ...rest }: Props & SvelteHTMLElements['picture'] = $props();
|
||||
|
||||
const icon = $derived(icon_id ?? 0);
|
||||
const avifs = $derived(
|
||||
sizes
|
||||
.filter((sz) => sz >= size)
|
||||
.map((sz) => `/img/support/${icon}/${sz}x${sz}.avif ${sz / size}x`)
|
||||
.join(', '),
|
||||
);
|
||||
const pngs = $derived(
|
||||
sizes
|
||||
.filter((sz) => sz >= size)
|
||||
.map((sz) => `/img/support/${icon}/${sz}x${sz}.png ${sz / size}x`)
|
||||
.join(', '),
|
||||
);
|
||||
const src = $derived(icon !== 0 ? `/img/support/${icon}/${sizes[0]}x${sizes[0]}.png` : `/img/support/10011`);
|
||||
const title = $derived(alt ?? `Support card icon ID ${icon_id}`);
|
||||
</script>
|
||||
|
||||
{#if icon !== 0}
|
||||
<picture {...rest}>
|
||||
<source srcset={avifs} type="image/avif" />
|
||||
<source srcset={pngs} type="image/png" />
|
||||
<img {src} {title} alt={title} class="inline" width={size} height={size} loading="lazy" />
|
||||
</picture>
|
||||
{:else}
|
||||
<div class={`w-[${size}px] h-[${size}px]`}></div>
|
||||
{/if}
|
||||
@@ -45,6 +45,13 @@ export const PAGES = {
|
||||
description: 'Calculate the importance of acceleration effects at the starting gate.',
|
||||
},
|
||||
] satisfies Page[],
|
||||
career: [
|
||||
{
|
||||
route: resolve('/career/ismdata'),
|
||||
name: 'Idle Single Mode Analysis',
|
||||
description: 'Analyze Umadump records of Independent Training',
|
||||
},
|
||||
] satisfies Page[],
|
||||
chara: [
|
||||
{
|
||||
route: resolve('/chara/affinity'),
|
||||
|
||||
@@ -47,7 +47,6 @@ export interface SupportCard {
|
||||
Effect[],
|
||||
Effect[],
|
||||
Effect[],
|
||||
Effect[],
|
||||
];
|
||||
/**
|
||||
* Unique effect, for cards that have them.
|
||||
@@ -210,3 +209,18 @@ export interface Hint {
|
||||
wit?: number;
|
||||
sp?: number;
|
||||
}
|
||||
|
||||
export async function supports(): Promise<SupportCard[]> {
|
||||
const resp = await fetch('/api/global/support');
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
export const SPECIALTY_ICON = {
|
||||
Speed: 0,
|
||||
Stamina: 1,
|
||||
Power: 2,
|
||||
Guts: 3,
|
||||
Wit: 4,
|
||||
Pal: 5,
|
||||
Group: 6,
|
||||
} as const;
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
import * as z from 'zod';
|
||||
import * as race from './race';
|
||||
|
||||
const Skill = z.strictObject({
|
||||
skill_id: z.int32(),
|
||||
level: z.int32(),
|
||||
});
|
||||
|
||||
export type Skill = z.infer<typeof Skill>;
|
||||
|
||||
const SkillTip = z.strictObject({
|
||||
group_id: z.int32(),
|
||||
rarity: z.int32(),
|
||||
level: z.int32(),
|
||||
});
|
||||
|
||||
export type SkillTip = z.infer<typeof SkillTip>;
|
||||
|
||||
/** SingleModeSupportCard */
|
||||
const SupportCard = z.strictObject({
|
||||
position: z.int32(),
|
||||
support_card_id: z.int32(),
|
||||
limit_break_count: z.int32(),
|
||||
exp: z.int32(),
|
||||
owner_viewer_id: z.number(),
|
||||
});
|
||||
|
||||
export type SupportCard = z.infer<typeof SupportCard>;
|
||||
|
||||
const GroupOuting = z.strictObject({
|
||||
chara_id: z.int32(),
|
||||
is_outing: z.int32(),
|
||||
story_step: z.int32(),
|
||||
});
|
||||
|
||||
export type GroupOuting = z.infer<typeof GroupOuting>;
|
||||
|
||||
const Evaluation = z.strictObject({
|
||||
target_id: z.int32(),
|
||||
training_partner_id: z.int32(),
|
||||
evaluation: z.int32(),
|
||||
is_outing: z.int32(),
|
||||
story_step: z.int32(),
|
||||
is_appear: z.int32(),
|
||||
group_outing_info_array: z.array(GroupOuting),
|
||||
});
|
||||
|
||||
export type Evaluation = z.infer<typeof Evaluation>;
|
||||
|
||||
const TrainingLevel = z.strictObject({
|
||||
command_id: z.int32(),
|
||||
level: z.int32(),
|
||||
});
|
||||
|
||||
export type TrainingLevel = z.infer<typeof TrainingLevel>;
|
||||
|
||||
const GuestOuting = z.strictObject({
|
||||
support_card_id: z.int32(),
|
||||
story_step: z.int32(),
|
||||
group_outing_info_array: z.array(GroupOuting),
|
||||
});
|
||||
|
||||
export type GuestOuting = z.infer<typeof GuestOuting>;
|
||||
|
||||
/** SingleModeSkillUpgrade */
|
||||
const SkillUpgrade = z.strictObject({
|
||||
condition_id: z.int32(),
|
||||
total_count: z.int32(),
|
||||
current_count: z.int32(),
|
||||
});
|
||||
|
||||
export type SkillUpgrade = z.infer<typeof SkillUpgrade>;
|
||||
|
||||
const AptitudeLevel = z.enum(race.AptitudeLevel);
|
||||
|
||||
const SingleModeChara = z.strictObject({
|
||||
single_mode_chara_id: z.int32(),
|
||||
card_id: z.int32(),
|
||||
chara_grade: z.int32(),
|
||||
speed: z.int32(),
|
||||
stamina: z.int32(),
|
||||
power: z.int32(),
|
||||
wiz: z.int32(),
|
||||
guts: z.int32(),
|
||||
vital: z.int32(),
|
||||
max_speed: z.int32(),
|
||||
max_stamina: z.int32(),
|
||||
max_power: z.int32(),
|
||||
max_wiz: z.int32(),
|
||||
max_guts: z.int32(),
|
||||
default_max_speed: z.int32(),
|
||||
default_max_stamina: z.int32(),
|
||||
default_max_power: z.int32(),
|
||||
default_max_wiz: z.int32(),
|
||||
default_max_guts: z.int32(),
|
||||
max_vital: z.int32(),
|
||||
motivation: z.int32(),
|
||||
fans: z.int32(),
|
||||
rarity: z.int32(),
|
||||
race_program_id: z.int32(),
|
||||
reserve_race_program_id: z.int32(),
|
||||
race_running_style: z.int32(),
|
||||
is_short_race: z.int32(),
|
||||
talent_level: z.int32(),
|
||||
skill_array: z.array(Skill),
|
||||
disable_skill_id_array: z.array(z.int32()),
|
||||
skill_tips_array: z.array(SkillTip),
|
||||
support_card_array: z.array(SupportCard),
|
||||
succession_trained_chara_id_1: z.int32(),
|
||||
succession_trained_chara_id_2: z.int32(),
|
||||
proper_distance_short: AptitudeLevel,
|
||||
proper_distance_mile: AptitudeLevel,
|
||||
proper_distance_middle: AptitudeLevel,
|
||||
proper_distance_long: AptitudeLevel,
|
||||
proper_running_style_nige: AptitudeLevel,
|
||||
proper_running_style_senko: AptitudeLevel,
|
||||
proper_running_style_sashi: AptitudeLevel,
|
||||
proper_running_style_oikomi: AptitudeLevel,
|
||||
proper_ground_turf: AptitudeLevel,
|
||||
proper_ground_dirt: AptitudeLevel,
|
||||
turn: z.int32(),
|
||||
skill_point: z.int32(),
|
||||
short_cut_state: z.int32(),
|
||||
state: z.int32(),
|
||||
playing_state: z.int32(),
|
||||
scenario_id: z.int32(),
|
||||
route_id: z.int32(),
|
||||
start_time: z.string(),
|
||||
evaluation_info_array: z.array(Evaluation),
|
||||
training_level_info_array: z.array(TrainingLevel),
|
||||
nickname_id_array: z.array(z.int32()),
|
||||
chara_effect_id_array: z.array(z.int32()),
|
||||
route_race_id_array: z.array(z.int32()),
|
||||
guest_outing_info_array: z.array(GuestOuting),
|
||||
skill_upgrade_info_array: z.array(SkillUpgrade),
|
||||
});
|
||||
|
||||
export type SingleModeChara = z.infer<typeof SingleModeChara>;
|
||||
|
||||
const ProgressInfo = z.strictObject({
|
||||
chara_info: z.optional(SingleModeChara),
|
||||
start_time: z.string(),
|
||||
end_time: z.string(),
|
||||
dress_id: z.int32(),
|
||||
});
|
||||
|
||||
export type ProgressInfo = z.infer<typeof ProgressInfo>;
|
||||
|
||||
const CharaEffectLog = z.strictObject({
|
||||
chara_effect_id: z.int32(),
|
||||
is_active: z.boolean(),
|
||||
});
|
||||
|
||||
export type CharaEffectLog = z.infer<typeof CharaEffectLog>;
|
||||
|
||||
/** IdleSingleModeSignedInt */
|
||||
const SignedInt = z.strictObject({
|
||||
sign: z.int32(),
|
||||
value: z.int32(),
|
||||
});
|
||||
|
||||
export type SignedInt = z.infer<typeof SignedInt>;
|
||||
|
||||
/** IdleSingleModeGainInfo */
|
||||
const GainInfo = z.strictObject({
|
||||
speed: SignedInt,
|
||||
stamina: SignedInt,
|
||||
power: SignedInt,
|
||||
wiz: SignedInt,
|
||||
guts: SignedInt,
|
||||
max_speed: z.int32(),
|
||||
max_stamina: z.int32(),
|
||||
max_power: z.int32(),
|
||||
max_wiz: z.int32(),
|
||||
max_guts: z.int32(),
|
||||
proper_distance_short: z.int32(),
|
||||
proper_distance_mile: z.int32(),
|
||||
proper_distance_middle: z.int32(),
|
||||
proper_distance_long: z.int32(),
|
||||
proper_running_style_nige: z.int32(),
|
||||
proper_running_style_senko: z.int32(),
|
||||
proper_running_style_sashi: z.int32(),
|
||||
proper_running_style_oikomi: z.int32(),
|
||||
proper_ground_turf: z.int32(),
|
||||
proper_ground_dirt: z.int32(),
|
||||
skill_point: z.int32(),
|
||||
skill_tips_array: z.array(SkillTip),
|
||||
});
|
||||
|
||||
export type GainInfo = z.infer<typeof GainInfo>;
|
||||
|
||||
/** IdleSingleModeSupportCardGain */
|
||||
const SupportCardGain = z.strictObject({
|
||||
support_card_id: z.int32(),
|
||||
gain_info: GainInfo,
|
||||
});
|
||||
|
||||
export type SupportCardGain = z.infer<typeof SupportCardGain>;
|
||||
|
||||
const Factor = z.strictObject({
|
||||
factor_id: z.int32(),
|
||||
level: z.int32(),
|
||||
});
|
||||
|
||||
export type Factor = z.infer<typeof Factor>;
|
||||
|
||||
/** IdleSingleModeSuccessionFactorGain */
|
||||
const SuccessionFactorGain = z.strictObject({
|
||||
year: z.int32(),
|
||||
gain_factor_info_array: z.array(Factor),
|
||||
});
|
||||
|
||||
export type SuccessionFactorGain = z.infer<typeof SuccessionFactorGain>;
|
||||
|
||||
const SingleRaceHistory = z.strictObject({
|
||||
turn: z.int32(),
|
||||
program_id: z.int32(),
|
||||
weather: z.int32(),
|
||||
ground_condition: z.int32(),
|
||||
running_style: z.int32(),
|
||||
result_rank: z.int32(),
|
||||
frame_order: z.int32(),
|
||||
npc_count: z.int32(),
|
||||
});
|
||||
|
||||
export type SingleRaceHistory = z.infer<typeof SingleRaceHistory>;
|
||||
|
||||
const RaceReward = z.strictObject({
|
||||
item_type: z.int32(),
|
||||
item_id: z.int32(),
|
||||
item_num: z.int32(),
|
||||
});
|
||||
|
||||
export type RaceReward = z.infer<typeof RaceReward>;
|
||||
|
||||
const RaceRewardLimit = z.strictObject({
|
||||
reward_id: z.int32(),
|
||||
item_type: z.int32(),
|
||||
item_id: z.int32(),
|
||||
item_num: z.int32(),
|
||||
rest_count: z.int32(),
|
||||
});
|
||||
|
||||
export type RaceRewardLimit = z.infer<typeof RaceRewardLimit>;
|
||||
|
||||
const CharaRaceReward = z.strictObject({
|
||||
result_rank: z.int32(),
|
||||
result_time: z.int32(),
|
||||
race_reward: z.array(RaceReward),
|
||||
race_reward_bonus: z.array(RaceReward),
|
||||
race_reward_plus_bonus: z.array(RaceReward),
|
||||
race_reward_bonus_win: z.array(RaceReward),
|
||||
race_reward_limit: z.optional(z.array(RaceRewardLimit)),
|
||||
gained_fans: z.int32(),
|
||||
campaign_id_array: z.array(z.int32()),
|
||||
});
|
||||
|
||||
export type CharaRaceReward = z.infer<typeof CharaRaceReward>;
|
||||
|
||||
/** IdleSingleModeRaceHistory */
|
||||
const RaceHistory = z.strictObject({
|
||||
race_history: SingleRaceHistory,
|
||||
race_reward_info: CharaRaceReward,
|
||||
lose_tips_id: z.int32(),
|
||||
});
|
||||
|
||||
export type RaceHistory = z.infer<typeof RaceHistory>;
|
||||
|
||||
/** IdleSingleModeProgressLog */
|
||||
const ProgressLog = z.strictObject({
|
||||
chara_effect_log_array: z.array(CharaEffectLog),
|
||||
support_card_gain_info_array: z.array(SupportCardGain),
|
||||
event_gain_info: GainInfo,
|
||||
succession_gain_info: GainInfo,
|
||||
succession_factor_gain_array: z.array(SuccessionFactorGain),
|
||||
race_history_array: z.array(RaceHistory),
|
||||
gain_skill_id_array: z.array(z.int32()),
|
||||
total_skill_point: z.int32(),
|
||||
});
|
||||
|
||||
export type ProgressLog = z.infer<typeof ProgressLog>;
|
||||
|
||||
const AddItem = z.strictObject({
|
||||
item_id: z.int32(),
|
||||
number: z.int32(),
|
||||
});
|
||||
|
||||
export type AddItem = z.infer<typeof AddItem>;
|
||||
|
||||
const RewardSummary = z.strictObject({
|
||||
add_item_list: z.array(AddItem),
|
||||
});
|
||||
|
||||
export type RewardSummary = z.infer<typeof RewardSummary>;
|
||||
|
||||
const End = z.strictObject({
|
||||
chara_info: SingleModeChara,
|
||||
reward_summary_info: RewardSummary,
|
||||
});
|
||||
|
||||
export type End = z.infer<typeof End>;
|
||||
|
||||
/**
|
||||
* WorkIdleSingleMode, or all the data extracted from an ISM career.
|
||||
*/
|
||||
export const Career = z.object({
|
||||
progress_info: ProgressInfo,
|
||||
progress_log_info: ProgressLog,
|
||||
end_info: End,
|
||||
});
|
||||
|
||||
export type Career = z.infer<typeof Career>;
|
||||
+70
-33
@@ -20,6 +20,34 @@ export enum Stat {
|
||||
*/
|
||||
export const StatList = [Stat.Speed, Stat.Stamina, Stat.Power, Stat.Guts, Stat.Wit] as const;
|
||||
|
||||
/**
|
||||
* Aptitude or proficiency levels.
|
||||
*/
|
||||
export enum AptitudeLevel {
|
||||
G = 1,
|
||||
F = 2,
|
||||
E = 3,
|
||||
D = 4,
|
||||
C = 5,
|
||||
B = 6,
|
||||
A = 7,
|
||||
S = 8,
|
||||
}
|
||||
|
||||
/**
|
||||
* Aptitude levels as a descending list for easy iterating.
|
||||
*/
|
||||
export const APTITUDE_LEVELS = [
|
||||
AptitudeLevel.S,
|
||||
AptitudeLevel.A,
|
||||
AptitudeLevel.B,
|
||||
AptitudeLevel.C,
|
||||
AptitudeLevel.D,
|
||||
AptitudeLevel.E,
|
||||
AptitudeLevel.F,
|
||||
AptitudeLevel.G,
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Mood levels.
|
||||
*/
|
||||
@@ -159,7 +187,16 @@ const powerStatGroundMod = {
|
||||
},
|
||||
} as const;
|
||||
|
||||
const strategyProficiencyMod = [0.1, 0.2, 0.4, 0.6, 0.75, 0.85, 1.0, 1.1] as const;
|
||||
const strategyProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
|
||||
[AptitudeLevel.G]: 0.1,
|
||||
[AptitudeLevel.F]: 0.2,
|
||||
[AptitudeLevel.E]: 0.4,
|
||||
[AptitudeLevel.D]: 0.6,
|
||||
[AptitudeLevel.C]: 0.75,
|
||||
[AptitudeLevel.B]: 0.85,
|
||||
[AptitudeLevel.A]: 1.0,
|
||||
[AptitudeLevel.S]: 1.1,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Compute adjusted speed for a race.
|
||||
@@ -282,34 +319,6 @@ export const RUNNING_STYLES = [
|
||||
['Runaway', RunningStyle.Runaway],
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Aptitude or proficiency levels.
|
||||
*/
|
||||
export enum AptitudeLevel {
|
||||
G,
|
||||
F,
|
||||
E,
|
||||
D,
|
||||
C,
|
||||
B,
|
||||
A,
|
||||
S,
|
||||
}
|
||||
|
||||
/**
|
||||
* Aptitude levels as a descending list for easy iterating.
|
||||
*/
|
||||
export const APTITUDE_LEVELS = [
|
||||
AptitudeLevel.S,
|
||||
AptitudeLevel.A,
|
||||
AptitudeLevel.B,
|
||||
AptitudeLevel.C,
|
||||
AptitudeLevel.D,
|
||||
AptitudeLevel.E,
|
||||
AptitudeLevel.F,
|
||||
AptitudeLevel.G,
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Race phases.
|
||||
* While last spurt phase is also a phase, it is not distinguished here.
|
||||
@@ -428,7 +437,16 @@ const speedStrategyPhaseCoeff = [
|
||||
[1.063, 0.962, 0.95],
|
||||
] as const;
|
||||
|
||||
const distanceProficiencyMod = [0.1, 0.2, 0.4, 0.6, 0.8, 0.9, 1.0, 1.05] as const;
|
||||
const distanceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
|
||||
[AptitudeLevel.G]: 0.1,
|
||||
[AptitudeLevel.F]: 0.2,
|
||||
[AptitudeLevel.E]: 0.4,
|
||||
[AptitudeLevel.D]: 0.6,
|
||||
[AptitudeLevel.C]: 0.8,
|
||||
[AptitudeLevel.B]: 0.9,
|
||||
[AptitudeLevel.A]: 1.0,
|
||||
[AptitudeLevel.S]: 1.05,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Get a horse's base target speed outside of late race.
|
||||
@@ -457,7 +475,7 @@ export function baseTargetSpeed(
|
||||
style: RunningStyle,
|
||||
phase: Phase,
|
||||
speedStat?: number,
|
||||
distanceApt?: number,
|
||||
distanceApt?: AptitudeLevel,
|
||||
): number {
|
||||
const base = baseSpeed(raceLen);
|
||||
const baseTarget = base * speedStrategyPhaseCoeff[style][phase];
|
||||
@@ -598,8 +616,27 @@ const accelStrategyPhaseCoeff = {
|
||||
[RunningStyle.Runaway]: [1.17, 0.94, 0.956],
|
||||
} as const;
|
||||
|
||||
const surfaceProficiencyMod = [0.1, 0.3, 0.5, 0.7, 0.8, 0.9, 1.0, 1.05] as const;
|
||||
const accelDistanceProficiencyMod = [0.4, 0.5, 0.6, 1, 1, 1, 1, 1] as const;
|
||||
const surfaceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
|
||||
[AptitudeLevel.G]: 0.1,
|
||||
[AptitudeLevel.F]: 0.3,
|
||||
[AptitudeLevel.E]: 0.5,
|
||||
[AptitudeLevel.D]: 0.7,
|
||||
[AptitudeLevel.C]: 0.8,
|
||||
[AptitudeLevel.B]: 0.9,
|
||||
[AptitudeLevel.A]: 1.0,
|
||||
[AptitudeLevel.S]: 1.05,
|
||||
} as const;
|
||||
|
||||
const accelDistanceProficiencyMod: Readonly<Record<AptitudeLevel, number>> = {
|
||||
[AptitudeLevel.G]: 0.4,
|
||||
[AptitudeLevel.F]: 0.5,
|
||||
[AptitudeLevel.E]: 0.6,
|
||||
[AptitudeLevel.D]: 1.0,
|
||||
[AptitudeLevel.C]: 1.0,
|
||||
[AptitudeLevel.B]: 1.0,
|
||||
[AptitudeLevel.A]: 1.0,
|
||||
[AptitudeLevel.S]: 1.0,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Calculate a horse's instantaneous acceleration value.
|
||||
|
||||
Reference in New Issue
Block a user