import { db } from './index'; import type { Encounter, PicletInstance } from './schema'; import { EncounterType } from './schema'; import { getOrCreateGameState, markEncountersRefreshed } from './gameState'; // 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 { 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 { await db.encounters.clear(); await markEncountersRefreshed(); } // Get current encounters static async getCurrentEncounters(): Promise { return await db.encounters .orderBy('createdAt') .reverse() .toArray(); } // Clear all encounters static async clearEncounters(): Promise { await db.encounters.clear(); } // Generate new encounters static async generateEncounters(): Promise { const encounters: Omit[] = []; // Check if player has caught any piclets first const playerPiclets = await db.picletInstances.toArray(); if (playerPiclets.length === 0) { // No piclets caught yet - check for discovered piclets // For now, we'll check the monsters collection as a proxy for discovered piclets // In a real app, this would check a remote database for piclets discovered by scanning const discoveredPiclets = await db.monsters.toArray(); if (discoveredPiclets.length === 0) { // No piclets discovered yet - return empty encounters await db.encounters.clear(); await markEncountersRefreshed(); return []; } // Player has discovered but not caught any piclets - show ONLY first catch encounter const firstDiscovered = discoveredPiclets[0]; encounters.push({ type: EncounterType.WILD_PICLET, title: 'Your First Piclet!', description: 'A friendly piclet appears! This one seems easy to catch.', picletTypeId: firstDiscovered.name.toLowerCase().replace(/\s+/g, '-'), enemyLevel: 5, createdAt: new Date() }); // IMPORTANT: Return here - don't add shop/health center for first catch await db.encounters.clear(); await db.encounters.add(encounters[0]); await markEncountersRefreshed(); return await this.getCurrentEncounters(); } // Player has piclets - generate normal encounters // Always add shop and health center first 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() }); // Generate wild piclet encounters const wildEncounters = await this.generateWildEncounters(); encounters.push(...wildEncounters); // 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> { // 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[]> { const encounters: Omit[] = []; // Get player's average level const avgLevel = await this.getPlayerAverageLevel(); // Get all discovered monsters const discoveredMonsters = await db.monsters.toArray(); if (discoveredMonsters.length === 0) return encounters; // Get player's caught piclets to potentially exclude them const caughtPiclets = await db.picletInstances.toArray(); const caughtTypeIds = new Set(caughtPiclets.map(p => p.typeId)); // Filter for uncaught monsters (optional - you might want to allow catching duplicates) const availableMonsters = discoveredMonsters; // Or filter: .filter(m => !caughtTypeIds.has(m.name.toLowerCase().replace(/\s+/g, '-'))) const encounterCount = MIN_WILD_ENCOUNTERS + Math.floor(Math.random() * (MAX_WILD_ENCOUNTERS - MIN_WILD_ENCOUNTERS + 1)); for (let i = 0; i < encounterCount; i++) { // Pick a random monster from discovered ones const monster = availableMonsters[Math.floor(Math.random() * availableMonsters.length)]; const levelVariance = Math.floor(Math.random() * (LEVEL_VARIANCE * 2 + 1)) - LEVEL_VARIANCE; const enemyLevel = Math.max(1, avgLevel + levelVariance); encounters.push({ type: EncounterType.WILD_PICLET, title: `Wild ${monster.name} Appeared!`, description: `A level ${enemyLevel} ${monster.name} blocks your path!`, picletTypeId: monster.name.toLowerCase().replace(/\s+/g, '-'), enemyLevel, createdAt: new Date() }); } return encounters; } // Get player's average piclet level private static async getPlayerAverageLevel(): Promise { const rosterPiclets = await db.picletInstances .where('isInRoster') .equals(1) // Dexie uses 1 for true in indexed fields .toArray(); if (rosterPiclets.length === 0) { const allPiclets = await db.picletInstances.toArray(); if (allPiclets.length === 0) return 5; // Default starting level const totalLevel = allPiclets.reduce((sum, p) => sum + p.level, 0); return Math.round(totalLevel / allPiclets.length); } const totalLevel = rosterPiclets.reduce((sum, p) => sum + p.level, 0); return Math.round(totalLevel / rosterPiclets.length); } // Catch a wild piclet (for first encounter) static async catchWildPiclet(encounter: Encounter): Promise { if (!encounter.picletTypeId) throw new Error('No piclet type specified'); // Import the conversion function const { monsterToPicletInstance } = await import('./piclets'); // Get the monster data by name (typeId is the lowercase hyphenated name) const monsterName = encounter.picletTypeId.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); const monster = await db.monsters .where('name') .equals(monsterName) .first(); if (!monster) { throw new Error(`Monster not found: ${monsterName}`); } // Convert monster to piclet instance const picletData = await monsterToPicletInstance(monster, encounter.enemyLevel || 5); // Set roster position 0 if this is the first piclet const existingPiclets = await db.picletInstances.toArray(); if (existingPiclets.length === 0) { picletData.rosterPosition = 0; } // Save the new piclet const id = await db.picletInstances.add(picletData); return { ...picletData, id }; } }