more more
Browse files
src/lib/components/Battle/BattleControls.svelte
CHANGED
@@ -34,7 +34,7 @@
|
|
34 |
<div class="action-area">
|
35 |
{#if waitingForContinue}
|
36 |
<!-- Tap to continue overlay -->
|
37 |
-
<div class="tap-continue-overlay" role="button" tabindex="0" onclick={onContinueTap}>
|
38 |
<div class="tap-indicator">
|
39 |
<span>Tap to continue</span>
|
40 |
</div>
|
|
|
34 |
<div class="action-area">
|
35 |
{#if waitingForContinue}
|
36 |
<!-- Tap to continue overlay -->
|
37 |
+
<div class="tap-continue-overlay" role="button" tabindex="0" onclick={onContinueTap} onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? onContinueTap() : null}>
|
38 |
<div class="tap-indicator">
|
39 |
<span>Tap to continue</span>
|
40 |
</div>
|
src/lib/components/PicletGenerator/PicletGenerator.svelte
CHANGED
@@ -7,7 +7,7 @@
|
|
7 |
import PicletResult from './PicletResult.svelte';
|
8 |
import { removeBackground } from '$lib/utils/professionalImageProcessing';
|
9 |
import { extractPicletMetadata } from '$lib/services/picletMetadata';
|
10 |
-
import { savePicletInstance,
|
11 |
import { PicletType, TYPE_DATA } from '$lib/types/picletTypes';
|
12 |
import { EncounterService } from '$lib/db/encounterService';
|
13 |
|
@@ -892,7 +892,7 @@ Write your response within \`\`\`json\`\`\``;
|
|
892 |
console.log('- stats:', cleanStats);
|
893 |
|
894 |
// Convert to PicletInstance format and save
|
895 |
-
const picletInstance = await
|
896 |
const picletId = await savePicletInstance(picletInstance);
|
897 |
console.log('Piclet auto-saved as uncaught with ID:', picletId);
|
898 |
|
|
|
7 |
import PicletResult from './PicletResult.svelte';
|
8 |
import { removeBackground } from '$lib/utils/professionalImageProcessing';
|
9 |
import { extractPicletMetadata } from '$lib/services/picletMetadata';
|
10 |
+
import { savePicletInstance, generatedDataToPicletInstance } from '$lib/db/piclets';
|
11 |
import { PicletType, TYPE_DATA } from '$lib/types/picletTypes';
|
12 |
import { EncounterService } from '$lib/db/encounterService';
|
13 |
|
|
|
892 |
console.log('- stats:', cleanStats);
|
893 |
|
894 |
// Convert to PicletInstance format and save
|
895 |
+
const picletInstance = await generatedDataToPicletInstance(picletData);
|
896 |
const picletId = await savePicletInstance(picletInstance);
|
897 |
console.log('Piclet auto-saved as uncaught with ID:', picletId);
|
898 |
|
src/lib/components/PicletGenerator/PicletResult.svelte
CHANGED
@@ -2,7 +2,7 @@
|
|
2 |
import type { PicletWorkflowState } from '$lib/types';
|
3 |
import type { PicletInstance } from '$lib/db/schema';
|
4 |
import { TYPE_DATA, PicletType } from '$lib/types/picletTypes';
|
5 |
-
import {
|
6 |
import PicletCard from '../Piclets/PicletCard.svelte';
|
7 |
import PicletDetail from '../Piclets/PicletDetail.svelte';
|
8 |
|
@@ -34,7 +34,7 @@
|
|
34 |
};
|
35 |
|
36 |
// Create piclet instance asynchronously
|
37 |
-
|
38 |
picletInstance = { ...instance, id: 0 }; // Add temporary id for display
|
39 |
}).catch(error => {
|
40 |
console.error('Failed to create piclet instance:', error);
|
@@ -76,7 +76,7 @@
|
|
76 |
{#if picletInstance}
|
77 |
<div class="piclet-preview">
|
78 |
<PicletCard
|
79 |
-
|
80 |
size={140}
|
81 |
onClick={handleCardClick}
|
82 |
/>
|
|
|
2 |
import type { PicletWorkflowState } from '$lib/types';
|
3 |
import type { PicletInstance } from '$lib/db/schema';
|
4 |
import { TYPE_DATA, PicletType } from '$lib/types/picletTypes';
|
5 |
+
import { generatedDataToPicletInstance } from '$lib/db/piclets';
|
6 |
import PicletCard from '../Piclets/PicletCard.svelte';
|
7 |
import PicletDetail from '../Piclets/PicletDetail.svelte';
|
8 |
|
|
|
34 |
};
|
35 |
|
36 |
// Create piclet instance asynchronously
|
37 |
+
generatedDataToPicletInstance(picletData).then(instance => {
|
38 |
picletInstance = { ...instance, id: 0 }; // Add temporary id for display
|
39 |
}).catch(error => {
|
40 |
console.error('Failed to create piclet instance:', error);
|
|
|
76 |
{#if picletInstance}
|
77 |
<div class="piclet-preview">
|
78 |
<PicletCard
|
79 |
+
piclet={picletInstance}
|
80 |
size={140}
|
81 |
onClick={handleCardClick}
|
82 |
/>
|
src/lib/components/Piclets/NewlyCaughtPicletDetail.svelte
CHANGED
@@ -62,12 +62,12 @@
|
|
62 |
}
|
63 |
</script>
|
64 |
|
65 |
-
<div class="detail-overlay" role="dialog" aria-modal="true" tabindex="-1" onclick={onClose}>
|
66 |
<div class="detail-container newly-caught" role="document" onclick={handleContainerClick}>
|
67 |
|
68 |
<!-- Celebration overlay -->
|
69 |
{#if showCelebration}
|
70 |
-
<div class="celebration-overlay" role="button" tabindex="0" onclick={dismissCelebration}>
|
71 |
<div class="celebration-content">
|
72 |
<div class="celebration-sparkles">✨</div>
|
73 |
<h1 class="celebration-title">Welcome to your team!</h1>
|
|
|
62 |
}
|
63 |
</script>
|
64 |
|
65 |
+
<div class="detail-overlay" role="dialog" aria-modal="true" tabindex="-1" onclick={onClose} onkeydown={(e) => e.key === 'Escape' ? onClose() : null}>
|
66 |
<div class="detail-container newly-caught" role="document" onclick={handleContainerClick}>
|
67 |
|
68 |
<!-- Celebration overlay -->
|
69 |
{#if showCelebration}
|
70 |
+
<div class="celebration-overlay" role="button" tabindex="0" onclick={dismissCelebration} onkeydown={(e) => e.key === 'Enter' || e.key === ' ' ? dismissCelebration() : null}>
|
71 |
<div class="celebration-content">
|
72 |
<div class="celebration-sparkles">✨</div>
|
73 |
<h1 class="celebration-title">Welcome to your team!</h1>
|
src/lib/db/piclets.ts
CHANGED
@@ -4,6 +4,139 @@ import { PicletType, AttackType, getTypeFromConcept } from '../types/picletTypes
|
|
4 |
import type { PicletStats } from '../types';
|
5 |
import { generateUnlockLevels } from '../services/unlockLevels';
|
6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
// Save a new PicletInstance
|
9 |
export async function savePicletInstance(piclet: Omit<PicletInstance, 'id'>): Promise<number> {
|
|
|
4 |
import type { PicletStats } from '../types';
|
5 |
import { generateUnlockLevels } from '../services/unlockLevels';
|
6 |
|
7 |
+
// Interface for generated piclet data (from PicletResult component)
|
8 |
+
interface GeneratedPicletData {
|
9 |
+
name: string;
|
10 |
+
imageUrl: string;
|
11 |
+
imageData?: string;
|
12 |
+
imageCaption: string;
|
13 |
+
concept: string;
|
14 |
+
imagePrompt: string;
|
15 |
+
stats: PicletStats;
|
16 |
+
createdAt: Date;
|
17 |
+
}
|
18 |
+
|
19 |
+
// Convert generated piclet data to a PicletInstance
|
20 |
+
export async function generatedDataToPicletInstance(data: GeneratedPicletData, level: number = 5): Promise<Omit<PicletInstance, 'id'>> {
|
21 |
+
if (!data.stats) {
|
22 |
+
throw new Error('Generated data must have stats to create PicletInstance');
|
23 |
+
}
|
24 |
+
|
25 |
+
// All generated data must now have the battle-ready format
|
26 |
+
const stats = data.stats as PicletStats;
|
27 |
+
|
28 |
+
if (!stats.baseStats || !stats.specialAbility || !stats.movepool) {
|
29 |
+
throw new Error('Generated stats must be in battle-ready format with baseStats, specialAbility, and movepool');
|
30 |
+
}
|
31 |
+
|
32 |
+
// Calculate base stats from battle-ready format
|
33 |
+
const baseHp = Math.floor(stats.baseStats.hp * 2 + 50);
|
34 |
+
const baseAttack = Math.floor(stats.baseStats.attack * 1.5 + 30);
|
35 |
+
const baseDefense = Math.floor(stats.baseStats.defense * 1.5 + 30);
|
36 |
+
const baseSpeed = Math.floor(stats.baseStats.speed * 1.5 + 30);
|
37 |
+
|
38 |
+
// Determine primary type from battle stats
|
39 |
+
const normalizedType = stats.primaryType.toLowerCase();
|
40 |
+
const validType = Object.values(PicletType).find(type => type === normalizedType);
|
41 |
+
const primaryType = validType || getTypeFromConcept(data.concept, data.imageCaption);
|
42 |
+
|
43 |
+
if (!validType) {
|
44 |
+
console.warn(`Invalid primaryType "${stats.primaryType}" from stats, falling back to concept detection`);
|
45 |
+
}
|
46 |
+
|
47 |
+
// Create moves from battle-ready format (without unlock levels initially)
|
48 |
+
const baseMoves: BattleMove[] = stats.movepool.map(move => ({
|
49 |
+
name: move.name,
|
50 |
+
type: move.type as unknown as AttackType,
|
51 |
+
power: move.power,
|
52 |
+
accuracy: move.accuracy,
|
53 |
+
pp: move.pp,
|
54 |
+
currentPp: move.pp,
|
55 |
+
description: move.description,
|
56 |
+
unlockLevel: 1 // Temporary, will be set below
|
57 |
+
}));
|
58 |
+
|
59 |
+
// Generate unlock levels for moves and special ability
|
60 |
+
const { movesWithUnlocks, abilityUnlockLevel } = generateUnlockLevels(baseMoves, stats.specialAbility);
|
61 |
+
const moves = movesWithUnlocks;
|
62 |
+
|
63 |
+
// Field stats are variations of regular stats
|
64 |
+
const baseFieldAttack = Math.floor(baseAttack * 0.8);
|
65 |
+
const baseFieldDefense = Math.floor(baseDefense * 0.8);
|
66 |
+
|
67 |
+
// Use Pokemon-accurate stat calculations (matching levelingService)
|
68 |
+
const calculateStat = (base: number, level: number) => {
|
69 |
+
if (level === 1) {
|
70 |
+
return Math.max(1, Math.floor(base / 10) + 5);
|
71 |
+
}
|
72 |
+
return Math.floor((2 * base * level) / 100) + 5;
|
73 |
+
};
|
74 |
+
|
75 |
+
const calculateHp = (base: number, level: number) => {
|
76 |
+
if (level === 1) {
|
77 |
+
return Math.max(1, Math.floor(base / 10) + 11);
|
78 |
+
}
|
79 |
+
return Math.floor((2 * base * level) / 100) + level + 10;
|
80 |
+
};
|
81 |
+
|
82 |
+
const maxHp = calculateHp(baseHp, level);
|
83 |
+
|
84 |
+
const bst = baseHp + baseAttack + baseDefense + baseFieldAttack + baseFieldDefense + baseSpeed;
|
85 |
+
|
86 |
+
return {
|
87 |
+
// Type Info
|
88 |
+
typeId: data.name.toLowerCase().replace(/\s+/g, '-'),
|
89 |
+
nickname: data.name,
|
90 |
+
primaryType: primaryType,
|
91 |
+
secondaryType: undefined,
|
92 |
+
|
93 |
+
// Current Stats
|
94 |
+
currentHp: maxHp,
|
95 |
+
maxHp,
|
96 |
+
level,
|
97 |
+
xp: 0,
|
98 |
+
attack: calculateStat(baseAttack, level),
|
99 |
+
defense: calculateStat(baseDefense, level),
|
100 |
+
fieldAttack: calculateStat(baseFieldAttack, level),
|
101 |
+
fieldDefense: calculateStat(baseFieldDefense, level),
|
102 |
+
speed: calculateStat(baseSpeed, level),
|
103 |
+
|
104 |
+
// Base Stats
|
105 |
+
baseHp,
|
106 |
+
baseAttack,
|
107 |
+
baseDefense,
|
108 |
+
baseFieldAttack,
|
109 |
+
baseFieldDefense,
|
110 |
+
baseSpeed,
|
111 |
+
|
112 |
+
// Battle
|
113 |
+
moves,
|
114 |
+
nature: stats.nature,
|
115 |
+
specialAbility: stats.specialAbility,
|
116 |
+
specialAbilityUnlockLevel: abilityUnlockLevel,
|
117 |
+
|
118 |
+
// Roster
|
119 |
+
isInRoster: false,
|
120 |
+
rosterPosition: undefined,
|
121 |
+
|
122 |
+
// Metadata
|
123 |
+
caught: false, // Scanned Piclets start as uncaught
|
124 |
+
caughtAt: undefined, // Will be set when caught
|
125 |
+
bst,
|
126 |
+
tier: stats.tier, // Use tier from stats
|
127 |
+
role: 'balanced', // Could be enhanced based on stat distribution
|
128 |
+
variance: 0,
|
129 |
+
|
130 |
+
// Original generation data
|
131 |
+
imageUrl: data.imageUrl,
|
132 |
+
imageData: data.imageData,
|
133 |
+
imageCaption: data.imageCaption,
|
134 |
+
concept: data.concept,
|
135 |
+
description: stats.description,
|
136 |
+
imagePrompt: data.imagePrompt
|
137 |
+
};
|
138 |
+
}
|
139 |
+
|
140 |
|
141 |
// Save a new PicletInstance
|
142 |
export async function savePicletInstance(piclet: Omit<PicletInstance, 'id'>): Promise<number> {
|