File size: 11,858 Bytes
465b043
 
 
 
5fe1a3d
465b043
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5fe1a3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465b043
 
 
 
99ecf7d
 
 
7428b13
99ecf7d
ab59033
 
99ecf7d
ab59033
 
99ecf7d
ab59033
 
 
 
 
 
 
99ecf7d
ab59033
 
 
 
99ecf7d
 
 
 
 
7428b13
 
23a36f6
465b043
7428b13
99ecf7d
 
7428b13
a21678d
 
 
 
 
 
465b043
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99ecf7d
 
 
90a88fc
99ecf7d
 
a21678d
 
90a88fc
99ecf7d
 
23a36f6
90a88fc
465b043
a21678d
465b043
 
23a36f6
 
90a88fc
465b043
 
 
23a36f6
 
 
a21678d
465b043
23a36f6
 
 
465b043
 
a21678d
 
 
 
465b043
 
a21678d
465b043
 
 
 
 
 
 
 
 
 
 
99ecf7d
 
465b043
99ecf7d
 
465b043
 
 
 
 
 
5fe1a3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23a36f6
465b043
90a88fc
 
99ecf7d
 
 
90a88fc
23a36f6
 
90a88fc
 
23a36f6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90a88fc
99ecf7d
 
 
 
 
 
23a36f6
 
90a88fc
465b043
90a88fc
23a36f6
 
465b043
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
import { db } from './index';
import type { Encounter, PicletInstance } from './schema';
import { EncounterType } from './schema';
import { getOrCreateGameState, markEncountersRefreshed } from './gameState';
import { getCaughtPiclets, getUncaughtPiclets } from './piclets';

// Configuration
const ENCOUNTER_REFRESH_HOURS = 2;
const MIN_WILD_ENCOUNTERS = 2;
const MAX_WILD_ENCOUNTERS = 3;
const LEVEL_VARIANCE = 2;

export class EncounterService {
  // Check if encounters should be refreshed
  static async shouldRefreshEncounters(): Promise<boolean> {
    const state = await getOrCreateGameState();
    const hoursSinceRefresh = (Date.now() - state.lastEncounterRefresh.getTime()) / (1000 * 60 * 60);
    return hoursSinceRefresh >= ENCOUNTER_REFRESH_HOURS;
  }

  // Force encounter refresh
  static async forceEncounterRefresh(): Promise<void> {
    await db.encounters.clear();
    await markEncountersRefreshed();
  }

  // Get current encounters
  static async getCurrentEncounters(): Promise<Encounter[]> {
    return await db.encounters
      .orderBy('createdAt')
      .reverse()
      .toArray();
  }

  // Clear all encounters
  static async clearEncounters(): Promise<void> {
    await db.encounters.clear();
  }

  // Create a "Your First Piclet" encounter from an uncaught Piclet
  static async createFirstPicletEncounter(picletId: number): Promise<Encounter> {
    const piclet = await db.picletInstances.get(picletId);
    if (!piclet) {
      throw new Error(`Piclet with ID ${picletId} not found`);
    }

    const encounter: Omit<Encounter, 'id'> = {
      type: EncounterType.FIRST_PICLET,
      title: '✨ Your First Piclet! ✨',
      description: `${piclet.nickname} wants to join your team! This special encounter will automatically catch them for you.`,
      picletInstanceId: picletId,
      createdAt: new Date()
    };

    const id = await db.encounters.add(encounter);
    return { ...encounter, id };
  }

  // Check if the player should get a "Your First Piclet" encounter
  static async shouldCreateFirstPicletEncounter(): Promise<number | null> {
    const caughtPiclets = await getCaughtPiclets();
    if (caughtPiclets.length > 0) {
      return null; // Player already has caught Piclets
    }

    const uncaughtPiclets = await getUncaughtPiclets();
    if (uncaughtPiclets.length === 0) {
      return null; // No uncaught Piclets available
    }

    // Return the ID of the most recently created uncaught Piclet
    const mostRecent = uncaughtPiclets.sort((a, b) => (b.id || 0) - (a.id || 0))[0];
    return mostRecent.id || null;
  }

  // Generate new encounters
  static async generateEncounters(): Promise<Encounter[]> {
    const encounters: Omit<Encounter, 'id'>[] = [];
    
    // Check for "Your First Piclet" scenario first
    const caughtPiclets = await getCaughtPiclets();
    const uncaughtPiclets = await getUncaughtPiclets();
    
    if (caughtPiclets.length === 0 && uncaughtPiclets.length > 0) {
      // Player has no caught piclets but has uncaught ones - create/return ONLY first piclet encounters
      console.log('Player has uncaught piclets but no caught ones - creating first piclet encounters');
      
      // Clear existing encounters first
      await db.encounters.clear();
      
      // Create first piclet encounters for all uncaught piclets
      const newEncounters: Encounter[] = [];
      for (const uncaughtPiclet of uncaughtPiclets) {
        if (uncaughtPiclet.id) {
          const encounter = await this.createFirstPicletEncounter(uncaughtPiclet.id);
          newEncounters.push(encounter);
        }
      }
      
      await markEncountersRefreshed();
      console.log('Created', newEncounters.length, 'first piclet encounters');
      return newEncounters;
    }
    
    if (caughtPiclets.length === 0 && uncaughtPiclets.length === 0) {
      // No piclets at all - return empty encounters
      console.log('No piclets found - returning empty encounters');
      await db.encounters.clear();
      await markEncountersRefreshed();
      return [];
    }
    
    // Player has caught piclets - generate normal encounters
    console.log('Generating normal encounters for player with caught piclets');
    
    // Generate wild piclet encounters FIRST to ensure they're included
    const wildEncounters = await this.generateWildEncounters();
    console.log('Wild encounters generated:', wildEncounters.length);
    encounters.push(...wildEncounters);
    
    // Always add shop and health center
    encounters.push({
      type: EncounterType.SHOP,
      title: 'Piclet Shop',
      description: 'Buy items and supplies for your journey',
      createdAt: new Date()
    });

    encounters.push({
      type: EncounterType.HEALTH_CENTER,
      title: 'Health Center',
      description: 'Heal your piclets back to full health',
      createdAt: new Date()
    });

    // Clear existing encounters and add new ones
    await db.encounters.clear();
    for (const encounter of encounters) {
      await db.encounters.add(encounter);
    }

    await markEncountersRefreshed();
    return await this.getCurrentEncounters();
  }

  // Create first catch encounter
  private static async createFirstCatchEncounter(): Promise<Omit<Encounter, 'id'>> {
    // TODO: Replace with actual piclet data when available
    // For now, using placeholder data
    return {
      type: EncounterType.WILD_PICLET,
      title: 'Your First Piclet!',
      description: 'A friendly piclet appears! This one seems easy to catch.',
      picletTypeId: 'starter-001', // Placeholder ID
      enemyLevel: 5,
      createdAt: new Date()
    };
  }

  // Generate wild piclet encounters
  private static async generateWildEncounters(): Promise<Omit<Encounter, 'id'>[]> {
    const encounters: Omit<Encounter, 'id'>[] = [];
    
    // Get player's average level
    const avgLevel = await this.getPlayerAverageLevel();
    
    // Get caught piclets only (these can appear as wild encounters)
    const caughtPiclets = await getCaughtPiclets();
    console.log('Caught piclets for wild encounters:', caughtPiclets.length);
    
    if (caughtPiclets.length === 0) {
      console.log('No caught piclets - returning empty wild encounters');
      return encounters;
    }
    
    // Use caught piclets as templates for wild encounters
    const availablePiclets = caughtPiclets;
    console.log('Available piclets for encounters:', availablePiclets.map(p => p.typeId));
    
    const encounterCount = MIN_WILD_ENCOUNTERS + Math.floor(Math.random() * (MAX_WILD_ENCOUNTERS - MIN_WILD_ENCOUNTERS + 1));
    console.log('Generating', encounterCount, 'wild encounters');
    
    for (let i = 0; i < encounterCount; i++) {
      // Pick a random piclet from available ones
      const piclet = availablePiclets[Math.floor(Math.random() * availablePiclets.length)];
      
      const levelVariance = Math.floor(Math.random() * (LEVEL_VARIANCE * 2 + 1)) - LEVEL_VARIANCE;
      const enemyLevel = Math.max(1, avgLevel + levelVariance);
      
      // Use the piclet's nickname or typeId for display
      const displayName = piclet.nickname || piclet.typeId.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
      
      const wildEncounter = {
        type: EncounterType.WILD_PICLET,
        title: `Wild ${displayName} Appeared!`,
        description: `A level ${enemyLevel} ${displayName} blocks your path!`,
        picletTypeId: piclet.typeId,
        enemyLevel,
        createdAt: new Date()
      };
      
      console.log('Created wild encounter:', wildEncounter.title, 'with typeId:', wildEncounter.picletTypeId);
      encounters.push(wildEncounter);
    }
    
    console.log('Generated', encounters.length, 'wild encounters');
    return encounters;
  }

  // Get player's average piclet level
  private static async getPlayerAverageLevel(): Promise<number> {
    const rosterPiclets = await db.picletInstances
      .where('isInRoster')
      .equals(1) // Dexie uses 1 for true in indexed fields
      .toArray();
    
    if (rosterPiclets.length === 0) {
      const caughtPiclets = await getCaughtPiclets();
      if (caughtPiclets.length === 0) return 5; // Default starting level
      
      const totalLevel = caughtPiclets.reduce((sum, p) => sum + p.level, 0);
      return Math.round(totalLevel / caughtPiclets.length);
    }
    
    const totalLevel = rosterPiclets.reduce((sum, p) => sum + p.level, 0);
    return Math.round(totalLevel / rosterPiclets.length);
  }

  // Catch your first Piclet (marks an existing uncaught Piclet as caught)
  static async catchFirstPiclet(encounter: Encounter): Promise<PicletInstance> {
    if (!encounter.picletInstanceId) {
      throw new Error('No piclet instance ID specified for first Piclet encounter');
    }

    const piclet = await db.picletInstances.get(encounter.picletInstanceId);
    if (!piclet) {
      throw new Error(`Piclet with ID ${encounter.picletInstanceId} not found`);
    }

    if (piclet.caught) {
      throw new Error('This Piclet has already been caught');
    }

    // Mark the Piclet as caught and put it in roster position 0 (first Piclet)
    const updatedPiclet: PicletInstance = {
      ...piclet,
      caught: true,
      caughtAt: new Date(),
      isInRoster: true,
      rosterPosition: 0
    };

    await db.picletInstances.update(encounter.picletInstanceId, {
      caught: true,
      caughtAt: new Date(),
      isInRoster: true,
      rosterPosition: 0
    });

    return updatedPiclet;
  }

  // Catch a wild piclet (creates a new instance based on existing piclet type)
  static async catchWildPiclet(encounter: Encounter): Promise<PicletInstance> {
    if (!encounter.picletTypeId) throw new Error('No piclet type specified');
    
    // Find a caught piclet instance with this typeId to use as a template
    const caughtPiclets = await getCaughtPiclets();
    const templatePiclet = caughtPiclets.find(p => p.typeId === encounter.picletTypeId);
    
    if (!templatePiclet) {
      throw new Error(`Piclet type not found: ${encounter.picletTypeId}`);
    }
    
    // Create a new piclet instance based on the template but with different stats/level
    const newLevel = encounter.enemyLevel || 5;
    
    // Calculate new stats based on level (using template's base stats)
    const calculateStat = (base: number, level: number) => Math.floor((base * level) / 50 + 5);
    const calculateHp = (base: number, level: number) => Math.floor((base * level) / 50 + level + 10);
    
    const newPiclet: Omit<PicletInstance, 'id'> = {
      ...templatePiclet,
      level: newLevel,
      xp: 0,
      currentHp: calculateHp(templatePiclet.baseHp, newLevel),
      maxHp: calculateHp(templatePiclet.baseHp, newLevel),
      attack: calculateStat(templatePiclet.baseAttack, newLevel),
      defense: calculateStat(templatePiclet.baseDefense, newLevel),
      fieldAttack: calculateStat(templatePiclet.baseFieldAttack, newLevel),
      fieldDefense: calculateStat(templatePiclet.baseFieldDefense, newLevel),
      speed: calculateStat(templatePiclet.baseSpeed, newLevel),
      
      // Reset move PP to full
      moves: templatePiclet.moves.map(move => ({
        ...move,
        currentPp: move.pp
      })),
      
      // Clear roster info for wild catch
      isInRoster: false,
      rosterPosition: undefined,
      caughtAt: new Date()
    };
    
    // Wild piclets are always caught
    newPiclet.caught = true;
    
    // Set roster position 0 if this is the first caught piclet
    const existingCaughtPiclets = await getCaughtPiclets();
    if (existingCaughtPiclets.length === 1) { // Only one caught piclet exists (the template)
      newPiclet.rosterPosition = 0;
      newPiclet.isInRoster = true;
    }
    
    // Save the new piclet
    const id = await db.picletInstances.add(newPiclet);
    return { ...newPiclet, id };
  }
}