show calculated skill values in dropdown, add level constraints
This commit is contained in:
parent
4b2d1b7dc0
commit
f34f2c4dc9
1 changed files with 73 additions and 100 deletions
|
|
@ -3,13 +3,21 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import DisclosureRow from '$lib/components/ui/DisclosureRow.svelte'
|
import DisclosureRow from '$lib/components/ui/DisclosureRow.svelte'
|
||||||
import Select from '$lib/components/ui/Select.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 {
|
interface Props {
|
||||||
/** Slot number (1-4) */
|
/** Slot number (1-4) */
|
||||||
slot: number
|
slot: number
|
||||||
/** Current skill configuration (null if not set) */
|
/** Current skill configuration (null if not set) */
|
||||||
skill: ArtifactSkillInstance | null
|
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) */
|
/** Available skills for this slot (from query) */
|
||||||
availableSkills?: ArtifactSkill[]
|
availableSkills?: ArtifactSkill[]
|
||||||
/** Handler to open modifier selection pane */
|
/** Handler to open modifier selection pane */
|
||||||
|
|
@ -23,6 +31,8 @@
|
||||||
const {
|
const {
|
||||||
slot,
|
slot,
|
||||||
skill,
|
skill,
|
||||||
|
allSkills,
|
||||||
|
artifactLevel,
|
||||||
availableSkills = [],
|
availableSkills = [],
|
||||||
onSelectModifier,
|
onSelectModifier,
|
||||||
onUpdateSkill,
|
onUpdateSkill,
|
||||||
|
|
@ -38,24 +48,57 @@
|
||||||
const modifierName = $derived(currentSkillDef?.name?.en ?? 'Unknown')
|
const modifierName = $derived(currentSkillDef?.name?.en ?? 'Unknown')
|
||||||
|
|
||||||
// Build strength options from the skill's baseValues
|
// 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(() => {
|
const strengthOptions = $derived(() => {
|
||||||
if (!currentSkillDef?.baseValues) return []
|
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
|
return currentSkillDef.baseValues
|
||||||
.map((v, i) => ({
|
.filter((v): v is number => v !== null && v !== 0)
|
||||||
value: v ?? 0,
|
.map((baseValue) => {
|
||||||
label: `${v}${currentSkillDef.suffix?.en ?? ''}`
|
const displayValue = calculateSkillDisplayValue(baseValue, growth, currentLevel)
|
||||||
}))
|
return {
|
||||||
.filter((opt) => opt.value !== 0)
|
value: baseValue, // Store the BASE value
|
||||||
|
label: `${sign}${displayValue}${suffix}` // Show polarity + calculated value
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Build level options (1-5)
|
// Calculate the maximum level this skill can have based on:
|
||||||
const levelOptions = [
|
// - Total budget: artifactLevel + 3
|
||||||
{ value: 1, label: '1' },
|
// - Levels used by other skills
|
||||||
{ value: 2, label: '2' },
|
// - Each skill must have at least level 1
|
||||||
{ value: 3, label: '3' },
|
const levelOptions = $derived(() => {
|
||||||
{ value: 4, label: '4' },
|
const totalBudget = artifactLevel + 3
|
||||||
{ value: 5, label: '5' }
|
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) {
|
function handleStrengthChange(newStrength: number) {
|
||||||
onUpdateSkill({ strength: newStrength })
|
onUpdateSkill({ strength: newStrength })
|
||||||
|
|
@ -76,49 +119,34 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{: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-row-set" class:disabled>
|
||||||
<div class="skill-header">
|
<DisclosureRow
|
||||||
<div class="modifier-info">
|
label={modifierName}
|
||||||
<span class="modifier-name">{modifierName}</span>
|
onclick={onSelectModifier}
|
||||||
{#if currentSkillDef?.polarity === 'negative'}
|
{disabled}
|
||||||
<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>
|
|
||||||
<div class="skill-controls">
|
<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">
|
<div class="control-group">
|
||||||
<label class="control-label">Value</label>
|
<label class="control-label">Value</label>
|
||||||
<Select
|
<Select
|
||||||
value={skill.strength}
|
value={skill.strength}
|
||||||
options={strengthOptions()}
|
options={strengthOptions()}
|
||||||
size="small"
|
|
||||||
contained
|
contained
|
||||||
onValueChange={(v) => v !== undefined && handleStrengthChange(v)}
|
onValueChange={(v) => v !== undefined && handleStrengthChange(v)}
|
||||||
{disabled}
|
{disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -129,7 +157,6 @@
|
||||||
@use '$src/themes/spacing' as spacing;
|
@use '$src/themes/spacing' as spacing;
|
||||||
@use '$src/themes/typography' as typography;
|
@use '$src/themes/typography' as typography;
|
||||||
@use '$src/themes/layout' as layout;
|
@use '$src/themes/layout' as layout;
|
||||||
@use '$src/themes/effects' as effects;
|
|
||||||
|
|
||||||
.skill-row {
|
.skill-row {
|
||||||
// Minimal container for 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 {
|
.skill-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: spacing.$unit-2x;
|
gap: spacing.$unit-2x;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue