|
|
|
|
|
|
|
|
|
|
|
import type { PicletInstance } from '$lib/db/schema'; |
|
|
|
|
|
export const NATURES = { |
|
'Hardy': [null, null], |
|
'Lonely': ['attack', 'defense'], |
|
'Brave': ['attack', 'speed'], |
|
'Adamant': ['attack', 'sp_attack'], |
|
'Naughty': ['attack', 'sp_defense'], |
|
'Bold': ['defense', 'attack'], |
|
'Docile': [null, null], |
|
'Relaxed': ['defense', 'speed'], |
|
'Impish': ['defense', 'sp_attack'], |
|
'Lax': ['defense', 'sp_defense'], |
|
'Timid': ['speed', 'attack'], |
|
'Hasty': ['speed', 'defense'], |
|
'Serious': [null, null], |
|
'Jolly': ['speed', 'sp_attack'], |
|
'Naive': ['speed', 'sp_defense'], |
|
'Modest': ['sp_attack', 'attack'], |
|
'Mild': ['sp_attack', 'defense'], |
|
'Quiet': ['sp_attack', 'speed'], |
|
'Bashful': [null, null], |
|
'Rash': ['sp_attack', 'sp_defense'], |
|
'Calm': ['sp_defense', 'attack'], |
|
'Gentle': ['sp_defense', 'defense'], |
|
'Sassy': ['sp_defense', 'speed'], |
|
'Careful': ['sp_defense', 'sp_attack'], |
|
'Quirky': [null, null], |
|
} as const; |
|
|
|
export type NatureName = keyof typeof NATURES; |
|
|
|
|
|
const TIER_XP_MULTIPLIERS = { |
|
'low': 0.8, |
|
'medium': 1.0, |
|
'high': 1.4, |
|
'legendary': 1.8 |
|
} as const; |
|
|
|
type TierType = keyof typeof TIER_XP_MULTIPLIERS; |
|
|
|
|
|
|
|
|
|
function normalizeTier(tier: string): TierType { |
|
if (tier in TIER_XP_MULTIPLIERS) { |
|
return tier as TierType; |
|
} |
|
return 'medium'; |
|
} |
|
|
|
|
|
|
|
const BASE_XP_REQUIREMENTS: number[] = []; |
|
for (let level = 1; level <= 100; level++) { |
|
BASE_XP_REQUIREMENTS[level] = level * level * level; |
|
} |
|
|
|
export interface LevelUpInfo { |
|
oldLevel: number; |
|
newLevel: number; |
|
statChanges: { |
|
hp: number; |
|
attack: number; |
|
defense: number; |
|
speed: number; |
|
}; |
|
} |
|
|
|
export interface NatureModifiers { |
|
attack: number; |
|
defense: number; |
|
speed: number; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function calculateHp(baseHp: number, level: number): number { |
|
if (level === 1) { |
|
return Math.max(1, Math.floor(baseHp / 10) + 11); |
|
} |
|
|
|
return Math.floor((2 * baseHp * level) / 100) + level + 10; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function calculateStat(baseStat: number, level: number, natureModifier: number = 1.0): number { |
|
if (level === 1) { |
|
return Math.max(1, Math.floor(baseStat / 10) + 5); |
|
} |
|
|
|
const baseValue = Math.floor((2 * baseStat * level) / 100) + 5; |
|
return Math.floor(baseValue * natureModifier); |
|
} |
|
|
|
|
|
|
|
|
|
export function getNatureModifiers(nature: string): NatureModifiers { |
|
const natureName = nature as NatureName; |
|
const [boosted, lowered] = NATURES[natureName] || NATURES['Hardy']; |
|
|
|
const modifiers: NatureModifiers = { |
|
attack: 1.0, |
|
defense: 1.0, |
|
speed: 1.0, |
|
}; |
|
|
|
if (boosted) { |
|
(modifiers as any)[boosted] = 1.1; |
|
} |
|
if (lowered) { |
|
(modifiers as any)[lowered] = 0.9; |
|
} |
|
|
|
return modifiers; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getXpForLevel(level: number, tier: string = 'medium'): number { |
|
if (level < 1 || level > 100) { |
|
throw new Error('Level must be between 1 and 100'); |
|
} |
|
const normalizedTier = normalizeTier(tier); |
|
const baseXp = BASE_XP_REQUIREMENTS[level]; |
|
const multiplier = TIER_XP_MULTIPLIERS[normalizedTier]; |
|
return Math.floor(baseXp * multiplier); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getXpForNextLevel(currentLevel: number, tier: string = 'medium'): number { |
|
if (currentLevel >= 100) return 0; |
|
return getXpForLevel(currentLevel + 1, tier); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getXpProgress(currentXp: number, currentLevel: number, tier: string = 'medium'): number { |
|
if (currentLevel >= 100) return 100; |
|
|
|
const currentLevelXp = getXpForLevel(currentLevel, tier); |
|
const nextLevelXp = getXpForLevel(currentLevel + 1, tier); |
|
const xpIntoLevel = currentXp - currentLevelXp; |
|
const xpNeededForLevel = nextLevelXp - currentLevelXp; |
|
|
|
return Math.min(100, Math.max(0, (xpIntoLevel / xpNeededForLevel) * 100)); |
|
} |
|
|
|
|
|
|
|
|
|
export function getXpTowardsNextLevel(currentXp: number, currentLevel: number, tier: string = 'medium'): { |
|
current: number; |
|
needed: number; |
|
percentage: number; |
|
} { |
|
if (currentLevel >= 100) { |
|
return { current: 0, needed: 0, percentage: 100 }; |
|
} |
|
|
|
const currentLevelXp = getXpForLevel(currentLevel, tier); |
|
const nextLevelXp = getXpForLevel(currentLevel + 1, tier); |
|
const xpIntoLevel = Math.max(0, currentXp - currentLevelXp); |
|
const xpNeededForLevel = nextLevelXp - currentLevelXp; |
|
const percentage = Math.min(100, Math.max(0, (xpIntoLevel / xpNeededForLevel) * 100)); |
|
|
|
return { |
|
current: xpIntoLevel, |
|
needed: xpNeededForLevel, |
|
percentage |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
export function recalculatePicletStats(instance: PicletInstance): PicletInstance { |
|
const natureModifiers = getNatureModifiers(instance.nature); |
|
|
|
|
|
const newMaxHp = calculateHp(instance.baseHp, instance.level); |
|
const newAttack = calculateStat(instance.baseAttack, instance.level, natureModifiers.attack); |
|
const newDefense = calculateStat(instance.baseDefense, instance.level, natureModifiers.defense); |
|
const newSpeed = calculateStat(instance.baseSpeed, instance.level, natureModifiers.speed); |
|
|
|
|
|
const newFieldAttack = Math.floor(newAttack * 0.8); |
|
const newFieldDefense = Math.floor(newDefense * 0.8); |
|
|
|
|
|
const hpRatio = instance.maxHp > 0 ? instance.currentHp / instance.maxHp : 1; |
|
const newCurrentHp = Math.ceil(newMaxHp * hpRatio); |
|
|
|
return { |
|
...instance, |
|
maxHp: newMaxHp, |
|
currentHp: newCurrentHp, |
|
attack: newAttack, |
|
defense: newDefense, |
|
speed: newSpeed, |
|
fieldAttack: newFieldAttack, |
|
fieldDefense: newFieldDefense |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
export function processLevelUp(instance: PicletInstance): { |
|
newInstance: PicletInstance; |
|
levelUpInfo: LevelUpInfo | null; |
|
} { |
|
const requiredXp = getXpForNextLevel(instance.level, instance.tier); |
|
|
|
|
|
if (instance.level >= 100 || instance.xp < requiredXp) { |
|
return { newInstance: instance, levelUpInfo: null }; |
|
} |
|
|
|
|
|
const oldStats = { |
|
hp: instance.maxHp, |
|
attack: instance.attack, |
|
defense: instance.defense, |
|
speed: instance.speed |
|
}; |
|
|
|
|
|
const leveledUpInstance = { |
|
...instance, |
|
level: instance.level + 1 |
|
}; |
|
|
|
|
|
const newInstance = recalculatePicletStats(leveledUpInstance); |
|
|
|
|
|
const finalInstance = { |
|
...newInstance, |
|
currentHp: newInstance.maxHp |
|
}; |
|
|
|
|
|
const statChanges = { |
|
hp: finalInstance.maxHp - oldStats.hp, |
|
attack: finalInstance.attack - oldStats.attack, |
|
defense: finalInstance.defense - oldStats.defense, |
|
speed: finalInstance.speed - oldStats.speed |
|
}; |
|
|
|
const levelUpInfo: LevelUpInfo = { |
|
oldLevel: instance.level, |
|
newLevel: finalInstance.level, |
|
statChanges |
|
}; |
|
|
|
return { newInstance: finalInstance, levelUpInfo }; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function calculateBattleXp(defeatedPiclet: PicletInstance, participantCount: number = 1): number { |
|
|
|
const bst = defeatedPiclet.baseHp + defeatedPiclet.baseAttack + defeatedPiclet.baseDefense + |
|
defeatedPiclet.baseSpeed + defeatedPiclet.baseFieldAttack + defeatedPiclet.baseFieldDefense; |
|
|
|
|
|
const baseExpYield = Math.max(50, Math.floor(bst / 4)); |
|
|
|
|
|
const baseXp = Math.floor((baseExpYield * defeatedPiclet.level) / 7); |
|
|
|
|
|
return Math.max(1, Math.floor(baseXp / participantCount)); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function processAllLevelUps(instance: PicletInstance): { |
|
newInstance: PicletInstance; |
|
levelUpInfo: LevelUpInfo[]; |
|
} { |
|
const levelUps: LevelUpInfo[] = []; |
|
let currentInstance = instance; |
|
|
|
|
|
while (currentInstance.level < 100) { |
|
const result = processLevelUp(currentInstance); |
|
|
|
if (result.levelUpInfo) { |
|
levelUps.push(result.levelUpInfo); |
|
currentInstance = result.newInstance; |
|
} else { |
|
break; |
|
} |
|
} |
|
|
|
return { |
|
newInstance: currentInstance, |
|
levelUpInfo: levelUps |
|
}; |
|
} |