Compare commits

..
2 Commits
Author SHA1 Message Date
zephyr ad02e0711d zenno/chara/convo: include character icons
ci/woodpecker/push/zenno Pipeline failed
ci/woodpecker/push/horsebot Pipeline was successful
2026-09-06 14:21:09 -04:00
zephyr 869235a8c2 zenno/chara/convo: use condition_type
Fixes #8.
2026-09-06 11:56:50 -04:00
2 changed files with 70 additions and 39 deletions
+23 -1
View File
@@ -32,7 +32,29 @@ export interface Conversation {
/** /**
* Some unknown number in the game's local database. * Some unknown number in the game's local database.
*/ */
condition_type: 0 | 1 | 2 | 3 | 4; condition_type: Required;
}
export enum Required {
None = 0,
All = 1,
Chara1 = 2,
Chara2 = 3,
Chara3 = 4,
}
export function convoCharas(c: Conversation): number[] {
return [c.chara_1, c.chara_2, c.chara_3].filter((id) => id != null);
}
export function requiredCharas(c: Conversation): number[] {
switch (c.condition_type) {
case Required.None: return []
case Required.All: return convoCharas(c);
case Required.Chara1: return [c.chara_1];
case Required.Chara2: return [c.chara_2!];
case Required.Chara3: return [c.chara_3!];
}
} }
export async function conversation(): Promise<Conversation[]> { export async function conversation(): Promise<Conversation[]> {
+33 -24
View File
@@ -1,15 +1,24 @@
<script lang="ts"> <script lang="ts">
import { character, charaNames, type Character } from '$lib/data/character'; import { character, charaNames, type Character } from '$lib/data/character';
import { byChara, locations, groupPopulars, conversation, type Conversation } from '$lib/data/convo'; import {
byChara,
locations,
groupPopulars,
conversation,
type Conversation,
requiredCharas,
convoCharas,
} from '$lib/data/convo';
import CharaPick from '$lib/CharaPick.svelte'; import CharaPick from '$lib/CharaPick.svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import CharaIcon from '$lib/CharaIcon.svelte';
let conversations: Conversation[] = $state([]); let conversations: Conversation[] = $state([]);
const convoMap = $derived(byChara(conversations)); const convoMap = $derived(byChara(conversations));
const popular = $derived(groupPopulars(conversations)); const popular = $derived(groupPopulars(conversations));
let characters: Character[] = $state([]); let characters: Character[] = $state([]);
const selCharas = $derived(characters.filter((c) => convoMap.has(c.chara_id))); const selCharas = $derived(characters.filter((c) => convoMap.has(c.chara_id)));
const names = $derived(charaNames(characters ?? [])); const names = $derived(charaNames(characters));
const minSuggest = 8; const minSuggest = 8;
onMount(async () => { onMount(async () => {
@@ -21,11 +30,9 @@
const options = $derived(convoMap.get(chara?.chara_id ?? 0) ?? []); const options = $derived(convoMap.get(chara?.chara_id ?? 0) ?? []);
const cur = $derived(options.find((c) => c.number === convo)); const cur = $derived(options.find((c) => c.number === convo));
const cur1Name = $derived(cur?.chara_1 && names.get(cur.chara_1)?.en); const charaIDs = $derived(cur != null ? convoCharas(cur) : []);
const cur2Name = $derived(cur?.chara_2 && names.get(cur.chara_2)?.en); const charas = $derived(charaIDs.map((id) => names.get(id)!));
const cur3Name = $derived(cur?.chara_3 && names.get(cur.chara_3)?.en); const suggested = $derived.by(() => {
const alone = $derived([cur?.chara_1, cur?.chara_2, cur?.chara_3].filter((x) => x != null).length == 1 ? ' alone' : '');
const suggested = $derived.by(async () => {
if (cur == null) { if (cur == null) {
return []; return [];
} }
@@ -36,6 +43,7 @@
return r.map(([chara_id, count]) => ({ chara_id, chara_name: names.get(chara_id), count })); return r.map(([chara_id, count]) => ({ chara_id, chara_name: names.get(chara_id), count }));
}); });
const curLoc = $derived(cur != null ? locations[cur.location] : null); const curLoc = $derived(cur != null ? locations[cur.location] : null);
const req = $derived(cur != null ? requiredCharas(cur) : []);
</script> </script>
<h1 class="text-4xl">Lobby Conversations</h1> <h1 class="text-4xl">Lobby Conversations</h1>
@@ -53,23 +61,25 @@
</select> </select>
</div> </div>
</div> </div>
{#await suggested} {#if cur}
<div class="mt-4 w-full text-center text-3xl">Loading...</div>
{:then suggested}
{#if cur}
<div class="shadow-sm transition-shadow hover:shadow-md"> <div class="shadow-sm transition-shadow hover:shadow-md">
<div class="mt-8 flex text-center text-lg"> <div class="mt-8 flex flex-col text-center text-lg md:flex-row">
<span class="flex-1">{cur1Name}{alone}</span> {#each charaIDs as id, i (id)}
{#if cur2Name} <span class={{ 'flex-1': true, 'font-bold italic': req.includes(id) }}>
<span class="flex-1">{cur2Name}</span> <CharaIcon chara_id={id} size={64} />
{/if} {charas[i].en}
{#if cur3Name} </span>
<span class="flex-1">{cur3Name}</span> {/each}
{/if}
</div> </div>
<div class="flex w-full text-center text-lg"> <div class="block w-full text-center text-lg">
<span class="flex-1">at {curLoc?.name.en}</span> at the {curLoc?.name.en}
</div> </div>
{#if req.length > 0}
<span class="mx-auto mt-4 block text-center text-sm">
The conversation will not appear unless you own a trainee or support card of all characters with names in
<span class="font-bold italic">bold italics</span>.
</span>
{/if}
</div> </div>
<div class="mt-4 block text-center"> <div class="mt-4 block text-center">
<span>Other characters who appear here most often:</span> <span>Other characters who appear here most often:</span>
@@ -81,9 +91,8 @@
</div> </div>
<div class="mt-4 block text-center"> <div class="mt-4 block text-center">
<span> <span>
Set these characters to fixed positions (main, upgrades, story, races) to maximize the chance of getting this Setting these characters to fixed positions (main, upgrades, story, races) may maximize the chance of getting this
conversation. conversation.
</span> </span>
</div> </div>
{/if} {/if}
{/await}