integrate befoulment into weapon edit views

- wire BefoulmentSelect into EditWeaponSidebar and WeaponEditPane
- update hasAxSkills logic to use series.augmentType
- add befoulment to save payloads and handlers
- add hasBefoulment to modificationDetector
- update TeamView to display befoulment with exorcism level
- fix story mocks
This commit is contained in:
Justin Edmund 2025-12-31 00:34:11 -08:00
parent afdeacd11e
commit 21e948be7e
6 changed files with 122 additions and 26 deletions

View file

@ -10,7 +10,7 @@
*/ */
import { onMount } from 'svelte' import { onMount } from 'svelte'
import type { CollectionWeapon } from '$lib/types/api/collection' import type { CollectionWeapon } from '$lib/types/api/collection'
import type { AugmentSkill } from '$lib/types/api/weaponStatModifier' import type { AugmentSkill, Befoulment } from '$lib/types/api/weaponStatModifier'
import { import {
useUpdateCollectionWeapon, useUpdateCollectionWeapon,
useRemoveWeaponFromCollection useRemoveWeaponFromCollection
@ -89,7 +89,8 @@
level: weapon.awakening.level level: weapon.awakening.level
} }
: null, : null,
axSkills: (weapon.ax as AugmentSkill[]) ?? [] axSkills: (weapon.ax as AugmentSkill[]) ?? [],
befoulment: (weapon.befoulment as Befoulment) ?? null
}) })
// Element name for theming // Element name for theming
@ -143,15 +144,22 @@
} }
// AX skills // AX skills
if (updates.axModifier1 !== undefined) { if (updates.axModifier1Id !== undefined) {
input.axModifier1 = updates.axModifier1 input.axModifier1Id = updates.axModifier1Id
input.axStrength1 = updates.axStrength1 input.axStrength1 = updates.axStrength1
} }
if (updates.axModifier2 !== undefined) { if (updates.axModifier2Id !== undefined) {
input.axModifier2 = updates.axModifier2 input.axModifier2Id = updates.axModifier2Id
input.axStrength2 = updates.axStrength2 input.axStrength2 = updates.axStrength2
} }
// Befoulment
if (updates.befoulmentModifierId !== undefined) {
input.befoulmentModifierId = updates.befoulmentModifierId
input.befoulmentStrength = updates.befoulmentStrength
input.exorcismLevel = updates.exorcismLevel
}
const updatedWeapon = await updateMutation.mutateAsync({ const updatedWeapon = await updateMutation.mutateAsync({
id: weapon.id, id: weapon.id,
input input

View file

@ -11,12 +11,13 @@
* - Awakening (for weapons with awakening support) * - Awakening (for weapons with awakening support)
*/ */
import type { Weapon, Awakening } from '$lib/types/api/entities' import type { Weapon, Awakening } from '$lib/types/api/entities'
import type { AugmentSkill } from '$lib/types/api/weaponStatModifier' import type { AugmentSkill, Befoulment } from '$lib/types/api/weaponStatModifier'
import DetailsSection from '$lib/components/sidebar/details/DetailsSection.svelte' import DetailsSection from '$lib/components/sidebar/details/DetailsSection.svelte'
import Select from '$lib/components/ui/Select.svelte' import Select from '$lib/components/ui/Select.svelte'
import WeaponKeySelect from '$lib/components/sidebar/edit/WeaponKeySelect.svelte' import WeaponKeySelect from '$lib/components/sidebar/edit/WeaponKeySelect.svelte'
import AwakeningSelect from '$lib/components/sidebar/edit/AwakeningSelect.svelte' import AwakeningSelect from '$lib/components/sidebar/edit/AwakeningSelect.svelte'
import AxSkillSelect from '$lib/components/sidebar/edit/AxSkillSelect.svelte' import AxSkillSelect from '$lib/components/sidebar/edit/AxSkillSelect.svelte'
import BefoulmentSelect from '$lib/components/sidebar/edit/BefoulmentSelect.svelte'
import UncapIndicator from '$lib/components/uncap/UncapIndicator.svelte' import UncapIndicator from '$lib/components/uncap/UncapIndicator.svelte'
import { getElementIcon } from '$lib/utils/images' import { getElementIcon } from '$lib/utils/images'
import { seriesHasWeaponKeys, getSeriesSlug } from '$lib/utils/weaponSeries' import { seriesHasWeaponKeys, getSeriesSlug } from '$lib/utils/weaponSeries'
@ -33,6 +34,7 @@
level: number level: number
} | null } | null
axSkills: AugmentSkill[] axSkills: AugmentSkill[]
befoulment?: Befoulment | null
} }
export interface WeaponEditUpdates { export interface WeaponEditUpdates {
@ -51,6 +53,9 @@
axStrength1?: number axStrength1?: number
axModifier2Id?: string axModifier2Id?: string
axStrength2?: number axStrength2?: number
befoulmentModifierId?: string
befoulmentStrength?: number
exorcismLevel?: number
} }
interface Props { interface Props {
@ -74,6 +79,7 @@
let selectedAwakening = $state<Awakening | undefined>(currentValues.awakening?.type) let selectedAwakening = $state<Awakening | undefined>(currentValues.awakening?.type)
let awakeningLevel = $state(currentValues.awakening?.level ?? 1) let awakeningLevel = $state(currentValues.awakening?.level ?? 1)
let axSkills = $state<AugmentSkill[]>(currentValues.axSkills ?? []) let axSkills = $state<AugmentSkill[]>(currentValues.axSkills ?? [])
let befoulment = $state<Befoulment | null>(currentValues.befoulment ?? null)
// Re-initialize when currentValues changes // Re-initialize when currentValues changes
$effect(() => { $effect(() => {
@ -86,6 +92,7 @@
selectedAwakening = currentValues.awakening?.type selectedAwakening = currentValues.awakening?.type
awakeningLevel = currentValues.awakening?.level ?? 1 awakeningLevel = currentValues.awakening?.level ?? 1
axSkills = currentValues.axSkills ?? [] axSkills = currentValues.axSkills ?? []
befoulment = currentValues.befoulment ?? null
}) })
// Derived conditions // Derived conditions
@ -106,8 +113,10 @@
const hasWeaponKeys = $derived(seriesHasWeaponKeys(series)) const hasWeaponKeys = $derived(seriesHasWeaponKeys(series))
const keySlotCount = $derived(seriesSlug ? (WEAPON_KEY_SLOTS[seriesSlug] ?? 2) : 0) const keySlotCount = $derived(seriesSlug ? (WEAPON_KEY_SLOTS[seriesSlug] ?? 2) : 0)
const hasAxSkills = $derived(weaponData?.ax === true) // Augment type from series determines AX skills vs befoulment
const axType = $derived(weaponData?.axType ?? 1) const augmentType = $derived(series?.augmentType ?? 'none')
const hasAxSkills = $derived(augmentType === 'ax')
const hasBefoulment = $derived(augmentType === 'befoulment')
const hasAwakening = $derived((weaponData?.maxAwakeningLevel ?? 0) > 0) const hasAwakening = $derived((weaponData?.maxAwakeningLevel ?? 0) > 0)
const availableAwakenings = $derived(weaponData?.awakenings ?? []) const availableAwakenings = $derived(weaponData?.awakenings ?? [])
@ -195,6 +204,15 @@
} }
} }
// Befoulment
if (hasBefoulment) {
if (befoulment?.modifier?.id) {
updates.befoulmentModifierId = befoulment.modifier.id
updates.befoulmentStrength = befoulment.strength
updates.exorcismLevel = befoulment.exorcismLevel
}
}
onSave?.(updates) onSave?.(updates)
} }
</script> </script>
@ -267,7 +285,6 @@
<DetailsSection title="AX Skills"> <DetailsSection title="AX Skills">
<div class="section-content"> <div class="section-content">
<AxSkillSelect <AxSkillSelect
{axType}
currentSkills={axSkills} currentSkills={axSkills}
onChange={(skills) => { onChange={(skills) => {
axSkills = skills axSkills = skills
@ -277,6 +294,19 @@
</DetailsSection> </DetailsSection>
{/if} {/if}
{#if hasBefoulment}
<DetailsSection title="Befoulment">
<div class="section-content">
<BefoulmentSelect
currentBefoulment={befoulment}
onChange={(bef) => {
befoulment = bef
}}
/>
</div>
</DetailsSection>
{/if}
{#if hasAwakening && availableAwakenings.length > 0} {#if hasAwakening && availableAwakenings.length > 0}
<DetailsSection title="Awakening"> <DetailsSection title="Awakening">
<div class="section-content"> <div class="section-content">

View file

@ -2,13 +2,14 @@
import type { GridWeapon } from '$lib/types/api/party' import type { GridWeapon } from '$lib/types/api/party'
import type { WeaponKey } from '$lib/api/adapters/entity.adapter' import type { WeaponKey } from '$lib/api/adapters/entity.adapter'
import type { Awakening } from '$lib/types/api/entities' import type { Awakening } from '$lib/types/api/entities'
import type { AugmentSkill } from '$lib/types/api/weaponStatModifier' import type { AugmentSkill, Befoulment } from '$lib/types/api/weaponStatModifier'
import DetailsSection from './details/DetailsSection.svelte' import DetailsSection from './details/DetailsSection.svelte'
import ItemHeader from './details/ItemHeader.svelte' import ItemHeader from './details/ItemHeader.svelte'
import Select from '$lib/components/ui/Select.svelte' import Select from '$lib/components/ui/Select.svelte'
import WeaponKeySelect from './edit/WeaponKeySelect.svelte' import WeaponKeySelect from './edit/WeaponKeySelect.svelte'
import AwakeningSelect from './edit/AwakeningSelect.svelte' import AwakeningSelect from './edit/AwakeningSelect.svelte'
import AxSkillSelect from './edit/AxSkillSelect.svelte' import AxSkillSelect from './edit/AxSkillSelect.svelte'
import BefoulmentSelect from './edit/BefoulmentSelect.svelte'
import Button from '$lib/components/ui/Button.svelte' import Button from '$lib/components/ui/Button.svelte'
import Icon from '$lib/components/Icon.svelte' import Icon from '$lib/components/Icon.svelte'
import { getElementIcon } from '$lib/utils/images' import { getElementIcon } from '$lib/utils/images'
@ -55,6 +56,9 @@
// AX skill state - initialize from existing AX skills // AX skill state - initialize from existing AX skills
let axSkills = $state<AugmentSkill[]>(weapon.ax ?? []) let axSkills = $state<AugmentSkill[]>(weapon.ax ?? [])
// Befoulment state - initialize from existing befoulment
let befoulment = $state<Befoulment | null>(weapon.befoulment ?? null)
// Weapon data shortcuts // Weapon data shortcuts
const weaponData = $derived(weapon.weapon) const weaponData = $derived(weapon.weapon)
const canChangeElement = $derived(weaponData?.element === 0) const canChangeElement = $derived(weaponData?.element === 0)
@ -77,8 +81,10 @@
const hasWeaponKeys = $derived(seriesHasWeaponKeys(series)) const hasWeaponKeys = $derived(seriesHasWeaponKeys(series))
const keySlotCount = $derived(seriesSlug ? (WEAPON_KEY_SLOTS[seriesSlug] ?? 2) : 0) const keySlotCount = $derived(seriesSlug ? (WEAPON_KEY_SLOTS[seriesSlug] ?? 2) : 0)
const hasAxSkills = $derived(weaponData?.ax === true) // Augment type from series determines AX skills vs befoulment
const axType = $derived(weaponData?.axType ?? 1) const augmentType = $derived(series?.augmentType ?? 'none')
const hasAxSkills = $derived(augmentType === 'ax')
const hasBefoulment = $derived(augmentType === 'befoulment')
const hasAwakening = $derived((weaponData?.maxAwakeningLevel ?? 0) > 0) const hasAwakening = $derived((weaponData?.maxAwakeningLevel ?? 0) > 0)
const availableAwakenings = $derived(weaponData?.awakenings ?? []) const availableAwakenings = $derived(weaponData?.awakenings ?? [])
@ -125,6 +131,9 @@
axStrength1?: number | null axStrength1?: number | null
axModifier2Id?: string | null axModifier2Id?: string | null
axStrength2?: number | null axStrength2?: number | null
befoulmentModifierId?: string | null
befoulmentStrength?: number | null
exorcismLevel?: number | null
} }
function handleSave() { function handleSave() {
@ -188,6 +197,21 @@
} }
} }
// Befoulment - send modifier ID, strength, and exorcism level
if (hasBefoulment) {
const originalBef = weapon.befoulment
if (befoulment?.modifier?.id !== originalBef?.modifier?.id) {
updates.befoulmentModifierId = befoulment?.modifier?.id ?? null
}
if (befoulment?.strength !== originalBef?.strength) {
updates.befoulmentStrength = befoulment?.strength ?? null
}
if (befoulment?.exorcismLevel !== originalBef?.exorcismLevel) {
updates.exorcismLevel = befoulment?.exorcismLevel ?? null
}
}
// Only call onSave if there are actual updates // Only call onSave if there are actual updates
if (Object.keys(updates).length > 0) { if (Object.keys(updates).length > 0) {
onSave?.(updates as Partial<GridWeapon>) onSave?.(updates as Partial<GridWeapon>)
@ -206,6 +230,7 @@
selectedAwakening = weapon.awakening?.type selectedAwakening = weapon.awakening?.type
awakeningLevel = weapon.awakening?.level ?? 1 awakeningLevel = weapon.awakening?.level ?? 1
axSkills = weapon.ax ?? [] axSkills = weapon.ax ?? []
befoulment = weapon.befoulment ?? null
onCancel?.() onCancel?.()
} }
</script> </script>
@ -286,7 +311,6 @@
<DetailsSection title="AX Skills"> <DetailsSection title="AX Skills">
<div class="ax-skills-wrapper"> <div class="ax-skills-wrapper">
<AxSkillSelect <AxSkillSelect
{axType}
currentSkills={axSkills} currentSkills={axSkills}
onChange={(skills) => { onChange={(skills) => {
axSkills = skills axSkills = skills
@ -296,6 +320,19 @@
</DetailsSection> </DetailsSection>
{/if} {/if}
{#if hasBefoulment}
<DetailsSection title="Befoulment">
<div class="befoulment-wrapper">
<BefoulmentSelect
currentBefoulment={befoulment}
onChange={(bef) => {
befoulment = bef
}}
/>
</div>
</DetailsSection>
{/if}
{#if hasAwakening && availableAwakenings.length > 0} {#if hasAwakening && availableAwakenings.length > 0}
<DetailsSection title="Awakening"> <DetailsSection title="Awakening">
<div class="awakening-select-wrapper"> <div class="awakening-select-wrapper">
@ -428,7 +465,8 @@
padding: spacing.$unit; padding: spacing.$unit;
} }
.ax-skills-wrapper { .ax-skills-wrapper,
.befoulment-wrapper {
padding: spacing.$unit; padding: spacing.$unit;
} }

View file

@ -105,17 +105,32 @@
</DetailsSection> </DetailsSection>
{/if} {/if}
{#if modificationStatus.hasAxSkills && weapon.ax} {#if modificationStatus.hasAxSkills && weapon.ax?.length}
<DetailsSection title="AX Skills"> <DetailsSection title="AX Skills">
{#each weapon.ax as axSkill} {#each weapon.ax as axSkill}
<DetailRow {#if axSkill.modifier?.id}
label={formatAxSkill(axSkill).split('+')[0]?.trim() ?? ''} <DetailRow
value={`+${axSkill.strength}${axSkill.modifier <= 2 ? '' : '%'}`} label={axSkill.modifier.nameEn}
/> value={`+${axSkill.strength}${axSkill.modifier.suffix ?? ''}`}
/>
{/if}
{/each} {/each}
</DetailsSection> </DetailsSection>
{/if} {/if}
{#if modificationStatus.hasBefoulment && weapon.befoulment?.modifier}
<DetailsSection title="Befoulment">
<DetailRow
label={weapon.befoulment.modifier.nameEn}
value={`${weapon.befoulment.strength}${weapon.befoulment.modifier.suffix ?? ''}`}
/>
<DetailRow
label="Exorcism Level"
value={`${weapon.befoulment.exorcismLevel ?? 0}`}
/>
</DetailsSection>
{/if}
{#if modificationStatus.hasElement && weapon.element} {#if modificationStatus.hasElement && weapon.element}
<DetailsSection title="Element Override"> <DetailsSection title="Element Override">
<DetailRow label="Weapon Element"> <DetailRow label="Weapon Element">

View file

@ -6,6 +6,7 @@ export interface ModificationStatus {
hasAwakening: boolean hasAwakening: boolean
hasWeaponKeys: boolean hasWeaponKeys: boolean
hasAxSkills: boolean hasAxSkills: boolean
hasBefoulment: boolean
hasRings: boolean hasRings: boolean
hasEarring: boolean hasEarring: boolean
hasPerpetuity: boolean hasPerpetuity: boolean
@ -25,6 +26,7 @@ export function detectModifications(
hasAwakening: false, hasAwakening: false,
hasWeaponKeys: false, hasWeaponKeys: false,
hasAxSkills: false, hasAxSkills: false,
hasBefoulment: false,
hasRings: false, hasRings: false,
hasEarring: false, hasEarring: false,
hasPerpetuity: false, hasPerpetuity: false,
@ -60,6 +62,7 @@ export function detectModifications(
status.hasAwakening = !!weapon.awakening status.hasAwakening = !!weapon.awakening
status.hasWeaponKeys = !!(weapon.weaponKeys && weapon.weaponKeys.length > 0) status.hasWeaponKeys = !!(weapon.weaponKeys && weapon.weaponKeys.length > 0)
status.hasAxSkills = !!(weapon.ax && weapon.ax.length > 0) status.hasAxSkills = !!(weapon.ax && weapon.ax.length > 0)
status.hasBefoulment = !!weapon.befoulment?.modifier
status.hasTranscendence = !!(weapon.transcendenceStep && weapon.transcendenceStep > 0) status.hasTranscendence = !!(weapon.transcendenceStep && weapon.transcendenceStep > 0)
status.hasUncapLevel = weapon.uncapLevel !== undefined && weapon.uncapLevel !== null status.hasUncapLevel = weapon.uncapLevel !== undefined && weapon.uncapLevel !== null
status.hasElement = !!(weapon.element && weapon.weapon?.element === 0) status.hasElement = !!(weapon.element && weapon.weapon?.element === 0)
@ -68,6 +71,7 @@ export function detectModifications(
status.hasAwakening || status.hasAwakening ||
status.hasWeaponKeys || status.hasWeaponKeys ||
status.hasAxSkills || status.hasAxSkills ||
status.hasBefoulment ||
status.hasTranscendence || status.hasTranscendence ||
status.hasUncapLevel || status.hasUncapLevel ||
status.hasElement status.hasElement
@ -111,13 +115,14 @@ export function canWeaponBeModified(gridWeapon: GridWeapon | undefined): boolean
// Weapon keys (series-specific) - use utility function that handles both formats // Weapon keys (series-specific) - use utility function that handles both formats
const hasWeaponKeys = seriesHasWeaponKeys(weapon.series) const hasWeaponKeys = seriesHasWeaponKeys(weapon.series)
// AX skills // AX skills or Befoulment - check augmentType from series
const hasAxSkills = weapon.ax === true const augmentType = weapon.series?.augmentType ?? 'none'
const hasAugments = augmentType !== 'none'
// Awakening (maxAwakeningLevel > 0 means it can have awakening) // Awakening (maxAwakeningLevel > 0 means it can have awakening)
const hasAwakening = (weapon.maxAwakeningLevel ?? 0) > 0 const hasAwakening = (weapon.maxAwakeningLevel ?? 0) > 0
return canChangeElement || hasWeaponKeys || hasAxSkills || hasAwakening return canChangeElement || hasWeaponKeys || hasAugments || hasAwakening
} }
/** /**

View file

@ -8,7 +8,7 @@ const mockOmegaSeries = {
name: { en: 'Omega', ja: 'マグナ' }, name: { en: 'Omega', ja: 'マグナ' },
hasWeaponKeys: false, hasWeaponKeys: false,
hasAwakening: true, hasAwakening: true,
hasAxSkills: true, augmentType: 'ax' as const,
extra: false, extra: false,
elementChangeable: false elementChangeable: false
}; };
@ -19,7 +19,7 @@ const mockOpusSeries = {
name: { en: 'Opus', ja: 'オプス' }, name: { en: 'Opus', ja: 'オプス' },
hasWeaponKeys: true, hasWeaponKeys: true,
hasAwakening: true, hasAwakening: true,
hasAxSkills: false, augmentType: 'none' as const,
extra: false, extra: false,
elementChangeable: false elementChangeable: false
}; };
@ -30,7 +30,7 @@ const mockDraconicSeries = {
name: { en: 'Draconic', ja: 'ドラゴニック' }, name: { en: 'Draconic', ja: 'ドラゴニック' },
hasWeaponKeys: true, hasWeaponKeys: true,
hasAwakening: false, hasAwakening: false,
hasAxSkills: false, augmentType: 'none' as const,
extra: false, extra: false,
elementChangeable: false elementChangeable: false
}; };