From c0dc3d0bc1e816d33d3266ef09e1f267f99755e9 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Sun, 30 Nov 2025 20:05:54 -0800 Subject: [PATCH] utils: update grid helpers and modification utilities --- src/lib/utils/gridHelpers.ts | 2 +- src/lib/utils/modificationDetector.ts | 52 ++++++++++ src/lib/utils/modificationFormatters.ts | 131 +++++++++++++----------- 3 files changed, 123 insertions(+), 62 deletions(-) diff --git a/src/lib/utils/gridHelpers.ts b/src/lib/utils/gridHelpers.ts index 3898b465..e2543725 100644 --- a/src/lib/utils/gridHelpers.ts +++ b/src/lib/utils/gridHelpers.ts @@ -19,7 +19,7 @@ export interface SlotRange { const GRID_CONFIGS: Record = { [GridType.Weapon]: { start: 0, end: 8, specialSlots: [-1] }, // mainhand + 9 grid slots [GridType.Summon]: { start: 0, end: 5, specialSlots: [-1, 6] }, // main + 6 grid + friend - [GridType.Character]: { start: 1, end: 4, specialSlots: [] } // 4 slots (1-4), position 0 is protagonist (not user-selectable) + [GridType.Character]: { start: 0, end: 4, specialSlots: [] } // 5 character slots (0-4) } /** diff --git a/src/lib/utils/modificationDetector.ts b/src/lib/utils/modificationDetector.ts index 9a6ad3ca..8e0b8156 100644 --- a/src/lib/utils/modificationDetector.ts +++ b/src/lib/utils/modificationDetector.ts @@ -94,3 +94,55 @@ export function hasAnyModification( ): boolean { 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 + */ +export function canWeaponBeModified(gridWeapon: GridWeapon | undefined): boolean { + if (!gridWeapon?.weapon) return false + + const weapon = gridWeapon.weapon + + // 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) + + // AX skills + const hasAxSkills = weapon.ax === true + + // Awakening (maxAwakeningLevel > 0 means it can have awakening) + const hasAwakening = (weapon.maxAwakeningLevel ?? 0) > 0 + + return canChangeElement || hasWeaponKeys || hasAxSkills || hasAwakening +} + +/** + * Check if a character CAN be modified (has modifiable properties) + * This is different from hasModifications which checks if it HAS been modified + */ +export function canCharacterBeModified(gridCharacter: GridCharacter | undefined): boolean { + if (!gridCharacter?.character) return false + + const character = gridCharacter.character + + // Awakening (maxAwakeningLevel > 0 means it can have awakening) + const hasAwakening = (character.maxAwakeningLevel ?? 0) > 0 + + // All characters can have rings (Over Mastery) + const canHaveRings = true + + // All characters can have earrings (Aetherial Mastery) + const canHaveEarring = true + + // Perpetuity is only for non-MC characters (position > 0) + const canHavePerpetuity = gridCharacter.position > 0 + + return hasAwakening || canHaveRings || canHaveEarring || canHavePerpetuity +} diff --git a/src/lib/utils/modificationFormatters.ts b/src/lib/utils/modificationFormatters.ts index 152bd8d8..a326bdf2 100644 --- a/src/lib/utils/modificationFormatters.ts +++ b/src/lib/utils/modificationFormatters.ts @@ -2,93 +2,102 @@ import type { SimpleAxSkill } from '$lib/types/api/entities' import { getRingStat, getEarringStat, getElementalizedEarringStat } from './masteryUtils' 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' + 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, - locale: 'en' | 'ja' = 'en' + modifier: number, + strength: number, + locale: 'en' | 'ja' = 'en' ): string { - const stat = getRingStat(modifier) - if (!stat) return `Unknown +${strength}` + const stat = getRingStat(modifier) + if (!stat) return `Unknown +${strength}` - const statName = stat.name[locale] - return `${statName} +${strength}${stat.suffix}` + const statName = stat.name[locale] + return `${statName} +${strength}${stat.suffix}` } export function formatEarringStat( - modifier: number, - strength: number, - locale: 'en' | 'ja' = 'en', - characterElement?: number + modifier: number, + strength: number, + locale: 'en' | 'ja' = 'en', + characterElement?: number ): string { - // Use elementalized version if element is provided and it's an element-specific stat - const stat = characterElement !== undefined && (modifier === 3 || modifier === 4) - ? getElementalizedEarringStat(modifier, characterElement, locale) - : getEarringStat(modifier) + // Use elementalized version if element is provided and it's an element-specific stat + const stat = + characterElement !== undefined && (modifier === 3 || modifier === 4) + ? getElementalizedEarringStat(modifier, characterElement, locale) + : getEarringStat(modifier) - if (!stat) return `Unknown +${strength}` + if (!stat) return `Unknown +${strength}` - const statName = stat.name[locale] - return `${statName} +${strength}${stat.suffix}` + const statName = stat.name[locale] + return `${statName} +${strength}${stat.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}` + 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' - } + switch (series) { + case 2: + return 'Pendulums & Chains' + case 3: + case 34: + return 'Telumas' + case 17: + return 'Ultima Keys' + case 22: + return 'Emblems' + default: + return 'Weapon Keys' + } } export function formatUncapLevel(level?: number | null): string { - if (level === undefined || level === null) return '0★' - return `${level}★` + if (level === undefined || level === null) return '0★' + return `${level}★` } export function formatTranscendenceStep(step?: number | null): string { - if (!step || step === 0) return '' - return `Stage ${step}` + if (!step || step === 0) return '' + return `Stage ${step}` } export function getStatModifierIcon(type: 'ring' | 'earring', modifier: number): string | null { - return 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 + 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' + } +}