diff --git a/src/lib/utils/modificationDetector.ts b/src/lib/utils/modificationDetector.ts new file mode 100644 index 00000000..ecd889fa --- /dev/null +++ b/src/lib/utils/modificationDetector.ts @@ -0,0 +1,99 @@ +import type { GridCharacter, GridWeapon, GridSummon } from '$lib/types/api/party' + +export interface ModificationStatus { + hasModifications: boolean + hasAwakening: boolean + hasWeaponKeys: boolean + hasAxSkills: boolean + hasRings: boolean + hasEarring: boolean + hasPerpetuity: boolean + hasTranscendence: boolean + hasUncapLevel: boolean + hasElement: boolean + hasQuickSummon: boolean + hasFriendSummon: boolean +} + +export function detectModifications( + type: 'character' | 'weapon' | 'summon', + item: GridCharacter | GridWeapon | GridSummon | undefined +): ModificationStatus { + const status: ModificationStatus = { + hasModifications: false, + hasAwakening: false, + hasWeaponKeys: false, + hasAxSkills: false, + hasRings: false, + hasEarring: false, + hasPerpetuity: false, + hasTranscendence: false, + hasUncapLevel: false, + hasElement: false, + hasQuickSummon: false, + hasFriendSummon: false + } + + if (!item) return status + + if (type === 'character') { + const char = item as GridCharacter + + status.hasAwakening = !!char.awakening + status.hasRings = !!(char.rings && char.rings.length > 0) || + !!(char.over_mastery && char.over_mastery.length > 0) + status.hasEarring = !!char.earring || !!char.aetherial_mastery + status.hasPerpetuity = !!char.perpetuity + status.hasTranscendence = !!(char.transcendenceStep && char.transcendenceStep > 0) + status.hasUncapLevel = char.uncapLevel !== undefined && char.uncapLevel !== null + + status.hasModifications = + status.hasAwakening || + status.hasRings || + status.hasEarring || + status.hasPerpetuity || + status.hasTranscendence || + status.hasUncapLevel + + } else if (type === 'weapon') { + const weapon = item as GridWeapon + + status.hasAwakening = !!weapon.awakening + status.hasWeaponKeys = !!(weapon.weaponKeys && weapon.weaponKeys.length > 0) + status.hasAxSkills = !!(weapon.ax && weapon.ax.length > 0) + status.hasTranscendence = !!(weapon.transcendenceStep && weapon.transcendenceStep > 0) + status.hasUncapLevel = weapon.uncapLevel !== undefined && weapon.uncapLevel !== null + status.hasElement = !!(weapon.element && weapon.weapon?.element === 0) + + status.hasModifications = + status.hasAwakening || + status.hasWeaponKeys || + status.hasAxSkills || + status.hasTranscendence || + status.hasUncapLevel || + status.hasElement + + } else if (type === 'summon') { + const summon = item as GridSummon + + status.hasTranscendence = !!(summon.transcendenceStep && summon.transcendenceStep > 0) + status.hasUncapLevel = summon.uncapLevel !== undefined && summon.uncapLevel !== null + status.hasQuickSummon = !!summon.quickSummon + status.hasFriendSummon = !!summon.friend + + status.hasModifications = + status.hasTranscendence || + status.hasUncapLevel || + status.hasQuickSummon || + status.hasFriendSummon + } + + return status +} + +export function hasAnyModification( + type: 'character' | 'weapon' | 'summon', + item: GridCharacter | GridWeapon | GridSummon | undefined +): boolean { + return detectModifications(type, item).hasModifications +} \ No newline at end of file diff --git a/src/lib/utils/modificationFormatters.ts b/src/lib/utils/modificationFormatters.ts new file mode 100644 index 00000000..b1453dec --- /dev/null +++ b/src/lib/utils/modificationFormatters.ts @@ -0,0 +1,115 @@ +import type { SimpleAxSkill } from '$lib/types/api/entities' + +const RING_STAT_NAMES: Record = { + 1: 'HP', + 2: 'Attack', + 3: 'Double Attack', + 4: 'Triple Attack', + 5: 'Elemental Attack', + 6: 'Critical Hit', + 7: 'Skill Damage', + 8: 'Skill DMG Cap', + 9: 'Ougi Damage', + 10: 'Ougi DMG Cap', + 11: 'Chain Burst DMG', + 12: 'Chain Burst Cap', + 13: 'Healing', + 14: 'Healing Cap', + 15: 'Stamina', + 16: 'Enmity', + 17: 'Debuff Success' +} + +const EARRING_STAT_NAMES: Record = { + 1: 'HP', + 2: 'Attack', + 3: 'Defense', + 4: 'Double Attack', + 5: 'Triple Attack', + 6: 'Elemental Attack', + 7: 'Critical Hit', + 8: 'Skill Damage', + 9: 'Skill DMG Cap', + 10: 'Ougi Damage', + 11: 'Ougi DMG Cap', + 12: 'Auto Attack Cap', + 13: 'Chain Burst DMG', + 14: 'Chain Burst Cap', + 15: 'Healing', + 16: 'Healing Cap' +} + +const AX_SKILL_NAMES: Record = { + 1: 'Attack', + 2: 'HP', + 3: 'Double Attack', + 4: 'Triple Attack', + 5: 'C.A. DMG', + 6: 'C.A. DMG Cap', + 7: 'Skill DMG', + 8: 'Skill DMG Cap', + 9: 'Stamina', + 10: 'Enmity', + 11: 'Critical Hit' +} + +export function formatRingStat(modifier: number, strength: number): string { + const statName = RING_STAT_NAMES[modifier] || `Unknown (${modifier})` + const suffix = modifier <= 2 ? '' : '%' + return `${statName} +${strength}${suffix}` +} + +export function formatEarringStat(modifier: number, strength: number): string { + const statName = EARRING_STAT_NAMES[modifier] || `Unknown (${modifier})` + const suffix = modifier <= 3 ? '' : '%' + return `${statName} +${strength}${suffix}` +} + +export function formatAxSkill(ax: SimpleAxSkill): string { + const skillName = AX_SKILL_NAMES[ax.modifier] || `Unknown (${ax.modifier})` + const suffix = ax.modifier <= 2 ? '' : '%' + return `${skillName} +${ax.strength}${suffix}` +} + +export function getWeaponKeyTitle(series?: number): string { + switch (series) { + case 2: + return 'Opus Pendulums' + case 3: + case 34: + return 'Draconic Telumas' + case 17: + return 'Ultima Keys' + case 22: + return 'Revans Emblems' + default: + return 'Weapon Keys' + } +} + +export function formatUncapLevel(level?: number | null): string { + if (level === undefined || level === null) return '0★' + return `${level}★` +} + +export function formatTranscendenceStep(step?: number | null): string { + if (!step || step === 0) return '' + return `Stage ${step}` +} + +export function getStatModifierIcon(type: 'ring' | 'earring', modifier: number): string | null { + return null +} + +export function getElementName(element?: number | null): string { + switch (element) { + case 0: return 'Null' + case 1: return 'Wind' + case 2: return 'Fire' + case 3: return 'Water' + case 4: return 'Earth' + case 5: return 'Dark' + case 6: return 'Light' + default: return 'Unknown' + } +} \ No newline at end of file diff --git a/src/lib/utils/modifiers.ts b/src/lib/utils/modifiers.ts new file mode 100644 index 00000000..ac84234a --- /dev/null +++ b/src/lib/utils/modifiers.ts @@ -0,0 +1,100 @@ +/** + * Utility functions for weapon and character modifiers (awakenings, weapon keys, AX skills) + */ + +import type { Awakening, WeaponKey, SimpleAxSkill } from '$lib/types' + +/** + * Get the image URL for an awakening type + */ +export function getAwakeningImage(awakening?: { type?: Awakening; level?: number }): string | null { + if (!awakening?.type?.slug) return null + return `/images/awakening/${awakening.type.slug}.png` +} + +/** + * Get the image URL for a weapon key with proper element/proficiency/mod variants + */ +export function getWeaponKeyImage( + key: WeaponKey, + weaponElement?: number, + weaponProficiency?: number, + weaponSeries?: number, + weaponName?: { en?: string } +): string { + if (!key.slug) return '' + + const baseUrl = '/images/weapon-keys/' + let filename = key.slug + + // Handle element-specific telumas (Draconic weapons) + const elementalTelumas = [15008, 16001, 16002] + const granblueId = parseInt(key.granblueId || '0') + + if (elementalTelumas.includes(granblueId) && weaponElement) { + filename += `-${weaponElement}` + } + + // Handle proficiency-specific ultima keys (slot 0) + if (key.slot === 0 && weaponSeries === 17 && weaponProficiency) { + filename += `-${weaponProficiency}` + } + + // Handle element-specific opus pendulums (slot 1) + if (weaponSeries === 2 && key.slot === 1 && weaponElement) { + const mod = weaponName?.en?.includes('Repudiation') ? 'primal' : 'magna' + const suffixes = [ + 'pendulum-strength', + 'pendulum-zeal', + 'pendulum-strife', + 'chain-temperament', + 'chain-restoration', + 'chain-glorification' + ] + + if (suffixes.includes(key.slug)) { + filename += `-${mod}-${weaponElement}` + } + } + + return `${baseUrl}${filename}.png` +} + +/** + * Get all weapon key images for a weapon + */ +export function getWeaponKeyImages( + keys?: WeaponKey[], + weaponElement?: number, + weaponProficiency?: number, + weaponSeries?: number, + weaponName?: { en?: string } +): Array<{ url: string; alt: string }> { + if (!keys || keys.length === 0) return [] + + return keys + .filter(key => key.slug) + .map(key => ({ + url: getWeaponKeyImage(key, weaponElement, weaponProficiency, weaponSeries, weaponName), + alt: key.name?.en || key.slug || 'Weapon Key' + })) +} + +/** + * Get the image URL for an AX skill + * Note: Requires ax data reference implementation + */ +export function getAxSkillImage(axSkill?: { slug?: string }): string | null { + if (!axSkill?.slug) return null + return `/images/ax/${axSkill.slug}.png` +} + +/** + * Get all AX skill images for a weapon + * Note: This is a placeholder until ax data structure is fully implemented + */ +export function getAxSkillImages(ax?: SimpleAxSkill[]): Array<{ url: string; alt: string }> { + // TODO: Implement when ax data reference is available + // This would need to map ax modifiers to actual ax skill data + return [] +} \ No newline at end of file