diff --git a/zenno/src/routes/career/ismdata/+page.svelte b/zenno/src/routes/career/ismdata/+page.svelte index 2a173a0..f7ed5dc 100644 --- a/zenno/src/routes/career/ismdata/+page.svelte +++ b/zenno/src/routes/career/ismdata/+page.svelte @@ -13,6 +13,8 @@ import SupportIcon from '$lib/SupportIcon.svelte'; import SkillIcon from '$lib/SkillIcon.svelte'; import Spark from '$lib/Spark.svelte'; + import SupportCardGainChart from './SupportCardGainChart.svelte'; + import { DEPENDENT, INDEPENDENT, type SupportCardDatum } from './SupportCardGainChart'; let characters = new SvelteMap(); let skills = new SvelteMap(); @@ -55,6 +57,7 @@ }); let files: FileList | undefined = $state(); + let careersOpen = $state(false); const parses = $derived.by(async () => { if (files == null) { return []; @@ -75,7 +78,11 @@ let exclude = new SvelteSet(); const errors = $derived((await parses).filter((f) => 'err' in f) as Array<{ f: File; err: unknown }>); const loaded = $derived((await parses).filter((f) => 'ok' in f) as Array<{ f: File; ok: ism.Career }>); - const careers = $derived(loaded.filter((f) => !exclude.has(f.f.name))); + const careers = $derived(loaded.filter((f) => !exclude.has(f.f.name)).map((f) => f.ok)); + + let supportGainX: keyof SupportCardDatum = $state('Friendship Bonus'); + let supportGainY: keyof SupportCardDatum = $state('Total Stats'); + let supportGainS: keyof SupportCardDatum = $state('Source'); function groupskill(g: skill.SkillGroup | undefined, rarity: ism.SkillTip['rarity']) { if (g == null) { @@ -116,49 +123,68 @@ {#if loaded.length > 0}
-
+
{(files?.length ?? 0) - errors.length} careers {#if exclude.size !== 0} ({loaded.length - careers.length} excluded) {/if} - {#each loaded as career (career.ok.end_info.chara_info.single_mode_chara_id)} - {@const uma = umas.get(career.ok.end_info.chara_info.card_id)} - {@const scen = scenarios.get(career.ok.end_info.chara_info.scenario_id)} - !exclude.has(career.f.name), (v) => setExclude(career.f.name, v)}> - {#snippet chara()} - {uma?.name} - {/snippet} - {#snippet scenario()} - {scen?.name} - {/snippet} - {#snippet support(card: ism.SupportCard)} - {@const s = supports.get(card.support_card_id)} -
- - {'◆'.repeat(card.limit_break_count)}{'◇'.repeat(4 - card.limit_break_count)} -
- {/snippet} - {#snippet skillHint(hint: ism.SkillTip)} - {@const g = skillGroups.get(hint.group_id)} - {@const s = groupskill(g, hint.rarity)} -
- {#if s != null} - - {s.name} - +{hint.level} + {#if careersOpen} + {#each loaded as career (career.ok.end_info.chara_info.single_mode_chara_id)} + {@const uma = umas.get(career.ok.end_info.chara_info.card_id)} + {@const scen = scenarios.get(career.ok.end_info.chara_info.scenario_id)} + !exclude.has(career.f.name), (v) => setExclude(career.f.name, v)}> + {#snippet chara()} + {uma?.name} + {/snippet} + {#snippet scenario()} + {scen?.name} + {/snippet} + {#snippet support(card: ism.SupportCard)} + {@const s = supports.get(card.support_card_id)} +
+ + {'◆'.repeat(card.limit_break_count)}{'◇'.repeat(4 - card.limit_break_count)} +
+ {/snippet} + {#snippet skillHint(hint: ism.SkillTip)} + {@const g = skillGroups.get(hint.group_id)} + {@const s = groupskill(g, hint.rarity)} +
+ {#if s != null} + + {s.name} + +{hint.level} + {/if} +
+ {/snippet} + {#snippet spark(id: number)} + {@const spark = sparks.get(id)} + {#if spark != null} + {/if} -
- {/snippet} - {#snippet spark(id: number)} - {@const spark = sparks.get(id)} - {#if spark != null} - - {/if} - {/snippet} -
- {/each} + {/snippet} + + {/each} + {/if}
+ +
+ + vs + +
+
+ +
{/if} diff --git a/zenno/src/routes/career/ismdata/CareerOverview.svelte b/zenno/src/routes/career/ismdata/CareerOverview.svelte index 6904e46..f8c9f6b 100644 --- a/zenno/src/routes/career/ismdata/CareerOverview.svelte +++ b/zenno/src/routes/career/ismdata/CareerOverview.svelte @@ -19,6 +19,8 @@ const totalSparks = $derived( career.progress_log_info.succession_factor_gain_array.reduce((a, y) => a + y.gain_factor_info_array.length, 0), ); + + let open = $state(false); {#snippet gainStat(name: ComponentProps['name'], stat: number, max: number)} @@ -67,31 +69,33 @@ {@render support(card)} {/each} -
+
Gains per source - {#each career.progress_log_info.support_card_gain_info_array as card (card.support_card_id)} - {@const cardInfo = career.end_info.chara_info.support_card_array.find((c) => c.support_card_id === card.support_card_id)!} + {#if open} + {#each career.progress_log_info.support_card_gain_info_array as card (card.support_card_id)} + {@const cardInfo = career.end_info.chara_info.support_card_array.find((c) => c.support_card_id === card.support_card_id)!} +
+
+ {@render support(cardInfo)} +
+
+ {@render gainInfo(card.gain_info)} +
+
+ {/each}
-
- {@render support(cardInfo)} -
+
Events
- {@render gainInfo(card.gain_info)} + {@render gainInfo(career.progress_log_info.event_gain_info)}
- {/each} -
-
Events
-
- {@render gainInfo(career.progress_log_info.event_gain_info)} +
+
Inspiration
+
+ {@render gainInfo(career.progress_log_info.succession_gain_info)} +
-
-
-
Inspiration
-
- {@render gainInfo(career.progress_log_info.succession_gain_info)} -
-
+ {/if}
Hints for {career.end_info.chara_info.skill_tips_array.length} skills diff --git a/zenno/src/routes/career/ismdata/SupportCardGainChart.svelte b/zenno/src/routes/career/ismdata/SupportCardGainChart.svelte new file mode 100644 index 0000000..5c5f8b1 --- /dev/null +++ b/zenno/src/routes/career/ismdata/SupportCardGainChart.svelte @@ -0,0 +1,66 @@ + + +
+
+ the chart seems to have didn't +
+
+ + diff --git a/zenno/src/routes/career/ismdata/SupportCardGainChart.ts b/zenno/src/routes/career/ismdata/SupportCardGainChart.ts new file mode 100644 index 0000000..428d25b --- /dev/null +++ b/zenno/src/routes/career/ismdata/SupportCardGainChart.ts @@ -0,0 +1,147 @@ +import * as ism from '$lib/ism'; +import * as support from '$lib/data/support'; +import * as scenario from '$lib/data/scenario'; + +function gainTotalStats(gain: ism.GainInfo) { + return gain.speed.value + gain.stamina.value + gain.power.value + gain.guts.value + gain.wiz.value; +} + +export const DEPENDENT = [ + { + name: 'Total Stats', + map: (gain: ism.GainInfo, _effects: support.Effect[]) => gainTotalStats(gain), + }, + { + name: 'Total Stats + SP', + map: (gain: ism.GainInfo, _effects: support.Effect[]) => gainTotalStats(gain) + gain.skill_point, + }, + { + name: 'Speed', + map: (gain: ism.GainInfo, _effects: support.Effect[]) => gain.speed.value, + }, + { + name: 'Stamina', + map: (gain: ism.GainInfo, _effects: support.Effect[]) => gain.stamina.value, + }, + { + name: 'Power', + map: (gain: ism.GainInfo, _effects: support.Effect[]) => gain.power.value, + }, + { + name: 'Guts', + map: (gain: ism.GainInfo, _effects: support.Effect[]) => gain.guts.value, + }, + { + name: 'Wit', + map: (gain: ism.GainInfo, _effects: support.Effect[]) => gain.wiz.value, + }, + { + name: 'SP', + map: (gain: ism.GainInfo, _effects: support.Effect[]) => gain.skill_point, + }, + { + name: 'Skill Hints', + map: (gain: ism.GainInfo, effects: support.Effect[]) => { + // NOTE(zephyr): reduction starts with 1 because we want the value + // of each hint, not just the bonus from effects + const hintLevels = effects.reduce((l, e) => e.type === support.EffectType.HintLevels ? l + e.value : l, 1); + return gain.skill_tips_array.reduce((n, t) => n + Math.ceil(t.level / hintLevels), 0); + }, + }, + { + name: 'Skills', + map: (gain: ism.GainInfo, _effects: support.Effect[]) => gain.skill_tips_array.length, + }, +] as const; + +function label(c: ism.Career): string { + return `${c.end_info.chara_info.single_mode_chara_id} - ${c.end_info.chara_info.start_time}`; +} + +export const INDEPENDENT = [ + { + name: 'Friendship Bonus', + effect: support.EffectType.FriendshipBonus, + }, + { + name: 'Mood Effect', + effect: support.EffectType.MoodEffect, + }, + { + name: 'Speed Bonus', + effect: support.EffectType.SpeedBonus, + }, + { + name: 'Stamina Bonus', + effect: support.EffectType.StaminaBonus, + }, + { + name: 'Power Bonus', + effect: support.EffectType.PowerBonus, + }, + { + name: 'Guts Bonus', + effect: support.EffectType.GutsBonus, + }, + { + name: 'Wit Bonus', + effect: support.EffectType.WitBonus, + }, + { + name: 'Skill Point Bonus', + effect: support.EffectType.SkillPointBonus, + }, + { + name: 'Training Effectiveness', + effect: support.EffectType.TrainingEffectiveness, + }, + { + name: 'Initial Friendship', + effect: support.EffectType.InitialFriendship, + }, + { + name: 'Race Bonus', + effect: support.EffectType.RaceBonus, + }, + { + name: 'Hint Frequency', + effect: support.EffectType.HintFrequency, + }, + { + name: 'Specialty Priority', + effect: support.EffectType.SpecialtyPriority, + }, +] as const; + +export type SupportCardDatum = { + 'Career': string; + 'Scenario': string; + 'Source': string; + 'Unique Effect': string; + 'Idle Mode Sub Rate': number; + 'Races': number; + 'Race Wins': number; +} & Record & Record; + +export function supportVariables(career: ism.Career, lookupCard: (id: number) => support.SupportCard, lookupScenario: (id: number) => scenario.Scenario): SupportCardDatum[] { + const deck = career.progress_log_info.support_card_gain_info_array.map((g) => lookupCard(g.support_card_id)); + const scen = lookupScenario(career.end_info.chara_info.scenario_id); + return career.progress_log_info.support_card_gain_info_array.map((g, i) => { + const card = deck[i]; + const level = support.level(card.rarity, career.end_info.chara_info.support_card_array[i].exp); + const { effects, unique } = support.effects(lookupCard(g.support_card_id), level); + const indep = Object.fromEntries(INDEPENDENT.map((t) => [t.name, effects.find((e) => e.type === t.effect)?.value ?? 0])) as Record; + const dep = Object.fromEntries(DEPENDENT.map((t) => [t.name, t.map(g.gain_info, effects)])) as Record; + return { + 'Career': label(career), + 'Scenario': scen.name, + 'Source': card.name, + 'Unique Effect': unique?.description ?? 'None', + 'Idle Mode Sub Rate': unique?.idle_mode_sub_rate ?? 0, + 'Races': career.progress_log_info.race_history_array.length, + 'Race Wins': career.progress_log_info.race_history_array.reduce((w, r) => r.race_history.result_rank === 1 ? w + 1 : w, 0), + ...indep, + ...dep, + }; + }); +}