/** * InventoryManager - Handles player inventory tracking and progression path generation * Determines tier based on available items and creates personalized photo challenges */ class InventoryManager { constructor() { this.inventory = null; this.tier = 0; this.totalItems = 0; } /** * Store player's inventory from questionnaire */ setInventory(inventoryData) { this.inventory = inventoryData; this.totalItems = this.countTotalItems(); this.tier = this.calculateTier(); console.log('📦 Inventory set:', this.totalItems, 'items, Tier', this.tier); return { inventory: this.inventory, tier: this.tier, totalItems: this.totalItems }; } /** * Count total non-none items across all categories */ countTotalItems() { let count = 0; // Count clothing items for (const [key, value] of Object.entries(this.inventory.clothing || {})) { if (value && value !== 'none') count++; } // Count accessories for (const [key, value] of Object.entries(this.inventory.accessories || {})) { if (value && value !== 'none') count++; } // Count toys for (const [key, value] of Object.entries(this.inventory.toys || {})) { if (value && value !== 'none') count++; } // Count environment items (booleans) for (const [key, value] of Object.entries(this.inventory.environment || {})) { if (value === true) count++; } return count; } /** * Calculate tier based on total items */ calculateTier() { if (this.totalItems >= 18) return 5; if (this.totalItems >= 13) return 4; if (this.totalItems >= 8) return 3; if (this.totalItems >= 4) return 2; return 1; } /** * Get available items as array */ getAvailableItems() { const items = []; // Collect clothing items for (const [key, value] of Object.entries(this.inventory.clothing || {})) { if (value && value !== 'none') { items.push({ category: 'clothing', type: key, level: value }); } } // Collect accessories for (const [key, value] of Object.entries(this.inventory.accessories || {})) { if (value && value !== 'none') { items.push({ category: 'accessories', type: key, level: value }); } } // Collect toys for (const [key, value] of Object.entries(this.inventory.toys || {})) { if (value && value !== 'none') { items.push({ category: 'toys', type: key, level: value }); } } return items; } /** * Check if player has specific item */ hasItem(itemType) { const allItems = { ...this.inventory.clothing, ...this.inventory.accessories, ...this.inventory.toys, ...this.inventory.environment }; return allItems[itemType] && allItems[itemType] !== 'none'; } /** * Get item level (none/basic/sexy/multiple, etc.) */ getItemLevel(itemType) { const allItems = { ...this.inventory.clothing, ...this.inventory.accessories, ...this.inventory.toys }; return allItems[itemType] || 'none'; } /** * Generate photo progression based on tier */ generatePhotoProgression() { const availableItems = this.getAvailableItems(); const photoCount = this.getPhotoCountForTier(); console.log(`📸 Generating ${photoCount} photo challenges for Tier ${this.tier}`); const progression = []; for (let i = 1; i <= photoCount; i++) { const challenge = this.generatePhotoChallenge(i, photoCount, availableItems); progression.push(challenge); } return progression; } /** * Get photo count based on tier */ getPhotoCountForTier() { const counts = { 1: 5, 2: 10, 3: 15, 4: 20, 5: 25 }; return counts[this.tier] || 5; } /** * Generate individual photo challenge */ generatePhotoChallenge(challengeNumber, totalChallenges, availableItems) { const intensity = this.calculateChallengeIntensity(challengeNumber, totalChallenges); const requiredItems = this.selectItemsForChallenge(challengeNumber, availableItems); const includesEdging = this.shouldIncludeEdging(challengeNumber, totalChallenges, requiredItems); const poseCategory = this.determinePoseCategory(requiredItems, includesEdging); // Generate instruction text const instruction = this.generateInstructionText(challengeNumber, totalChallenges, requiredItems, poseCategory, includesEdging); return { number: challengeNumber, intensity: intensity, requiredItems: requiredItems, edging: includesEdging, poseCategory: poseCategory, instruction: instruction }; } /** * Generate instruction text for photo challenge */ generateInstructionText(challengeNumber, totalChallenges, requiredItems, poseCategory, includesEdging) { // Build item list with specific instructions for special items const itemDescriptions = []; const specialInstructions = []; requiredItems.forEach(item => { if (item === 'dildos-oral') { itemDescriptions.push('dildo'); specialInstructions.push('suck the dildo or slap your face with it'); } else { itemDescriptions.push(item); } }); const progressText = `Photo ${challengeNumber} of ${totalChallenges}`; const edgingText = includesEdging ? ' Edge while posing.' : ''; // Handle nude photos if (requiredItems.length === 0) { const nudePoses = [ 'Pose nude for the camera', 'Strike a confident nude pose', 'Show yourself completely exposed', 'Display your naked body', 'Present yourself with nothing on', 'Kneel nude and look at the camera', 'Bend over and show everything', 'Lie on your back nude', 'Stand with legs spread', 'Sit nude and spread your legs' ]; const nudeText = nudePoses[challengeNumber % nudePoses.length]; return `${progressText} - ${nudeText}.${edgingText}`; } const itemList = itemDescriptions.join(', '); // Check if user has multiple of any items and suggest variety const varietyItems = this.getVarietyItems(requiredItems); const varietyText = varietyItems.length > 0 ? ` Try a different ${varietyItems.join(' or ')} than last time.` : ''; // Special instructions for specific items const specialText = specialInstructions.length > 0 ? ` ${specialInstructions.join('. ')}.` : ''; // Detailed pose instructions based on category const poseInstructions = { 'standing': [ 'Stand with hands on hips', 'Stand with arms behind your head', 'Strike a confident standing pose', 'Stand and show off your outfit', 'Stand with one leg forward' ], 'sitting': [ 'Sit with legs crossed', 'Sit and spread your legs', 'Sit on the edge of a chair', 'Sit with hands on your knees', 'Recline while seated' ], 'kneeling': [ 'Kneel with hands behind your back', 'Kneel and look up at the camera', 'Kneel with legs spread', 'Kneel and present yourself', 'Kneel on all fours' ], 'lying': [ 'Lie on your back', 'Lie on your side', 'Lie face down', 'Recline and show off', 'Lie with legs spread' ], 'mirror': [ 'Admire yourself in the mirror', 'Take a mirror selfie', 'Pose in front of the mirror', 'Check yourself out in the mirror', 'Show your outfit in the mirror' ], 'provocative': [ 'Bend over and look back', 'Arch your back', 'Strike a seductive pose', 'Pose provocatively', 'Show off your assets' ], 'submissive': [ 'Kneel submissively', 'Lower your head in submission', 'Present yourself submissively', 'Assume a submissive position', 'Display your obedience' ], 'edging': [ 'Edge while looking at the camera', 'Edge and pose', 'Touch yourself while posing', 'Edge in position', 'Stroke while holding the pose' ], 'toy': [ 'Display your toys', 'Pose with toys visible', 'Show off your toy collection', 'Present your toys', 'Hold your toys for the camera' ], 'heel': [ 'Show off your heels - legs extended', 'Stand on tiptoes in your heels', 'Sit and display your heels', 'Pose to highlight your heels', 'Walk pose in your heels' ], 'dress': [ 'Twirl in your dress/skirt', 'Lift your dress/skirt slightly', 'Model your outfit', 'Show off your dress/skirt', 'Pose to display your outfit' ], 'bra': [ 'Show off your bra', 'Display your lingerie', 'Cup your chest', 'Present your bra', 'Pose to highlight your lingerie' ], 'panty': [ 'Show off your panties', 'Display what you\'re wearing', 'Pull your panties to the side', 'Present yourself in panties', 'Bend over in your panties' ] }; // Get pose text - use challenge number to rotate through options const poseOptions = poseInstructions[poseCategory] || [ 'Strike a confident pose', 'Pose seductively', 'Show everything to the camera', 'Present yourself', 'Display yourself' ]; const poseText = poseOptions[challengeNumber % poseOptions.length]; return `${progressText} - ${poseText} wearing ${itemList}.${specialText}${varietyText}${edgingText}`; } /** * Get items that user has multiple of for variety suggestions */ getVarietyItems(requiredItems) { const varietyItems = []; requiredItems.forEach(itemKey => { const level = this.getItemLevel(itemKey); if (level === 'multiple') { // Convert item key to friendly name const friendlyNames = { 'panties': 'pair of panties', 'bras': 'bra', 'lingerie': 'lingerie set', 'dresses': 'dress', 'skirts': 'skirt', 'pantyhose': 'pantyhose', 'heels': 'heels', 'wigs': 'wig' }; if (friendlyNames[itemKey]) { varietyItems.push(friendlyNames[itemKey]); } } }); return varietyItems; } /** * Calculate intensity (0-1) based on progression */ calculateChallengeIntensity(challengeNumber, totalChallenges) { return challengeNumber / totalChallenges; } /** * Select items to use for this specific challenge * Considers item compatibility and mutual exclusivity */ selectItemsForChallenge(challengeNumber, availableItems) { const items = []; const intensity = challengeNumber / this.getPhotoCountForTier(); // Track what's already selected to avoid conflicts let hasUnderwear = false; let hasAnalToy = false; let hasChastity = false; // Early photos (0-15%) - start nude or minimal if (intensity < 0.15) { // Nude poses to start return []; } // Lingerie is a complete set - if selected, use it instead of bra/panties if (intensity >= 0.25 && this.hasItem('lingerie')) { items.push('lingerie'); hasUnderwear = true; } else { // Otherwise build from bra and panties separately if (intensity >= 0.15 && this.hasItem('panties')) { items.push('panties'); hasUnderwear = true; } if (intensity >= 0.2 && this.hasItem('bras')) { items.push('bras'); } } // Add dress/skirt around 30% (goes over underwear) if (intensity >= 0.3) { if (this.hasItem('dresses')) items.push('dresses'); else if (this.hasItem('skirts')) items.push('skirts'); } // Add pantyhose around 40% (under dress/skirt, over panties) if (intensity >= 0.4 && this.hasItem('pantyhose')) { items.push('pantyhose'); } // Add heels around 45% if (intensity >= 0.45 && this.hasItem('heels')) { items.push('heels'); } // Add wig around 55% if (intensity >= 0.55 && this.hasItem('wigs')) { items.push('wigs'); } // Add makeup around 60% if (intensity >= 0.6 && this.hasItem('makeup')) { items.push('makeup'); } // Add jewelry around 65% if (intensity >= 0.65 && this.hasItem('jewelry')) { items.push('jewelry'); } // Chastity check - if wearing chastity, mark it if (intensity >= 0.5 && this.hasItem('chastity')) { items.push('chastity'); hasChastity = true; } // Anal toys - only ONE at a time (plug OR dildo, not both) // Prefer plug first, then switch to dildo later if (intensity >= 0.5 && intensity < 0.7 && !hasAnalToy) { if (this.hasItem('plugs')) { items.push('plugs'); hasAnalToy = true; } } // Hand-held toys (can use if no current anal plug, or for specific actions) // Around 70%, switch from plug to dildo with specific actions if (intensity >= 0.7 && this.hasItem('dildos')) { // If we had a plug earlier, remove it and add dildo instead const plugIndex = items.indexOf('plugs'); if (plugIndex > -1) { items.splice(plugIndex, 1); } // Dildo with specific action items.push('dildos-oral'); hasAnalToy = true; } // Vibrator (can be used regardless of other toys) if (intensity >= 0.75 && this.hasItem('vibrators') && !hasChastity) { items.push('vibrators'); } // Restraints around 78% if (intensity >= 0.78 && this.hasItem('restraints')) { items.push('restraints'); } // Nipple clamps around 82% if (intensity >= 0.82 && this.hasItem('nippleClamps')) { items.push('nippleClamps'); } // Gags around 88% if (intensity >= 0.88 && this.hasItem('gags')) { items.push('gags'); } return items; } /** * Determine if challenge should include edging * Cannot edge if wearing chastity cage */ shouldIncludeEdging(challengeNumber, totalChallenges, requiredItems) { // No edging if chastity cage is involved if (requiredItems.includes('chastity')) return false; const intensity = challengeNumber / totalChallenges; // No edging in first 20% if (intensity < 0.2) return false; // Occasional edging in middle if (intensity < 0.6) return challengeNumber % 3 === 0; // Frequent edging in later stages if (intensity < 0.8) return challengeNumber % 2 === 0; // Almost always edging in final stages return true; } /** * Determine pose category based on items */ determinePoseCategory(requiredItems, includesEdging) { // If edging, select edging pose category if (includesEdging) return 'edging'; // If toys present, toy poses if (requiredItems.some(item => ['plugs', 'dildos', 'chastity', 'gags', 'nippleClamps'].includes(item))) { return 'toy'; } // If heels, heel poses if (requiredItems.includes('heels')) return 'heel'; // If dress/skirt, dress poses if (requiredItems.includes('dresses') || requiredItems.includes('skirts')) { return 'dress'; } // If bra, bra poses if (requiredItems.includes('bras')) return 'bra'; // If just panties, panty poses if (requiredItems.includes('panties')) return 'panty'; // Default to basic return 'basic'; } /** * Get inventory summary for display */ getInventorySummary() { const summary = { tier: this.tier, totalItems: this.totalItems, photoCount: this.getPhotoCountForTier(), itemsByCategory: { clothing: [], accessories: [], toys: [], environment: [] } }; // Collect clothing for (const [key, value] of Object.entries(this.inventory.clothing || {})) { if (value && value !== 'none') { summary.itemsByCategory.clothing.push(`${this.formatItemName(key)}: ${value}`); } } // Collect accessories for (const [key, value] of Object.entries(this.inventory.accessories || {})) { if (value && value !== 'none') { summary.itemsByCategory.accessories.push(`${this.formatItemName(key)}: ${value}`); } } // Collect toys for (const [key, value] of Object.entries(this.inventory.toys || {})) { if (value && value !== 'none') { summary.itemsByCategory.toys.push(`${this.formatItemName(key)}: ${value}`); } } // Collect environment for (const [key, value] of Object.entries(this.inventory.environment || {})) { if (value === true) { summary.itemsByCategory.environment.push(this.formatItemName(key)); } } return summary; } /** * Format item name for display */ formatItemName(key) { const names = { panties: 'Panties', bras: 'Bras', dresses: 'Dresses', skirts: 'Skirts', pantyhose: 'Pantyhose/Stockings', heels: 'Heels', wigs: 'Wigs', lingerie: 'Lingerie', makeup: 'Makeup', jewelry: 'Jewelry', nailPolish: 'Nail Polish', dildos: 'Dildos', plugs: 'Butt Plugs', chastity: 'Chastity Device', restraints: 'Restraints', gags: 'Gags', nippleClamps: 'Nipple Clamps', mirror: 'Mirror', fullMirror: 'Full-Length Mirror', privateSpace: 'Private Space', phoneStand: 'Phone/Camera Stand' }; return names[key] || key; } } // Make available globally window.InventoryManager = InventoryManager;