feat(isac): link skill chips to abilities
This commit is contained in:
@@ -44,6 +44,14 @@
|
||||
: 'TREE NODE'
|
||||
);
|
||||
const specializationRecord = $derived((currentSnapshot?.record ?? null) as Record<string, unknown> | null);
|
||||
const skillChipRecord = $derived((currentSnapshot?.record ?? null) as Record<string, unknown> | null);
|
||||
const skillChipFields = $derived([
|
||||
{ label: 'NAME', current: skillChipRecord?.name, proposed: proposed.name },
|
||||
{ label: 'SPECIALIZATION ABILITY', current: skillChipRecord?.specialization_ability_id, proposed: proposed.abilityId },
|
||||
{ label: '2-PIECE ATTRIBUTE', current: (currentSnapshot?.twoPiece as any)?.stat_attribute_id, proposed: proposed.twoPieceStatId },
|
||||
{ label: '2-PIECE RANGE', current: `${(currentSnapshot?.twoPiece as any)?.min_value ?? '—'} – ${(currentSnapshot?.twoPiece as any)?.max_value ?? '—'}`, proposed: `${proposed.twoPieceMin || '—'} – ${proposed.twoPieceMax || '—'}` },
|
||||
{ label: '3-PIECE TALENT', current: (currentSnapshot?.talent as any)?.name, proposed: proposed.talentName }
|
||||
]);
|
||||
const specializationFields = $derived(
|
||||
action === 'saveProgression'
|
||||
? [
|
||||
@@ -142,6 +150,29 @@
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
{:else if module === 'skillchips' && action === 'save'}
|
||||
<div class="proposal-compare-grid">
|
||||
<article class="proposal-editor-preview before">
|
||||
<header><span>CURRENT LIVE · SKILL CHIP</span><b>{skillChipRecord ? 'BEFORE' : 'NO LIVE RECORD'}</b></header>
|
||||
{#if skillChipRecord}
|
||||
<div class="proposal-field-summary">
|
||||
{#each skillChipFields as field}<div><span>{field.label}</span><strong>{shown(field.current)}</strong></div>{/each}
|
||||
<div><span>ROLL POOLS</span><strong>{(currentSnapshot?.primaryOptions as any[])?.length ?? 0} PRIMARY · {(currentSnapshot?.secondaryOptions as any[])?.length ?? 0} SECONDARY</strong></div>
|
||||
<div><span>TALENT STRUCTURE</span><strong>{(currentSnapshot?.talentTriggers as any[])?.length ?? 0} TRIGGERS · {(currentSnapshot?.talentEffects as any[])?.length ?? 0} EFFECTS</strong></div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="proposal-empty-state"><b>NEW SKILL CHIP</b><span>No live registry record will be replaced.</span></div>
|
||||
{/if}
|
||||
</article>
|
||||
<article class="proposal-editor-preview after">
|
||||
<header><span>PROPOSED · SKILL CHIP</span><b>AFTER</b></header>
|
||||
<div class="proposal-field-summary">
|
||||
{#each skillChipFields as field}<div><span>{field.label}</span><strong>{shown(field.proposed)}</strong></div>{/each}
|
||||
<div><span>ROLL POOLS</span><strong>{Array.isArray(proposed.primaryStatIds) ? proposed.primaryStatIds.length : Number(Boolean(proposed.primaryStatIds))} PRIMARY · {Array.isArray(proposed.secondaryStatIds) ? proposed.secondaryStatIds.length : Number(Boolean(proposed.secondaryStatIds))} SECONDARY</strong></div>
|
||||
<div><span>TALENT STRUCTURE</span><strong>{Array.isArray(proposed.triggerIds) ? proposed.triggerIds.length : Number(Boolean(proposed.triggerIds))} TRIGGERS · {((jsonValue(proposed.talentEffects) ?? []) as any[]).length} EFFECTS</strong></div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
{:else if module === 'specializations' && ['saveProgression','saveSpecialization','saveFocus','saveAbility','saveFocusAbilities','saveNode'].includes(action)}
|
||||
<div class="proposal-compare-grid specialization-proposal-preview">
|
||||
<article class="proposal-editor-preview before">
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { Database } from '@tursodatabase/database';
|
||||
|
||||
export async function refreshSpecializationFocusReady(database: Database, focusId: string) {
|
||||
const focus = await (await database.prepare(
|
||||
'SELECT id,active FROM specialization_focuses WHERE id=?'
|
||||
)).get(focusId);
|
||||
if (!focus) return { ready: false, pointTotal: 0, availablePoints: 0, abilityCount: 0, chipReadyCount: 0 };
|
||||
const [config, abilityCountRow, chipReadyRow, nodes, rankProfiles] = await Promise.all([
|
||||
(await database.prepare("SELECT xp_points,shd_cache_points FROM specialization_progression_config WHERE id='default'")).get(),
|
||||
(await database.prepare(`SELECT COUNT(*) AS count FROM specialization_focus_abilities link
|
||||
JOIN specialization_abilities ability ON ability.id=link.ability_id
|
||||
WHERE link.focus_id=? AND ability.active=1`)).get(focusId),
|
||||
(await database.prepare(`SELECT COUNT(*) AS count FROM specialization_focus_abilities link
|
||||
JOIN specialization_abilities ability ON ability.id=link.ability_id AND ability.active=1
|
||||
WHERE link.focus_id=? AND (
|
||||
SELECT COUNT(*) FROM skill_chip_sets chips
|
||||
WHERE chips.specialization_ability_id=ability.id AND chips.active=1
|
||||
)=3`)).get(focusId),
|
||||
(await database.prepare(
|
||||
'SELECT row_number,max_rank FROM specialization_tree_nodes WHERE focus_id=? AND active=1 ORDER BY row_number,column_number'
|
||||
)).all(focusId),
|
||||
(await database.prepare('SELECT rank,point_cost FROM specialization_rank_cost_profiles ORDER BY rank')).all()
|
||||
]);
|
||||
const pointForRank = new Map(rankProfiles.map((profile) => [Number(profile.rank), Number(profile.point_cost)]));
|
||||
const pointTotal = nodes.reduce((sum, node) => {
|
||||
for (let rank = 1; rank <= Number(node.max_rank); rank += 1) sum += pointForRank.get(rank) ?? rank;
|
||||
return sum;
|
||||
}, 0);
|
||||
const rows = [...new Set(nodes.map((node) => Number(node.row_number)))];
|
||||
const contiguousRows = rows.every((row, index) => row === index + 1);
|
||||
const abilityCount = Number(abilityCountRow?.count ?? 0);
|
||||
const chipReadyCount = Number(chipReadyRow?.count ?? 0);
|
||||
const availablePoints = Number(config?.xp_points ?? 30) + Number(config?.shd_cache_points ?? 19);
|
||||
const ready = Number(focus.active) === 1
|
||||
&& abilityCount === 3
|
||||
&& chipReadyCount === 3
|
||||
&& nodes.length > 0
|
||||
&& contiguousRows
|
||||
&& pointTotal === availablePoints;
|
||||
await (await database.prepare(
|
||||
'UPDATE specialization_focuses SET builder_ready=?,updated_at=? WHERE id=?'
|
||||
)).run(ready ? 1 : 0, new Date().toISOString(), focusId);
|
||||
return { ready, pointTotal, availablePoints, abilityCount, chipReadyCount };
|
||||
}
|
||||
|
||||
export async function refreshSpecializationFocuses(database: Database, specializationId: string) {
|
||||
const focuses = await (await database.prepare(
|
||||
'SELECT id FROM specialization_focuses WHERE specialization_id=?'
|
||||
)).all(specializationId);
|
||||
for (const focus of focuses) await refreshSpecializationFocusReady(database, String(focus.id));
|
||||
}
|
||||
|
||||
export async function refreshAbilityFocuses(database: Database, abilityId: string) {
|
||||
if (!abilityId) return;
|
||||
const focuses = await (await database.prepare(
|
||||
'SELECT focus_id FROM specialization_focus_abilities WHERE ability_id=?'
|
||||
)).all(abilityId);
|
||||
for (const focus of focuses) await refreshSpecializationFocusReady(database, String(focus.focus_id));
|
||||
}
|
||||
Reference in New Issue
Block a user