|
import { db } from './index'; |
|
import type { PicletInstance, BattleMove } from './schema'; |
|
import { PicletType, AttackType, getTypeFromConcept } from '../types/picletTypes'; |
|
import type { PicletStats } from '../types'; |
|
import { generateUnlockLevels } from '../services/unlockLevels'; |
|
|
|
|
|
interface GeneratedPicletData { |
|
name: string; |
|
imageUrl: string; |
|
imageData?: string; |
|
imageCaption: string; |
|
concept: string; |
|
imagePrompt: string; |
|
stats: PicletStats; |
|
createdAt: Date; |
|
} |
|
|
|
|
|
export async function generatedDataToPicletInstance(data: GeneratedPicletData, level: number = 5): Promise<Omit<PicletInstance, 'id'>> { |
|
if (!data.stats) { |
|
throw new Error('Generated data must have stats to create PicletInstance'); |
|
} |
|
|
|
|
|
const stats = data.stats as PicletStats; |
|
|
|
if (!stats.baseStats || !stats.specialAbility || !stats.movepool) { |
|
throw new Error('Generated stats must be in battle-ready format with baseStats, specialAbility, and movepool'); |
|
} |
|
|
|
|
|
const baseHp = Math.floor(stats.baseStats.hp * 2 + 50); |
|
const baseAttack = Math.floor(stats.baseStats.attack * 1.5 + 30); |
|
const baseDefense = Math.floor(stats.baseStats.defense * 1.5 + 30); |
|
const baseSpeed = Math.floor(stats.baseStats.speed * 1.5 + 30); |
|
|
|
|
|
const normalizedType = stats.primaryType.toLowerCase(); |
|
const validType = Object.values(PicletType).find(type => type === normalizedType); |
|
const primaryType = validType || getTypeFromConcept(data.concept, data.imageCaption); |
|
|
|
if (!validType) { |
|
console.warn(`Invalid primaryType "${stats.primaryType}" from stats, falling back to concept detection`); |
|
} |
|
|
|
|
|
const baseMoves: BattleMove[] = stats.movepool.map(move => ({ |
|
name: move.name, |
|
type: move.type as unknown as AttackType, |
|
power: move.power, |
|
accuracy: move.accuracy, |
|
pp: move.pp, |
|
currentPp: move.pp, |
|
description: move.description, |
|
unlockLevel: 1 |
|
})); |
|
|
|
|
|
const { movesWithUnlocks, abilityUnlockLevel } = generateUnlockLevels(baseMoves, stats.specialAbility); |
|
const moves = movesWithUnlocks; |
|
|
|
|
|
const baseFieldAttack = Math.floor(baseAttack * 0.8); |
|
const baseFieldDefense = Math.floor(baseDefense * 0.8); |
|
|
|
|
|
const calculateStat = (base: number, level: number) => { |
|
if (level === 1) { |
|
return Math.max(1, Math.floor(base / 10) + 5); |
|
} |
|
return Math.floor((2 * base * level) / 100) + 5; |
|
}; |
|
|
|
const calculateHp = (base: number, level: number) => { |
|
if (level === 1) { |
|
return Math.max(1, Math.floor(base / 10) + 11); |
|
} |
|
return Math.floor((2 * base * level) / 100) + level + 10; |
|
}; |
|
|
|
const maxHp = calculateHp(baseHp, level); |
|
|
|
const bst = baseHp + baseAttack + baseDefense + baseFieldAttack + baseFieldDefense + baseSpeed; |
|
|
|
|
|
const existingPiclets = await db.picletInstances.count(); |
|
const isFirstPiclet = existingPiclets === 0; |
|
|
|
return { |
|
|
|
typeId: data.name.toLowerCase().replace(/\s+/g, '-'), |
|
nickname: data.name, |
|
primaryType: primaryType, |
|
secondaryType: undefined, |
|
|
|
|
|
currentHp: maxHp, |
|
maxHp, |
|
level, |
|
xp: 0, |
|
attack: calculateStat(baseAttack, level), |
|
defense: calculateStat(baseDefense, level), |
|
fieldAttack: calculateStat(baseFieldAttack, level), |
|
fieldDefense: calculateStat(baseFieldDefense, level), |
|
speed: calculateStat(baseSpeed, level), |
|
|
|
|
|
baseHp, |
|
baseAttack, |
|
baseDefense, |
|
baseFieldAttack, |
|
baseFieldDefense, |
|
baseSpeed, |
|
|
|
|
|
moves, |
|
nature: stats.nature, |
|
specialAbility: stats.specialAbility, |
|
specialAbilityUnlockLevel: abilityUnlockLevel, |
|
|
|
|
|
isInRoster: isFirstPiclet, |
|
rosterPosition: isFirstPiclet ? 0 : undefined, |
|
|
|
|
|
caught: isFirstPiclet, |
|
caughtAt: isFirstPiclet ? new Date() : undefined, |
|
bst, |
|
tier: stats.tier, |
|
role: 'balanced', |
|
variance: 0, |
|
|
|
|
|
imageUrl: data.imageUrl, |
|
imageData: data.imageData, |
|
imageCaption: data.imageCaption, |
|
concept: data.concept, |
|
description: stats.description, |
|
imagePrompt: data.imagePrompt |
|
}; |
|
} |
|
|
|
|
|
|
|
export async function savePicletInstance(piclet: Omit<PicletInstance, 'id'>): Promise<number> { |
|
return await db.picletInstances.add(piclet); |
|
} |
|
|
|
|
|
export async function catchPiclet(picletId: number): Promise<void> { |
|
await db.picletInstances.update(picletId, { |
|
caught: true, |
|
caughtAt: new Date() |
|
}); |
|
} |
|
|
|
|
|
export async function getCaughtPiclets(): Promise<PicletInstance[]> { |
|
const allPiclets = await db.picletInstances.toArray(); |
|
return allPiclets.filter(p => p.caught === true); |
|
} |
|
|
|
|
|
export async function getUncaughtPiclets(): Promise<PicletInstance[]> { |
|
const allPiclets = await db.picletInstances.toArray(); |
|
return allPiclets.filter(p => p.caught === false); |
|
} |
|
|
|
|
|
export async function getAllPicletInstances(): Promise<PicletInstance[]> { |
|
return await db.picletInstances.toArray(); |
|
} |
|
|
|
|
|
export async function getRosterPiclets(): Promise<PicletInstance[]> { |
|
const allPiclets = await db.picletInstances.toArray(); |
|
return allPiclets |
|
.filter(p => |
|
p.caught === true && |
|
p.rosterPosition !== undefined && |
|
p.rosterPosition !== null && |
|
p.rosterPosition >= 0 && |
|
p.rosterPosition <= 5 |
|
) |
|
.sort((a, b) => (a.rosterPosition ?? 0) - (b.rosterPosition ?? 0)); |
|
} |
|
|
|
|
|
export async function updateRosterPosition(id: number, position: number | undefined): Promise<void> { |
|
await db.picletInstances.update(id, { |
|
isInRoster: position !== undefined, |
|
rosterPosition: position |
|
}); |
|
} |
|
|
|
|
|
export async function moveToRoster(id: number, position: number): Promise<void> { |
|
|
|
const existingPiclet = await db.picletInstances |
|
.where('rosterPosition') |
|
.equals(position) |
|
.and(item => item.isInRoster) |
|
.first(); |
|
|
|
if (existingPiclet) { |
|
|
|
await db.picletInstances.update(existingPiclet.id!, { |
|
isInRoster: false, |
|
rosterPosition: undefined |
|
}); |
|
} |
|
|
|
|
|
await db.picletInstances.update(id, { |
|
isInRoster: true, |
|
rosterPosition: position |
|
}); |
|
} |
|
|
|
|
|
export async function swapRosterPositions(id1: number, position1: number, id2: number, position2: number): Promise<void> { |
|
await db.transaction('rw', db.picletInstances, async () => { |
|
await db.picletInstances.update(id1, { rosterPosition: position2 }); |
|
await db.picletInstances.update(id2, { rosterPosition: position1 }); |
|
}); |
|
} |
|
|
|
|
|
export async function moveToStorage(id: number): Promise<void> { |
|
await db.picletInstances.update(id, { |
|
isInRoster: false, |
|
rosterPosition: undefined |
|
}); |
|
} |
|
|
|
|
|
export async function getStoragePiclets(): Promise<PicletInstance[]> { |
|
const allPiclets = await db.picletInstances.toArray(); |
|
return allPiclets.filter(p => |
|
p.caught === true && |
|
(p.rosterPosition === undefined || |
|
p.rosterPosition === null || |
|
p.rosterPosition < 0 || |
|
p.rosterPosition > 5) |
|
); |
|
} |
|
|
|
|
|
export async function deletePicletInstance(id: number): Promise<void> { |
|
await db.picletInstances.delete(id); |
|
} |