add artifact validation utilities
This commit is contained in:
parent
2b572d07a7
commit
e502c8128d
1 changed files with 218 additions and 0 deletions
218
src/lib/utils/artifactValidation.ts
Normal file
218
src/lib/utils/artifactValidation.ts
Normal file
|
|
@ -0,0 +1,218 @@
|
||||||
|
/**
|
||||||
|
* Artifact Validation Utilities
|
||||||
|
*
|
||||||
|
* Business rules for artifact validation:
|
||||||
|
* 1. Skill Level Sum: sum of skill levels === artifact_level + 3
|
||||||
|
* 2. No Duplicate Modifiers: Skills 1 & 2 cannot share the same modifier
|
||||||
|
* 3. Quirk Artifacts: Always level 1, no skills, proficiency on instance
|
||||||
|
* 4. Character Compatibility: Element + proficiency must match
|
||||||
|
* 5. Slot→Group Mapping: 1-2 → group_i, 3 → group_ii, 4 → group_iii
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ArtifactSkillInstance,
|
||||||
|
ArtifactSkillGroup,
|
||||||
|
GridArtifact,
|
||||||
|
CollectionArtifact
|
||||||
|
} from '$lib/types/api/artifact'
|
||||||
|
import type { Character } from '$lib/types/api/entities'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element enum values
|
||||||
|
* Used for filtering and matching
|
||||||
|
*/
|
||||||
|
export const ELEMENT_VALUES = {
|
||||||
|
NULL: 0,
|
||||||
|
WIND: 1,
|
||||||
|
FIRE: 2,
|
||||||
|
WATER: 3,
|
||||||
|
EARTH: 4,
|
||||||
|
DARK: 5,
|
||||||
|
LIGHT: 6
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps skill slot (1-4) to skill group
|
||||||
|
* Slots 1-2: group_i
|
||||||
|
* Slot 3: group_ii
|
||||||
|
* Slot 4: group_iii
|
||||||
|
*/
|
||||||
|
export function getSkillGroupForSlot(slot: number): ArtifactSkillGroup {
|
||||||
|
const groupMap: Record<number, ArtifactSkillGroup> = {
|
||||||
|
1: 'group_i',
|
||||||
|
2: 'group_i',
|
||||||
|
3: 'group_ii',
|
||||||
|
4: 'group_iii'
|
||||||
|
}
|
||||||
|
const group = groupMap[slot]
|
||||||
|
if (!group) {
|
||||||
|
throw new Error(`Invalid slot number: ${slot}. Must be 1-4.`)
|
||||||
|
}
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that the sum of skill levels equals artifact_level + 3
|
||||||
|
*
|
||||||
|
* @param artifactLevel - The artifact's level (1-20)
|
||||||
|
* @param skills - Array of skill instances (may contain nulls)
|
||||||
|
* @returns true if valid, false otherwise
|
||||||
|
*/
|
||||||
|
export function validateSkillLevelSum(
|
||||||
|
artifactLevel: number,
|
||||||
|
skills: (ArtifactSkillInstance | null)[]
|
||||||
|
): boolean {
|
||||||
|
const expectedSum = artifactLevel + 3
|
||||||
|
const actualSum = skills.reduce((sum, skill) => sum + (skill?.level ?? 0), 0)
|
||||||
|
return actualSum === expectedSum
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the expected total skill level sum for a given artifact level
|
||||||
|
*/
|
||||||
|
export function getExpectedSkillLevelSum(artifactLevel: number): number {
|
||||||
|
return artifactLevel + 3
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current total of all skill levels
|
||||||
|
*/
|
||||||
|
export function getCurrentSkillLevelSum(skills: (ArtifactSkillInstance | null)[]): number {
|
||||||
|
return skills.reduce((sum, skill) => sum + (skill?.level ?? 0), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that skills 1 & 2 don't have the same modifier
|
||||||
|
*
|
||||||
|
* @param skill1 - First skill instance
|
||||||
|
* @param skill2 - Second skill instance
|
||||||
|
* @returns true if valid (different modifiers or at least one null), false if duplicate
|
||||||
|
*/
|
||||||
|
export function validateDuplicateModifiers(
|
||||||
|
skill1?: ArtifactSkillInstance | null,
|
||||||
|
skill2?: ArtifactSkillInstance | null
|
||||||
|
): boolean {
|
||||||
|
// If either skill is null/undefined, no conflict
|
||||||
|
if (!skill1?.modifier || !skill2?.modifier) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Modifiers must be different
|
||||||
|
return skill1.modifier !== skill2.modifier
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an artifact is compatible with a character
|
||||||
|
*
|
||||||
|
* Compatibility rules:
|
||||||
|
* - Element must match character's element (unless artifact element is 0/null)
|
||||||
|
* - Proficiency must match character's proficiency1 or proficiency2
|
||||||
|
*
|
||||||
|
* @param artifact - The artifact to check
|
||||||
|
* @param character - The character to check against
|
||||||
|
* @returns true if compatible, false otherwise
|
||||||
|
*/
|
||||||
|
export function isCompatibleWithCharacter(
|
||||||
|
artifact: GridArtifact | CollectionArtifact,
|
||||||
|
character: Character
|
||||||
|
): boolean {
|
||||||
|
// Element check (0 is universal)
|
||||||
|
if (artifact.element !== 0 && artifact.element !== character.element) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proficiency check
|
||||||
|
const artifactProficiency = getArtifactProficiency(artifact)
|
||||||
|
if (artifactProficiency === undefined) {
|
||||||
|
// No proficiency requirement
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Character proficiency is an array of up to 2 proficiencies
|
||||||
|
const charProficiencies = character.proficiency ?? []
|
||||||
|
|
||||||
|
if (!charProficiencies.includes(artifactProficiency)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the proficiency value from an artifact
|
||||||
|
* For standard artifacts, uses artifact.artifact.proficiency
|
||||||
|
* For quirk artifacts, uses artifact.proficiency (instance level)
|
||||||
|
*/
|
||||||
|
export function getArtifactProficiency(
|
||||||
|
artifact: GridArtifact | CollectionArtifact
|
||||||
|
): number | undefined {
|
||||||
|
const isQuirk = artifact.artifact?.rarity === 'quirk'
|
||||||
|
const proficiency = isQuirk ? artifact.proficiency : artifact.artifact?.proficiency
|
||||||
|
return proficiency ?? undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an artifact is a quirk artifact
|
||||||
|
*/
|
||||||
|
export function isQuirkArtifact(artifact: GridArtifact | CollectionArtifact): boolean {
|
||||||
|
return artifact.artifact?.rarity === 'quirk'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates all skills for an artifact
|
||||||
|
*
|
||||||
|
* @returns Object with validation results
|
||||||
|
*/
|
||||||
|
export function validateArtifactSkills(
|
||||||
|
artifactLevel: number,
|
||||||
|
skills: (ArtifactSkillInstance | null)[]
|
||||||
|
): {
|
||||||
|
isValid: boolean
|
||||||
|
levelSumValid: boolean
|
||||||
|
noDuplicates: boolean
|
||||||
|
currentLevelSum: number
|
||||||
|
expectedLevelSum: number
|
||||||
|
errors: string[]
|
||||||
|
} {
|
||||||
|
const expectedLevelSum = getExpectedSkillLevelSum(artifactLevel)
|
||||||
|
const currentLevelSum = getCurrentSkillLevelSum(skills)
|
||||||
|
const levelSumValid = currentLevelSum === expectedLevelSum
|
||||||
|
const noDuplicates = validateDuplicateModifiers(skills[0], skills[1])
|
||||||
|
|
||||||
|
const errors: string[] = []
|
||||||
|
if (!levelSumValid) {
|
||||||
|
errors.push(
|
||||||
|
`Skill level sum (${currentLevelSum}) must equal artifact level + 3 (${expectedLevelSum})`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (!noDuplicates) {
|
||||||
|
errors.push('Skills 1 and 2 cannot have the same modifier')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isValid: levelSumValid && noDuplicates,
|
||||||
|
levelSumValid,
|
||||||
|
noDuplicates,
|
||||||
|
currentLevelSum,
|
||||||
|
expectedLevelSum,
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates available level points that can be distributed to skills
|
||||||
|
* Based on: total = artifact_level + 3, distributed among 4 skills
|
||||||
|
*/
|
||||||
|
export function calculateAvailableLevelPoints(
|
||||||
|
artifactLevel: number,
|
||||||
|
skills: (ArtifactSkillInstance | null)[],
|
||||||
|
excludeSlot?: number
|
||||||
|
): number {
|
||||||
|
const total = getExpectedSkillLevelSum(artifactLevel)
|
||||||
|
const used = skills.reduce((sum, skill, index) => {
|
||||||
|
if (excludeSlot !== undefined && index === excludeSlot) {
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
return sum + (skill?.level ?? 0)
|
||||||
|
}, 0)
|
||||||
|
return total - used
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue