Fraser commited on
Commit
90a88fc
·
1 Parent(s): 7774c0a
src/lib/components/Battle/BattleField.svelte CHANGED
@@ -59,12 +59,15 @@
59
  />
60
 
61
  {#if enemyVisible}
62
- <div class="piclet-sprite enemy-sprite" transition:fade={{ duration: 300 }}>
63
- <img
64
- src={enemyPiclet.imageUrl}
65
- alt={enemyPiclet.nickname}
66
- on:error={(e) => e.currentTarget.src = 'https://via.placeholder.com/120x120?text=Piclet'}
67
- />
 
 
 
68
  </div>
69
  {/if}
70
  </div>
@@ -72,12 +75,15 @@
72
  <!-- Player area -->
73
  <div class="player-area">
74
  {#if playerVisible}
75
- <div class="piclet-sprite player-sprite" transition:fade={{ duration: 300 }}>
76
- <img
77
- src={playerPiclet.imageUrl}
78
- alt={playerPiclet.nickname}
79
- on:error={(e) => e.currentTarget.src = 'https://via.placeholder.com/120x120?text=Piclet'}
80
- />
 
 
 
81
  </div>
82
  {/if}
83
 
@@ -91,12 +97,9 @@
91
 
92
  <style>
93
  .battle-field {
94
- height: 60vh;
95
- min-height: 400px;
96
  position: relative;
97
- background-size: cover;
98
- background-position: center;
99
- background-repeat: no-repeat;
100
  overflow: hidden;
101
  }
102
 
@@ -107,10 +110,11 @@
107
  45deg,
108
  transparent,
109
  transparent 10px,
110
- rgba(255, 255, 255, 0.05) 10px,
111
- rgba(255, 255, 255, 0.05) 20px
112
  );
113
  pointer-events: none;
 
114
  }
115
 
116
  .trainer-intro {
@@ -172,6 +176,33 @@
172
  transform: scaleX(-1); /* Mirror player sprite */
173
  }
174
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  @media (max-width: 768px) {
176
  .battle-field {
177
  height: 50vh;
 
59
  />
60
 
61
  {#if enemyVisible}
62
+ <div class="enemy-piclet-container">
63
+ <div class="piclet-sprite enemy-sprite" transition:fade={{ duration: 300 }}>
64
+ <img
65
+ src={enemyPiclet.imageData || enemyPiclet.imageUrl}
66
+ alt={enemyPiclet.nickname}
67
+ on:error={(e) => e.currentTarget.src = 'https://via.placeholder.com/120x120?text=Piclet'}
68
+ />
69
+ </div>
70
+ <div class="battle-platform enemy-platform"></div>
71
  </div>
72
  {/if}
73
  </div>
 
75
  <!-- Player area -->
76
  <div class="player-area">
77
  {#if playerVisible}
78
+ <div class="player-piclet-container">
79
+ <div class="piclet-sprite player-sprite" transition:fade={{ duration: 300 }}>
80
+ <img
81
+ src={playerPiclet.imageData || playerPiclet.imageUrl}
82
+ alt={playerPiclet.nickname}
83
+ on:error={(e) => e.currentTarget.src = 'https://via.placeholder.com/120x120?text=Piclet'}
84
+ />
85
+ </div>
86
+ <div class="battle-platform player-platform"></div>
87
  </div>
88
  {/if}
89
 
 
97
 
98
  <style>
99
  .battle-field {
100
+ height: 280px;
 
101
  position: relative;
102
+ background-color: #e8f4f8;
 
 
103
  overflow: hidden;
104
  }
105
 
 
110
  45deg,
111
  transparent,
112
  transparent 10px,
113
+ rgba(0, 0, 0, 0.02) 10px,
114
+ rgba(0, 0, 0, 0.02) 20px
115
  );
116
  pointer-events: none;
117
+ z-index: 1;
118
  }
119
 
120
  .trainer-intro {
 
176
  transform: scaleX(-1); /* Mirror player sprite */
177
  }
178
 
179
+ /* Piclet containers with platforms */
180
+ .enemy-piclet-container,
181
+ .player-piclet-container {
182
+ position: relative;
183
+ display: flex;
184
+ flex-direction: column;
185
+ align-items: center;
186
+ }
187
+
188
+ .battle-platform {
189
+ width: 140px;
190
+ height: 20px;
191
+ background: #90a4ae;
192
+ border-radius: 50%;
193
+ margin-top: -10px;
194
+ box-shadow: 0 4px 8px rgba(0,0,0,0.2);
195
+ z-index: 2;
196
+ }
197
+
198
+ .enemy-platform {
199
+ width: 120px;
200
+ }
201
+
202
+ .player-platform {
203
+ width: 140px;
204
+ }
205
+
206
  @media (max-width: 768px) {
207
  .battle-field {
208
  height: 50vh;
src/lib/components/Pages/Battle.svelte CHANGED
@@ -119,9 +119,9 @@
119
  }
120
  </script>
121
 
122
- <div class="battle-page">
123
  <nav class="battle-nav">
124
- <button class="back-button" on:click={() => onBattleEnd('cancelled')}>
125
  ← Back
126
  </button>
127
  <h1>{isWildBattle ? 'Wild Battle' : 'Battle'}</h1>
@@ -155,6 +155,9 @@
155
 
156
  <style>
157
  .battle-page {
 
 
 
158
  height: 100vh;
159
  display: flex;
160
  flex-direction: column;
 
119
  }
120
  </script>
121
 
122
+ <div class="battle-page" transition:fade={{ duration: 300 }}>
123
  <nav class="battle-nav">
124
+ <button class="back-button" on:click={() => onBattleEnd('cancelled')} style="display: none;">
125
  ← Back
126
  </button>
127
  <h1>{isWildBattle ? 'Wild Battle' : 'Battle'}</h1>
 
155
 
156
  <style>
157
  .battle-page {
158
+ position: fixed;
159
+ inset: 0;
160
+ z-index: 1000;
161
  height: 100vh;
162
  display: flex;
163
  flex-direction: column;
src/lib/components/Pages/Encounters.svelte CHANGED
@@ -142,7 +142,6 @@
142
  try {
143
  // Get all piclet instances
144
  const allPiclets = await db.picletInstances.toArray();
145
- console.log('All piclets:', allPiclets);
146
 
147
  // Filter piclets that have a roster position (0-5)
148
  const rosterPiclets = allPiclets.filter(p =>
@@ -151,14 +150,12 @@
151
  p.rosterPosition >= 0 &&
152
  p.rosterPosition <= 5
153
  );
154
- console.log('Roster piclets:', rosterPiclets);
155
 
156
  // Sort by roster position
157
  rosterPiclets.sort((a, b) => (a.rosterPosition ?? 0) - (b.rosterPosition ?? 0));
158
 
159
  // Get healthy piclets
160
  const healthyPiclets = rosterPiclets.filter(p => p.currentHp > 0);
161
- console.log('Healthy piclets:', healthyPiclets);
162
 
163
  if (healthyPiclets.length === 0) {
164
  alert('You need at least one healthy piclet in your roster to battle!');
@@ -167,7 +164,6 @@
167
 
168
  // Check if there's at least one piclet in position 0
169
  const hasPosition0 = rosterPiclets.some(p => p.rosterPosition === 0);
170
- console.log('Has position 0:', hasPosition0);
171
  if (!hasPosition0) {
172
  alert('You need a piclet in the first roster slot (position 0) to battle!');
173
  return;
@@ -190,54 +186,111 @@
190
  async function generateEnemyPiclet(encounter: Encounter): Promise<PicletInstance | null> {
191
  if (!encounter.picletTypeId || !encounter.enemyLevel) return null;
192
 
193
- // Create a mock enemy piclet instance
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  const enemyPiclet: PicletInstance = {
195
  id: -1, // Temporary ID for enemy
196
  typeId: encounter.picletTypeId,
197
- nickname: `Wild Piclet`,
198
- primaryTypeString: 'normal',
199
 
200
- level: encounter.enemyLevel,
201
  xp: 0,
202
- currentHp: 20 + (encounter.enemyLevel * 5),
203
- maxHp: 20 + (encounter.enemyLevel * 5),
204
- attack: 10 + (encounter.enemyLevel * 2),
205
- defense: 10 + (encounter.enemyLevel * 2),
206
- fieldAttack: 10 + (encounter.enemyLevel * 2),
207
- fieldDefense: 10 + (encounter.enemyLevel * 2),
208
- speed: 10 + (encounter.enemyLevel * 2),
209
 
210
- baseHp: 20,
211
- baseAttack: 10,
212
- baseDefense: 10,
213
- baseFieldAttack: 10,
214
- baseFieldDefense: 10,
215
- baseSpeed: 10,
216
 
217
  moves: [
218
  {
219
- name: 'Tackle',
220
  type: 'normal',
221
- power: 40,
 
 
 
 
 
 
 
 
 
222
  accuracy: 100,
223
- pp: 35,
224
- currentPp: 35,
225
- description: 'A physical attack'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  }
227
  ],
228
  nature: 'hardy',
229
 
230
  isInRoster: false,
231
  caughtAt: new Date(),
232
- bst: 60,
233
- tier: 'common',
234
  role: 'balanced',
235
- variance: 1,
236
 
237
- imageUrl: `https://storage.googleapis.com/piclodia/${encounter.picletTypeId}.png`,
238
- imageCaption: 'A wild piclet',
239
- concept: 'wild',
240
- imagePrompt: 'wild piclet'
 
241
  };
242
 
243
  return enemyPiclet;
 
142
  try {
143
  // Get all piclet instances
144
  const allPiclets = await db.picletInstances.toArray();
 
145
 
146
  // Filter piclets that have a roster position (0-5)
147
  const rosterPiclets = allPiclets.filter(p =>
 
150
  p.rosterPosition >= 0 &&
151
  p.rosterPosition <= 5
152
  );
 
153
 
154
  // Sort by roster position
155
  rosterPiclets.sort((a, b) => (a.rosterPosition ?? 0) - (b.rosterPosition ?? 0));
156
 
157
  // Get healthy piclets
158
  const healthyPiclets = rosterPiclets.filter(p => p.currentHp > 0);
 
159
 
160
  if (healthyPiclets.length === 0) {
161
  alert('You need at least one healthy piclet in your roster to battle!');
 
164
 
165
  // Check if there's at least one piclet in position 0
166
  const hasPosition0 = rosterPiclets.some(p => p.rosterPosition === 0);
 
167
  if (!hasPosition0) {
168
  alert('You need a piclet in the first roster slot (position 0) to battle!');
169
  return;
 
186
  async function generateEnemyPiclet(encounter: Encounter): Promise<PicletInstance | null> {
187
  if (!encounter.picletTypeId || !encounter.enemyLevel) return null;
188
 
189
+ // Get the discovered monster data for this piclet type
190
+ const monster = await db.monsters
191
+ .where('name')
192
+ .equals(encounter.picletTypeId.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()))
193
+ .first();
194
+
195
+ if (!monster || !monster.stats) {
196
+ console.error('Monster not found for typeId:', encounter.picletTypeId);
197
+ return null;
198
+ }
199
+
200
+ // Calculate stats based on monster's base stats and encounter level
201
+ const level = encounter.enemyLevel;
202
+ const stats = monster.stats;
203
+
204
+ // Calculate base stats from the 0-100 scale
205
+ const baseHp = Math.floor(stats.HP * 2 + 50);
206
+ const baseAttack = Math.floor(stats.attack * 1.5 + 30);
207
+ const baseDefense = Math.floor(stats.defence * 1.5 + 30);
208
+ const baseSpeed = Math.floor(stats.speed * 1.5 + 30);
209
+ const baseFieldAttack = Math.floor(baseAttack * 0.8);
210
+ const baseFieldDefense = Math.floor(baseDefense * 0.8);
211
+
212
+ // Calculate current stats based on level
213
+ const calculateStat = (base: number, level: number) => Math.floor((base * level) / 50 + 5);
214
+ const calculateHp = (base: number, level: number) => Math.floor((base * level) / 50 + level + 10);
215
+
216
+ const maxHp = calculateHp(baseHp, level);
217
+
218
+ // Create enemy piclet instance
219
  const enemyPiclet: PicletInstance = {
220
  id: -1, // Temporary ID for enemy
221
  typeId: encounter.picletTypeId,
222
+ nickname: monster.name,
223
+ primaryTypeString: 'normal', // Default type
224
 
225
+ level: level,
226
  xp: 0,
227
+ currentHp: maxHp,
228
+ maxHp: maxHp,
229
+ attack: calculateStat(baseAttack, level),
230
+ defense: calculateStat(baseDefense, level),
231
+ fieldAttack: calculateStat(baseFieldAttack, level),
232
+ fieldDefense: calculateStat(baseFieldDefense, level),
233
+ speed: calculateStat(baseSpeed, level),
234
 
235
+ baseHp,
236
+ baseAttack,
237
+ baseDefense,
238
+ baseFieldAttack,
239
+ baseFieldDefense,
240
+ baseSpeed,
241
 
242
  moves: [
243
  {
244
+ name: stats.attackActionName,
245
  type: 'normal',
246
+ power: 50,
247
+ accuracy: 95,
248
+ pp: 20,
249
+ currentPp: 20,
250
+ description: stats.attackActionDescription
251
+ },
252
+ {
253
+ name: stats.buffActionName,
254
+ type: 'status',
255
+ power: 0,
256
  accuracy: 100,
257
+ pp: 15,
258
+ currentPp: 15,
259
+ description: stats.buffActionDescription
260
+ },
261
+ {
262
+ name: stats.debuffActionName,
263
+ type: 'status',
264
+ power: 0,
265
+ accuracy: 85,
266
+ pp: 15,
267
+ currentPp: 15,
268
+ description: stats.debuffActionDescription
269
+ },
270
+ {
271
+ name: stats.specialActionName,
272
+ type: 'special',
273
+ power: 80,
274
+ accuracy: 90,
275
+ pp: 5,
276
+ currentPp: 5,
277
+ description: stats.specialActionDescription
278
  }
279
  ],
280
  nature: 'hardy',
281
 
282
  isInRoster: false,
283
  caughtAt: new Date(),
284
+ bst: baseHp + baseAttack + baseDefense + baseFieldAttack + baseFieldDefense + baseSpeed,
285
+ tier: stats.rarity > 80 ? 'legendary' : stats.rarity > 60 ? 'rare' : stats.rarity > 40 ? 'uncommon' : 'common',
286
  role: 'balanced',
287
+ variance: 0,
288
 
289
+ imageUrl: monster.imageUrl,
290
+ imageData: monster.imageData, // Use the transparent image data
291
+ imageCaption: monster.imageCaption,
292
+ concept: monster.concept,
293
+ imagePrompt: monster.imagePrompt
294
  };
295
 
296
  return enemyPiclet;
src/lib/db/encounterService.ts CHANGED
@@ -62,7 +62,7 @@ export class EncounterService {
62
  type: EncounterType.WILD_PICLET,
63
  title: 'Your First Piclet!',
64
  description: 'A friendly piclet appears! This one seems easy to catch.',
65
- picletTypeId: firstDiscovered.id?.toString() || 'starter-001',
66
  enemyLevel: 5,
67
  createdAt: new Date()
68
  });
@@ -126,19 +126,31 @@ export class EncounterService {
126
  // Get player's average level
127
  const avgLevel = await this.getPlayerAverageLevel();
128
 
129
- // TODO: When piclet types are available, filter for uncaught piclets
130
- // For now, generate placeholder encounters
 
 
 
 
 
 
 
 
 
131
  const encounterCount = MIN_WILD_ENCOUNTERS + Math.floor(Math.random() * (MAX_WILD_ENCOUNTERS - MIN_WILD_ENCOUNTERS + 1));
132
 
133
  for (let i = 0; i < encounterCount; i++) {
 
 
 
134
  const levelVariance = Math.floor(Math.random() * (LEVEL_VARIANCE * 2 + 1)) - LEVEL_VARIANCE;
135
  const enemyLevel = Math.max(1, avgLevel + levelVariance);
136
 
137
  encounters.push({
138
  type: EncounterType.WILD_PICLET,
139
- title: `Wild Piclet Appeared!`,
140
- description: `A level ${enemyLevel} piclet blocks your path!`,
141
- picletTypeId: `wild-${i + 1}`, // Placeholder ID
142
  enemyLevel,
143
  createdAt: new Date()
144
  });
@@ -168,68 +180,33 @@ export class EncounterService {
168
 
169
  // Catch a wild piclet (for first encounter)
170
  static async catchWildPiclet(encounter: Encounter): Promise<PicletInstance> {
171
- // Get the discovered piclet data
172
- const discoveredPiclets = await db.monsters.toArray();
173
- const picletData = discoveredPiclets.find(p => p.id?.toString() === encounter.picletTypeId) || discoveredPiclets[0];
174
-
175
- const newPiclet: Omit<PicletInstance, 'id'> = {
176
- typeId: encounter.picletTypeId!,
177
- nickname: picletData?.name || 'Starter Piclet',
178
- primaryTypeString: 'normal',
179
-
180
- // Stats based on level
181
- level: encounter.enemyLevel || 5,
182
- xp: 0,
183
- currentHp: 20 + (encounter.enemyLevel || 5) * 4,
184
- maxHp: 20 + (encounter.enemyLevel || 5) * 4,
185
- attack: 10 + (encounter.enemyLevel || 5) * 2,
186
- defense: 10 + (encounter.enemyLevel || 5) * 2,
187
- fieldAttack: 10 + (encounter.enemyLevel || 5) * 2,
188
- fieldDefense: 10 + (encounter.enemyLevel || 5) * 2,
189
- speed: 10 + (encounter.enemyLevel || 5) * 2,
190
-
191
- // Base stats
192
- baseHp: 20,
193
- baseAttack: 10,
194
- baseDefense: 10,
195
- baseFieldAttack: 10,
196
- baseFieldDefense: 10,
197
- baseSpeed: 10,
198
-
199
- // Battle moves
200
- moves: [
201
- {
202
- name: 'Tackle',
203
- type: 'normal',
204
- power: 40,
205
- accuracy: 100,
206
- pp: 35,
207
- currentPp: 35,
208
- description: 'A physical attack'
209
- }
210
- ],
211
- nature: 'hardy',
212
-
213
- // Roster
214
- isInRoster: true,
215
- rosterPosition: 0,
216
-
217
- // Metadata
218
- caughtAt: new Date(),
219
- bst: 60,
220
- tier: 'common',
221
- role: 'balanced',
222
- variance: 1,
223
-
224
- // Visuals from discovered data
225
- imageUrl: picletData?.imageUrl || `https://storage.googleapis.com/piclodia/${encounter.picletTypeId}.png`,
226
- imageData: picletData?.imageData,
227
- imageCaption: picletData?.imageCaption || 'A friendly starter piclet',
228
- concept: picletData?.concept || 'starter',
229
- imagePrompt: picletData?.imagePrompt || 'cute starter monster'
230
- };
231
 
232
- const id = await db.picletInstances.add(newPiclet);
233
- return { ...newPiclet, id };
 
234
  }
235
  }
 
62
  type: EncounterType.WILD_PICLET,
63
  title: 'Your First Piclet!',
64
  description: 'A friendly piclet appears! This one seems easy to catch.',
65
+ picletTypeId: firstDiscovered.name.toLowerCase().replace(/\s+/g, '-'),
66
  enemyLevel: 5,
67
  createdAt: new Date()
68
  });
 
126
  // Get player's average level
127
  const avgLevel = await this.getPlayerAverageLevel();
128
 
129
+ // Get all discovered monsters
130
+ const discoveredMonsters = await db.monsters.toArray();
131
+ if (discoveredMonsters.length === 0) return encounters;
132
+
133
+ // Get player's caught piclets to potentially exclude them
134
+ const caughtPiclets = await db.picletInstances.toArray();
135
+ const caughtTypeIds = new Set(caughtPiclets.map(p => p.typeId));
136
+
137
+ // Filter for uncaught monsters (optional - you might want to allow catching duplicates)
138
+ const availableMonsters = discoveredMonsters; // Or filter: .filter(m => !caughtTypeIds.has(m.name.toLowerCase().replace(/\s+/g, '-')))
139
+
140
  const encounterCount = MIN_WILD_ENCOUNTERS + Math.floor(Math.random() * (MAX_WILD_ENCOUNTERS - MIN_WILD_ENCOUNTERS + 1));
141
 
142
  for (let i = 0; i < encounterCount; i++) {
143
+ // Pick a random monster from discovered ones
144
+ const monster = availableMonsters[Math.floor(Math.random() * availableMonsters.length)];
145
+
146
  const levelVariance = Math.floor(Math.random() * (LEVEL_VARIANCE * 2 + 1)) - LEVEL_VARIANCE;
147
  const enemyLevel = Math.max(1, avgLevel + levelVariance);
148
 
149
  encounters.push({
150
  type: EncounterType.WILD_PICLET,
151
+ title: `Wild ${monster.name} Appeared!`,
152
+ description: `A level ${enemyLevel} ${monster.name} blocks your path!`,
153
+ picletTypeId: monster.name.toLowerCase().replace(/\s+/g, '-'),
154
  enemyLevel,
155
  createdAt: new Date()
156
  });
 
180
 
181
  // Catch a wild piclet (for first encounter)
182
  static async catchWildPiclet(encounter: Encounter): Promise<PicletInstance> {
183
+ if (!encounter.picletTypeId) throw new Error('No piclet type specified');
184
+
185
+ // Import the conversion function
186
+ const { monsterToPicletInstance } = await import('./piclets');
187
+
188
+ // Get the monster data by name (typeId is the lowercase hyphenated name)
189
+ const monsterName = encounter.picletTypeId.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
190
+ const monster = await db.monsters
191
+ .where('name')
192
+ .equals(monsterName)
193
+ .first();
194
+
195
+ if (!monster) {
196
+ throw new Error(`Monster not found: ${monsterName}`);
197
+ }
198
+
199
+ // Convert monster to piclet instance
200
+ const picletData = await monsterToPicletInstance(monster, encounter.enemyLevel || 5);
201
+
202
+ // Set roster position 0 if this is the first piclet
203
+ const existingPiclets = await db.picletInstances.toArray();
204
+ if (existingPiclets.length === 0) {
205
+ picletData.rosterPosition = 0;
206
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
 
208
+ // Save the new piclet
209
+ const id = await db.picletInstances.add(picletData);
210
+ return { ...picletData, id };
211
  }
212
  }