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> = { const GRID_CONFIGS: Record<GridType, SlotRange> = {
[GridType.Weapon]: { start: 0, end: 8, specialSlots: [-1] }, // mainhand + 9 grid slots [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.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 { ): boolean {
return detectModifications(type, item).hasModifications 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' import { getRingStat, getEarringStat, getElementalizedEarringStat } from './masteryUtils'
const AX_SKILL_NAMES: Record<number, string> = { const AX_SKILL_NAMES: Record<number, string> = {
1: 'Attack', 1: 'Attack',
2: 'HP', 2: 'HP',
3: 'Double Attack', 3: 'Double Attack',
4: 'Triple Attack', 4: 'Triple Attack',
5: 'C.A. DMG', 5: 'C.A. DMG',
6: 'C.A. DMG Cap', 6: 'C.A. DMG Cap',
7: 'Skill DMG', 7: 'Skill DMG',
8: 'Skill DMG Cap', 8: 'Skill DMG Cap',
9: 'Stamina', 9: 'Stamina',
10: 'Enmity', 10: 'Enmity',
11: 'Critical Hit' 11: 'Critical Hit'
} }
export function formatRingStat( export function formatRingStat(
modifier: number, modifier: number,
strength: number, strength: number,
locale: 'en' | 'ja' = 'en' locale: 'en' | 'ja' = 'en'
): string { ): string {
const stat = getRingStat(modifier) const stat = getRingStat(modifier)
if (!stat) return `Unknown +${strength}` if (!stat) return `Unknown +${strength}`
const statName = stat.name[locale] const statName = stat.name[locale]
return `${statName} +${strength}${stat.suffix}` return `${statName} +${strength}${stat.suffix}`
} }
export function formatEarringStat( export function formatEarringStat(
modifier: number, modifier: number,
strength: number, strength: number,
locale: 'en' | 'ja' = 'en', locale: 'en' | 'ja' = 'en',
characterElement?: number characterElement?: number
): string { ): string {
// Use elementalized version if element is provided and it's an element-specific stat // Use elementalized version if element is provided and it's an element-specific stat
const stat = characterElement !== undefined && (modifier === 3 || modifier === 4) const stat =
? getElementalizedEarringStat(modifier, characterElement, locale) characterElement !== undefined && (modifier === 3 || modifier === 4)
: getEarringStat(modifier) ? getElementalizedEarringStat(modifier, characterElement, locale)
: getEarringStat(modifier)
if (!stat) return `Unknown +${strength}` if (!stat) return `Unknown +${strength}`
const statName = stat.name[locale] const statName = stat.name[locale]
return `${statName} +${strength}${stat.suffix}` return `${statName} +${strength}${stat.suffix}`
} }
export function formatAxSkill(ax: SimpleAxSkill): string { export function formatAxSkill(ax: SimpleAxSkill): string {
const skillName = AX_SKILL_NAMES[ax.modifier] || `Unknown (${ax.modifier})` const skillName = AX_SKILL_NAMES[ax.modifier] || `Unknown (${ax.modifier})`
const suffix = ax.modifier <= 2 ? '' : '%' const suffix = ax.modifier <= 2 ? '' : '%'
return `${skillName} +${ax.strength}${suffix}` return `${skillName} +${ax.strength}${suffix}`
} }
export function getWeaponKeyTitle(series?: number): string { export function getWeaponKeyTitle(series?: number): string {
switch (series) { switch (series) {
case 2: case 2:
return 'Opus Pendulums' return 'Pendulums & Chains'
case 3: case 3:
case 34: case 34:
return 'Draconic Telumas' return 'Telumas'
case 17: case 17:
return 'Ultima Keys' return 'Ultima Keys'
case 22: case 22:
return 'Revans Emblems' return 'Emblems'
default: default:
return 'Weapon Keys' return 'Weapon Keys'
} }
} }
export function formatUncapLevel(level?: number | null): string { export function formatUncapLevel(level?: number | null): string {
if (level === undefined || level === null) return '0★' if (level === undefined || level === null) return '0★'
return `${level}` return `${level}`
} }
export function formatTranscendenceStep(step?: number | null): string { export function formatTranscendenceStep(step?: number | null): string {
if (!step || step === 0) return '' if (!step || step === 0) return ''
return `Stage ${step}` return `Stage ${step}`
} }
export function getStatModifierIcon(type: 'ring' | 'earring', modifier: number): string | null { export function getStatModifierIcon(type: 'ring' | 'earring', modifier: number): string | null {
return null return null
} }
export function getElementName(element?: number | null): string { export function getElementName(element?: number | null): string {
switch (element) { switch (element) {
case 0: return 'Null' case 0:
case 1: return 'Wind' return 'Null'
case 2: return 'Fire' case 1:
case 3: return 'Water' return 'Wind'
case 4: return 'Earth' case 2:
case 5: return 'Dark' return 'Fire'
case 6: return 'Light' case 3:
default: return 'Unknown' return 'Water'
} case 4:
return 'Earth'
case 5:
return 'Dark'
case 6:
return 'Light'
default:
return 'Unknown'
}
} }