diff --git a/src/lib/utils/modificationDetector.ts b/src/lib/utils/modificationDetector.ts index 8e0b8156..0354d32e 100644 --- a/src/lib/utils/modificationDetector.ts +++ b/src/lib/utils/modificationDetector.ts @@ -1,4 +1,5 @@ import type { GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party' +import { seriesHasWeaponKeys } from '$lib/utils/weaponSeries' export interface ModificationStatus { hasModifications: boolean @@ -95,10 +96,6 @@ export function hasAnyModification( return detectModifications(type, item).hasModifications } -// Weapon series that support weapon keys -// 2 = Dark Opus, 3 = Draconic, 17 = Ultima, 24 = Astral, 34 = Superlative -const WEAPON_KEY_SERIES = [2, 3, 17, 24, 34] - /** * Check if a weapon CAN be modified (has modifiable properties) * This is different from hasModifications which checks if it HAS been modified @@ -111,8 +108,8 @@ export function canWeaponBeModified(gridWeapon: GridWeapon | undefined): boolean // Element can be changed (element = 0 means "any element") const canChangeElement = weapon.element === 0 - // Weapon keys (series-specific) - const hasWeaponKeys = WEAPON_KEY_SERIES.includes(weapon.series) + // Weapon keys (series-specific) - use utility function that handles both formats + const hasWeaponKeys = seriesHasWeaponKeys(weapon.series) // AX skills const hasAxSkills = weapon.ax === true diff --git a/src/lib/utils/modificationFormatters.ts b/src/lib/utils/modificationFormatters.ts index a326bdf2..22194a26 100644 --- a/src/lib/utils/modificationFormatters.ts +++ b/src/lib/utils/modificationFormatters.ts @@ -1,5 +1,6 @@ import type { SimpleAxSkill } from '$lib/types/api/entities' import { getRingStat, getEarringStat, getElementalizedEarringStat } from './masteryUtils' +import { isWeaponSeriesRef, type WeaponSeriesRef } from '$lib/types/api/weaponSeries' const AX_SKILL_NAMES: Record = { 1: 'Attack', @@ -51,16 +52,21 @@ export function formatAxSkill(ax: SimpleAxSkill): string { return `${skillName} +${ax.strength}${suffix}` } -export function getWeaponKeyTitle(series?: number): string { - switch (series) { - case 2: +export function getWeaponKeyTitle(series?: WeaponSeriesRef | null): string { + if (!isWeaponSeriesRef(series)) { + return 'Weapon Keys' + } + + switch (series.slug) { + case 'dark-opus': return 'Pendulums & Chains' - case 3: - case 34: + case 'draconic': + case 'draconic-providence': + case 'superlative': return 'Telumas' - case 17: + case 'ultima': return 'Ultima Keys' - case 22: + case 'astral': return 'Emblems' default: return 'Weapon Keys' diff --git a/src/lib/utils/modifiers.ts b/src/lib/utils/modifiers.ts index 3fdc379a..65a8ec61 100644 --- a/src/lib/utils/modifiers.ts +++ b/src/lib/utils/modifiers.ts @@ -4,6 +4,7 @@ import type { Awakening, WeaponKey } from '$lib/types/api/entities' import type { SimpleAxSkill } from '$lib/types/SimpleAxSkill' +import { isWeaponSeriesRef, type WeaponSeriesRef } from '$lib/types/api/weaponSeries' import { getBasePath } from '$lib/utils/images' /** @@ -24,6 +25,16 @@ export function getAwakeningImage(awakening?: { type?: Awakening; level?: number return `${getBasePath()}/awakening/${slug}.${extension}` } +/** + * Helper to get series slug from WeaponSeriesRef + */ +function getSeriesSlug(series?: WeaponSeriesRef | null): string | undefined { + if (!isWeaponSeriesRef(series)) { + return undefined + } + return series.slug +} + /** * Get the image URL for a weapon key with proper element/proficiency/mod variants */ @@ -31,7 +42,7 @@ export function getWeaponKeyImage( key: WeaponKey, weaponElement?: number, weaponProficiency?: number, - weaponSeries?: number, + weaponSeries?: WeaponSeriesRef | null, weaponName?: { en?: string } ): string { if (!key.slug) return '' @@ -39,21 +50,24 @@ export function getWeaponKeyImage( const baseUrl = `${getBasePath()}/weapon-keys/` let filename = key.slug + // Get series slug for comparison + const seriesSlug = getSeriesSlug(weaponSeries) + // Handle element-specific telumas (Draconic weapons) const elementalTelumas = [15008, 16001, 16002] - const granblueId = parseInt(key.granblue_id || '0') + const granblueId = key.granblue_id ?? 0 if (elementalTelumas.includes(granblueId) && weaponElement) { filename += `-${weaponElement}` } // Handle proficiency-specific ultima keys (slot 0) - if (key.slot === 0 && weaponSeries === 17 && weaponProficiency) { + if (key.slot === 0 && seriesSlug === 'ultima' && weaponProficiency) { filename += `-${weaponProficiency}` } // Handle element-specific opus pendulums (slot 1) - if (weaponSeries === 2 && key.slot === 1 && weaponElement) { + if (seriesSlug === 'dark-opus' && key.slot === 1 && weaponElement) { const mod = weaponName?.en?.includes('Repudiation') ? 'primal' : 'magna' const suffixes = [ 'pendulum-strength', @@ -78,16 +92,19 @@ export function getWeaponKeyImage( export function getWeaponKeyImages( keys?: WeaponKey[], weaponElement?: number, - weaponProficiency?: number, - weaponSeries?: number, + weaponProficiency?: number | number[], + weaponSeries?: WeaponSeriesRef | null, weaponName?: { en?: string } ): Array<{ url: string; alt: string }> { if (!keys || keys.length === 0) return [] + // Handle proficiency being an array (take first element) + const proficiency = Array.isArray(weaponProficiency) ? weaponProficiency[0] : weaponProficiency + return keys .filter(key => key.slug) .map(key => ({ - url: getWeaponKeyImage(key, weaponElement, weaponProficiency, weaponSeries, weaponName), + url: getWeaponKeyImage(key, weaponElement, proficiency, weaponSeries, weaponName), alt: key.name?.en || key.slug || 'Weapon Key' })) } diff --git a/src/lib/utils/weaponSeries.ts b/src/lib/utils/weaponSeries.ts index 8292d511..158cffc9 100644 --- a/src/lib/utils/weaponSeries.ts +++ b/src/lib/utils/weaponSeries.ts @@ -2,90 +2,114 @@ * Weapon Series Utilities * * Provides helpers for weapon series identification and conflict messaging. + * Works with the API-driven WeaponSeriesRef type. * * @module utils/weaponSeries */ -export interface WeaponSeries { - id: number - slug: string -} +import type { WeaponSeriesRef } from '$lib/types/api/weaponSeries' +import { isWeaponSeriesRef } from '$lib/types/api/weaponSeries' /** - * All weapon series with their IDs and slugs. - * The slug is used for i18n message keys. - */ -export const weaponSeries: WeaponSeries[] = [ - { id: 0, slug: 'seraphic' }, - { id: 1, slug: 'grand' }, - { id: 2, slug: 'opus' }, - { id: 3, slug: 'draconic' }, - { id: 4, slug: 'revenant' }, - { id: 6, slug: 'primal' }, - { id: 7, slug: 'beast' }, - { id: 8, slug: 'regalia' }, - { id: 9, slug: 'omega' }, - { id: 10, slug: 'olden_primal' }, - { id: 11, slug: 'militis' }, - { id: 12, slug: 'hollowsky' }, - { id: 13, slug: 'xeno' }, - { id: 14, slug: 'astral' }, - { id: 15, slug: 'rose' }, - { id: 16, slug: 'bahamut' }, - { id: 17, slug: 'ultima' }, - { id: 18, slug: 'epic' }, - { id: 19, slug: 'ennead' }, - { id: 20, slug: 'cosmic' }, - { id: 21, slug: 'ancestral' }, - { id: 22, slug: 'superlative' }, - { id: 23, slug: 'vintage' }, - { id: 24, slug: 'class_champion' }, - { id: 25, slug: 'proving' }, - { id: 28, slug: 'sephira' }, - { id: 29, slug: 'new_world' }, - { id: 30, slug: 'disaster' }, - { id: 31, slug: 'illustrious' }, - { id: 32, slug: 'world' }, - { id: 34, slug: 'draconic_providence' } -] - -/** - * Series IDs that share the Opus/Draconic conflict rule. + * Slugs for series that share the Opus/Draconic conflict rule. * Only one weapon from these series can be in a party at a time. */ -export const OPUS_DRACONIC_SERIES = [2, 3, 34] +export const OPUS_DRACONIC_SLUGS = ['dark-opus', 'draconic', 'draconic-providence'] /** - * Get the slug for a weapon series by ID. + * Check if a series belongs to the Opus/Draconic conflict group. * - * @param id - The series ID - * @returns The series slug or undefined if not found - */ -export function getWeaponSeriesSlug(id: number): string | undefined { - return weaponSeries.find((s) => s.id === id)?.slug -} - -/** - * Check if a series ID belongs to the Opus/Draconic conflict group. - * - * @param seriesId - The series ID to check + * @param series - The series to check (WeaponSeriesRef or null) * @returns True if the series is Opus, Draconic, or Draconic Providence */ -export function isOpusDraconicSeries(seriesId: number): boolean { - return OPUS_DRACONIC_SERIES.includes(seriesId) +export function isOpusDraconicSeries(series: WeaponSeriesRef | null | undefined): boolean { + if (!isWeaponSeriesRef(series)) { + return false + } + return OPUS_DRACONIC_SLUGS.includes(series.slug) } /** - * Get all weapon series as options for a select/dropdown. + * Get the display name for a weapon series. * - * @returns Array of { value, label } options + * @param series - The weapon series reference + * @param locale - The locale to use ('en' or 'ja') + * @returns The localized series name, or 'Unknown' if not available */ -export function getWeaponSeriesOptions() { - return weaponSeries.map((series) => ({ - value: series.id, - label: series.slug - .split('_') - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' ') - })) +export function getSeriesDisplayName( + series: WeaponSeriesRef | null | undefined, + locale: 'en' | 'ja' = 'en' +): string { + if (!isWeaponSeriesRef(series)) { + return 'Unknown' + } + return series.name[locale] || series.name.en || 'Unknown' } + +/** + * Get the slug for a weapon series. + * + * @param series - The weapon series + * @returns The series slug or undefined + */ +export function getSeriesSlug(series: WeaponSeriesRef | null | undefined): string | undefined { + if (!isWeaponSeriesRef(series)) { + return undefined + } + return series.slug +} + +/** + * Check if a weapon series supports weapon keys. + * + * @param series - The weapon series + * @returns True if the series supports weapon keys + */ +export function seriesHasWeaponKeys(series: WeaponSeriesRef | null | undefined): boolean { + if (!isWeaponSeriesRef(series)) { + return false + } + return series.hasWeaponKeys +} + +/** + * Check if a weapon series supports awakening. + * + * @param series - The weapon series + * @returns True if the series supports awakening + */ +export function seriesHasAwakening(series: WeaponSeriesRef | null | undefined): boolean { + if (!isWeaponSeriesRef(series)) { + return false + } + return series.hasAwakening +} + +/** + * Check if a weapon series allows element changes. + * + * @param series - The weapon series + * @returns True if weapons in this series can have their element changed + */ +export function seriesIsElementChangeable(series: WeaponSeriesRef | null | undefined): boolean { + if (!isWeaponSeriesRef(series)) { + return false + } + return series.elementChangeable +} + +/** + * Check if a weapon series can be placed in extra grid slots. + * + * @param series - The weapon series + * @returns True if weapons in this series can be in extra slots + */ +export function seriesIsExtra(series: WeaponSeriesRef | null | undefined): boolean { + if (!isWeaponSeriesRef(series)) { + return false + } + return series.extra +} + +// Re-export the type guard for convenience +export { isWeaponSeriesRef } from '$lib/types/api/weaponSeries'