show calculated skill values in dropdown, add level constraints

This commit is contained in:
Justin Edmund 2025-12-03 20:50:47 -08:00
parent 4b2d1b7dc0
commit f34f2c4dc9

View file

@ -3,13 +3,21 @@
<script lang="ts">
import DisclosureRow from '$lib/components/ui/DisclosureRow.svelte'
import Select from '$lib/components/ui/Select.svelte'
import type { ArtifactSkill, ArtifactSkillInstance } from '$lib/types/api/artifact'
import {
calculateSkillDisplayValue,
type ArtifactSkill,
type ArtifactSkillInstance
} from '$lib/types/api/artifact'
interface Props {
/** Slot number (1-4) */
slot: number
/** Current skill configuration (null if not set) */
skill: ArtifactSkillInstance | null
/** All skills array (for calculating level constraints) */
allSkills: (ArtifactSkillInstance | null)[]
/** Artifact level (1-5) - determines total skill level budget */
artifactLevel: number
/** Available skills for this slot (from query) */
availableSkills?: ArtifactSkill[]
/** Handler to open modifier selection pane */
@ -23,6 +31,8 @@
const {
slot,
skill,
allSkills,
artifactLevel,
availableSkills = [],
onSelectModifier,
onUpdateSkill,
@ -38,24 +48,57 @@
const modifierName = $derived(currentSkillDef?.name?.en ?? 'Unknown')
// Build strength options from the skill's baseValues
// Display values are calculated based on current level, but stored value is the base
const strengthOptions = $derived(() => {
if (!currentSkillDef?.baseValues) return []
const growth = currentSkillDef.growth ?? 0
const currentLevel = skill?.level ?? 1
const suffix = currentSkillDef.suffix?.en ?? ''
const sign = currentSkillDef.polarity === 'negative' ? '' : '+'
return currentSkillDef.baseValues
.map((v, i) => ({
value: v ?? 0,
label: `${v}${currentSkillDef.suffix?.en ?? ''}`
}))
.filter((opt) => opt.value !== 0)
.filter((v): v is number => v !== null && v !== 0)
.map((baseValue) => {
const displayValue = calculateSkillDisplayValue(baseValue, growth, currentLevel)
return {
value: baseValue, // Store the BASE value
label: `${sign}${displayValue}${suffix}` // Show polarity + calculated value
}
})
})
// Build level options (1-5)
const levelOptions = [
{ value: 1, label: '1' },
{ value: 2, label: '2' },
{ value: 3, label: '3' },
{ value: 4, label: '4' },
{ value: 5, label: '5' }
]
// Calculate the maximum level this skill can have based on:
// - Total budget: artifactLevel + 3
// - Levels used by other skills
// - Each skill must have at least level 1
const levelOptions = $derived(() => {
const totalBudget = artifactLevel + 3
const currentSlotIndex = slot - 1
// Sum levels of other skills (excluding this one)
const otherSkillsLevelSum = allSkills.reduce((sum, s, i) => {
if (i === currentSlotIndex || !s) return sum
return sum + s.level
}, 0)
// Count skills that are set (excluding this one) - they each need at least level 1
const otherSetSkillsCount = allSkills.filter((s, i) => i !== currentSlotIndex && s).length
// Remaining budget for this skill
// We need to reserve 1 level for each unset skill slot (since they'll need at least 1 when set)
const unsetSlotsCount = allSkills.filter((s, i) => i !== currentSlotIndex && !s).length
const reservedForUnset = unsetSlotsCount // Each unset slot will need at least 1 when filled
const maxLevel = Math.min(5, totalBudget - otherSkillsLevelSum - reservedForUnset)
const minLevel = 1
const options: { value: number; label: string }[] = []
for (let i = minLevel; i <= maxLevel; i++) {
options.push({ value: i, label: String(i) })
}
return options
})
function handleStrengthChange(newStrength: number) {
onUpdateSkill({ strength: newStrength })
@ -76,49 +119,34 @@
{disabled}
/>
{:else}
<!-- Set: Show modifier name + value/level controls -->
<!-- Set: Show modifier name (clickable) + value/level controls -->
<div class="skill-row-set" class:disabled>
<div class="skill-header">
<div class="modifier-info">
<span class="modifier-name">{modifierName}</span>
{#if currentSkillDef?.polarity === 'negative'}
<span class="polarity negative"></span>
{:else}
<span class="polarity positive">+</span>
{/if}
</div>
<button
type="button"
class="change-btn"
onclick={onSelectModifier}
{disabled}
>
Change
</button>
</div>
<DisclosureRow
label={modifierName}
onclick={onSelectModifier}
{disabled}
/>
<div class="skill-controls">
<div class="control-group">
<label class="control-label">Level</label>
<Select
value={skill.level}
options={levelOptions()}
contained
onValueChange={(v) => v !== undefined && handleLevelChange(v)}
{disabled}
/>
</div>
<div class="control-group">
<label class="control-label">Value</label>
<Select
value={skill.strength}
options={strengthOptions()}
size="small"
contained
onValueChange={(v) => v !== undefined && handleStrengthChange(v)}
{disabled}
/>
</div>
<div class="control-group">
<label class="control-label">Level</label>
<Select
value={skill.level}
options={levelOptions}
size="small"
contained
onValueChange={(v) => v !== undefined && handleLevelChange(v)}
{disabled}
/>
</div>
</div>
</div>
{/if}
@ -129,7 +157,6 @@
@use '$src/themes/spacing' as spacing;
@use '$src/themes/typography' as typography;
@use '$src/themes/layout' as layout;
@use '$src/themes/effects' as effects;
.skill-row {
// Minimal container for skill row
@ -150,60 +177,6 @@
}
}
.skill-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.modifier-info {
display: flex;
align-items: center;
gap: spacing.$unit-half;
}
.modifier-name {
font-weight: typography.$medium;
color: colors.$grey-20;
}
.polarity {
font-size: typography.$font-small;
font-weight: typography.$bold;
padding: 2px 4px;
border-radius: 4px;
&.positive {
background: colors.$wind-bg-20;
color: colors.$wind-text-20;
}
&.negative {
background: colors.$error--bg--light;
color: colors.$error;
}
}
.change-btn {
font-size: typography.$font-small;
color: colors.$blue;
background: none;
border: none;
cursor: pointer;
padding: spacing.$unit-fourth spacing.$unit-half;
border-radius: layout.$item-corner;
transition: background-color effects.$duration-quick ease;
&:hover {
background: colors.$grey-90;
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
}
}
.skill-controls {
display: flex;
gap: spacing.$unit-2x;