utils: update grid helpers and modification utilities

This commit is contained in:
Justin Edmund 2025-11-30 20:05:54 -08:00
parent 8bfa31d925
commit c0dc3d0bc1
3 changed files with 123 additions and 62 deletions

View file

@ -19,7 +19,7 @@ export interface SlotRange {
const GRID_CONFIGS: Record<GridType, SlotRange> = {
[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)
}
/**

View file

@ -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
}

View file

@ -2,93 +2,102 @@ import type { SimpleAxSkill } from '$lib/types/api/entities'
import { getRingStat, getEarringStat, getElementalizedEarringStat } from './masteryUtils'
const AX_SKILL_NAMES: Record<number, string> = {
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'
}
}
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'
}
}