/** * MarriageRomanceSystem.js * ======================= * KRVAVA Ε½ETEV - Marriage & Romance System (P10) * * Features: * - Heart tracking (0-10 hearts per NPC) * - Gift system (loved/liked/neutral/disliked) * - Dating mechanics (8+ hearts) * - Date events (5 types) * - Marriage proposal system * - Wedding ceremony * - Married life mechanics * - 12 romance questlines * * @author NovaFarma Team * @date 2025-12-23 */ export default class MarriageRomanceSystem { constructor(scene) { this.scene = scene; // Romance data this.romanceData = new Map(); // npcId -> romance state this.marriageStatus = { isMarried: false, spouse: null, marriageDate: null, anniversaryDate: null }; // Dating status this.datingStatus = new Map(); // npcId -> dating/engaged/married // Current date event this.activeDate = null; console.log('πŸ’ MarriageRomanceSystem initialized'); } /** * 10.1 - Romance Hearts System * Track heart levels for each romanceable NPC */ initializeRomanceableNPC(npcId, name, gender = 'female') { if (this.romanceData.has(npcId)) { return; // Already initialized } const romanceState = { npcId: npcId, name: name, gender: gender, hearts: 0, // 0-10 hearts maxHearts: 10, isRomanceable: true, isDating: false, isEngaged: false, isMarried: false, // Heart events (cutscenes) heartEvents: { 1: { seen: false, sceneId: `${npcId}_heart_1` }, 2: { seen: false, sceneId: `${npcId}_heart_2` }, 3: { seen: false, sceneId: `${npcId}_heart_3` }, 4: { seen: false, sceneId: `${npcId}_heart_4` }, 5: { seen: false, sceneId: `${npcId}_heart_5` }, 6: { seen: false, sceneId: `${npcId}_heart_6` }, 7: { seen: false, sceneId: `${npcId}_heart_7` }, 8: { seen: false, sceneId: `${npcId}_heart_8` }, 9: { seen: false, sceneId: `${npcId}_heart_9` }, 10: { seen: false, sceneId: `${npcId}_heart_10` } }, // Gift preferences lovedItems: [], likedItems: [], neutralItems: [], dislikedItems: [], hatedItems: [], // Birthday birthday: { season: 'Spring', day: 1 }, // Romance questline questlineId: `romance_${npcId}`, questlineComplete: false, // Stats giftsGiven: 0, conversationsHad: 0, datesCompleted: 0, lastGiftDate: null, lastTalkDate: null }; this.romanceData.set(npcId, romanceState); console.log(`πŸ’• Initialized romance for ${name}`); return romanceState; } /** * 10.1 - Add hearts to NPC */ addHearts(npcId, amount, reason = 'unknown') { const romanceState = this.romanceData.get(npcId); if (!romanceState) { console.warn(`Romance data not found for ${npcId}`); return false; } const oldHearts = romanceState.hearts; romanceState.hearts = Math.min(romanceState.maxHearts, romanceState.hearts + amount); console.log(`πŸ’• ${romanceState.name}: ${oldHearts} β†’ ${romanceState.hearts} hearts (${reason})`); // Trigger heart event if just reached new level const newLevel = Math.floor(romanceState.hearts); const oldLevel = Math.floor(oldHearts); if (newLevel > oldLevel) { this.triggerHeartEvent(npcId, newLevel); } // UI update this.updateRomanceUI(npcId); return true; } /** * 10.1 - Trigger heart event cutscene */ triggerHeartEvent(npcId, heartLevel) { const romanceState = this.romanceData.get(npcId); if (!romanceState) return; const event = romanceState.heartEvents[heartLevel]; if (!event || event.seen) return; console.log(`πŸ’ Triggering ${romanceState.name}'s ${heartLevel}-heart event!`); // Mark as seen event.seen = true; // TODO: Trigger actual cutscene // this.scene.cutsceneSystem.play(event.sceneId); // Show notification this.showNotification({ title: `${heartLevel} Hearts!`, text: `${romanceState.name} loves you more!`, icon: 'πŸ’•' }); } /** * 10.2 - Gift System */ giveGift(npcId, itemId) { const romanceState = this.romanceData.get(npcId); if (!romanceState) { console.warn(`Cannot gift ${itemId} to unknown NPC ${npcId}`); return false; } // Check if already gifted today const today = this.scene.timeSystem?.getCurrentDate() || new Date(); if (romanceState.lastGiftDate === today) { this.showNotification({ title: 'Already Gifted', text: `You already gave ${romanceState.name} a gift today!`, icon: '🎁' }); return false; } // Check gift preference let heartsGained = 0; let reaction = 'neutral'; if (romanceState.lovedItems.includes(itemId)) { heartsGained = 0.8; // 8/10 of a heart reaction = 'loved'; } else if (romanceState.likedItems.includes(itemId)) { heartsGained = 0.4; // 4/10 of a heart reaction = 'liked'; } else if (romanceState.dislikedItems.includes(itemId)) { heartsGained = -0.2; // Lose 2/10 heart reaction = 'disliked'; } else if (romanceState.hatedItems.includes(itemId)) { heartsGained = -0.4; // Lose 4/10 heart reaction = 'hated'; } else { heartsGained = 0.2; // Neutral = 2/10 heart reaction = 'neutral'; } // Birthday bonus! const isBirthday = this.isBirthday(npcId); if (isBirthday) { heartsGained *= 2; this.showNotification({ title: 'Birthday!', text: `It's ${romanceState.name}'s birthday! Double hearts!`, icon: 'πŸŽ‚' }); } // Apply hearts this.addHearts(npcId, heartsGained, `gift: ${itemId} (${reaction})`); // Update stats romanceState.giftsGiven++; romanceState.lastGiftDate = today; // Show reaction this.showGiftReaction(npcId, itemId, reaction, heartsGained); return true; } /** * 10.2 - Check if NPC's birthday */ isBirthday(npcId) { const romanceState = this.romanceData.get(npcId); if (!romanceState) return false; const currentDate = this.scene.timeSystem?.getCurrentDate(); if (!currentDate) return false; return ( currentDate.season === romanceState.birthday.season && currentDate.day === romanceState.birthday.day ); } /** * 10.2 - Show gift reaction */ showGiftReaction(npcId, itemId, reaction, heartsGained) { const romanceState = this.romanceData.get(npcId); const reactions = { loved: `❀️ ${romanceState.name} loved it! This is her favorite!`, liked: `😊 ${romanceState.name} liked it! Thank you!`, neutral: `πŸ™‚ ${romanceState.name} accepted it politely.`, disliked: `😐 ${romanceState.name} didn't like it much...`, hated: `😠 ${romanceState.name} hated it! Why would you give this?!` }; this.showNotification({ title: 'Gift Given', text: reactions[reaction], icon: heartsGained > 0 ? 'πŸ’•' : 'πŸ’”' }); } /** * 10.3 - Dating Mechanics */ canStartDating(npcId) { const romanceState = this.romanceData.get(npcId); if (!romanceState) return false; return ( romanceState.hearts >= 8 && !romanceState.isDating && !romanceState.isMarried && !this.marriageStatus.isMarried // Can't date if married to someone else ); } /** * 10.3 - Give bouquet (start dating) */ giveBouquet(npcId) { if (!this.canStartDating(npcId)) { console.log('❌ Cannot give bouquet yet'); return false; } const romanceState = this.romanceData.get(npcId); // Check player has bouquet if (!this.scene.inventorySystem?.hasItem('bouquet', 1)) { this.showNotification({ title: 'No Bouquet', text: 'You need a Bouquet to start dating! (20 Flowers)', icon: 'πŸ’' }); return false; } // Remove bouquet from inventory this.scene.inventorySystem.removeItem('bouquet', 1); // Start dating romanceState.isDating = true; this.datingStatus.set(npcId, 'dating'); console.log(`πŸ’‘ Now dating ${romanceState.name}!`); // Trigger dating cutscene this.showDatingCutscene(npcId); return true; } /** * 10.3 - Show dating cutscene */ showDatingCutscene(npcId) { const romanceState = this.romanceData.get(npcId); // TODO: Implement actual cutscene this.showNotification({ title: 'Dating!', text: `πŸ’‘ ${romanceState.name} accepted! You're now dating!`, icon: 'πŸ’•' }); } /** * 10.4 - Date Events */ startDateEvent(npcId, dateType) { const romanceState = this.romanceData.get(npcId); if (!romanceState || !romanceState.isDating) { console.log('❌ Not dating this NPC'); return false; } const dateEvents = { 'beach_picnic': { name: 'Beach Picnic', location: { x: 100, y: 200 }, duration: 120000, // 2 minutes hearts: 0.5 }, 'restaurant': { name: 'Restaurant Dinner', location: { x: 300, y: 400 }, duration: 180000, // 3 minutes hearts: 0.5 }, 'stargazing': { name: 'Stargazing', location: { x: 500, y: 100 }, duration: 150000, // 2.5 minutes hearts: 0.5 }, 'adventure': { name: 'Adventure Date', location: { x: 700, y: 600 }, duration: 300000, // 5 minutes hearts: 1.0 }, 'festival': { name: 'Festival Date', location: { x: 400, y: 300 }, duration: 240000, // 4 minutes hearts: 0.75 } }; const dateEvent = dateEvents[dateType]; if (!dateEvent) { console.error(`Unknown date type: ${dateType}`); return false; } // Start date this.activeDate = { npcId: npcId, type: dateType, startTime: Date.now(), ...dateEvent }; console.log(`πŸ’‘ Starting ${dateEvent.name} with ${romanceState.name}`); // TODO: Implement actual date cutscene/mini-game return true; } /** * 10.4 - Complete date event */ completeDate() { if (!this.activeDate) return; const romanceState = this.romanceData.get(this.activeDate.npcId); // Grant hearts this.addHearts(this.activeDate.npcId, this.activeDate.hearts, 'date event'); // Update stats romanceState.datesCompleted++; this.showNotification({ title: 'Date Complete!', text: `πŸ’• ${romanceState.name} had a wonderful time!`, icon: 'πŸ’‘' }); this.activeDate = null; } /** * 10.5 - Marriage Proposal */ canPropose(npcId) { const romanceState = this.romanceData.get(npcId); if (!romanceState) return false; return ( romanceState.isDating && romanceState.hearts >= 10 && !romanceState.isEngaged && !romanceState.isMarried && !this.marriageStatus.isMarried ); } /** * 10.5 - Propose with Mermaid Pendant */ propose(npcId) { if (!this.canPropose(npcId)) { console.log('❌ Cannot propose yet'); return false; } // Check for Mermaid Pendant if (!this.scene.inventorySystem?.hasItem('mermaid_pendant', 1)) { this.showNotification({ title: 'Need Mermaid Pendant', text: 'Craft a Mermaid Pendant first! (50 Gold + Diamond + Pearl)', icon: 'πŸ’Ž' }); return false; } const romanceState = this.romanceData.get(npcId); // Remove pendant this.scene.inventorySystem.removeItem('mermaid_pendant', 1); // Engage! romanceState.isEngaged = true; this.datingStatus.set(npcId, 'engaged'); console.log(`πŸ’ Engaged to ${romanceState.name}!`); // Set wedding date (3 days from now) const weddingDate = new Date(); weddingDate.setDate(weddingDate.getDate() + 3); romanceState.weddingDate = weddingDate; // Trigger proposal cutscene this.showProposalCutscene(npcId); return true; } /** * 10.5 - Show proposal cutscene */ showProposalCutscene(npcId) { const romanceState = this.romanceData.get(npcId); // TODO: Implement actual cutscene this.showNotification({ title: 'She Said YES!', text: `πŸ’ ${romanceState.name} accepted your proposal! Wedding in 3 days!`, icon: 'πŸ’•' }); } /** * 10.6 - Wedding Ceremony */ startWedding(npcId) { const romanceState = this.romanceData.get(npcId); if (!romanceState || !romanceState.isEngaged) { console.log('❌ Not engaged'); return false; } console.log(`πŸ‘° Starting wedding ceremony with ${romanceState.name}!`); // Update status romanceState.isMarried = true; romanceState.isEngaged = false; romanceState.isDating = false; this.marriageStatus.isMarried = true; this.marriageStatus.spouse = npcId; this.marriageStatus.marriageDate = new Date(); this.datingStatus.set(npcId, 'married'); // Trigger wedding cutscene this.showWeddingCutscene(npcId); return true; } /** * 10.6 - Wedding cutscene */ showWeddingCutscene(npcId) { const romanceState = this.romanceData.get(npcId); // TODO: Implement full wedding cutscene with: // - Church decoration // - All NPCs attending // - Vows exchange // - Grok gong moment // - Ana reaction (crying) // - Wedding party // - Fireworks this.showNotification({ title: 'Married!', text: `πŸ‘°πŸ’’ You married ${romanceState.name}! Congratulations!`, icon: 'πŸ’•' }); } /** * 10.7 - Married Life */ getMorningKiss() { if (!this.marriageStatus.isMarried) return null; const spouse = this.romanceData.get(this.marriageStatus.spouse); if (!spouse) return null; // Grant +10 HP buff if (this.scene.player) { this.scene.player.heal(10); } return { text: `${spouse.name} gives you a morning kiss! (+10 HP)`, icon: 'πŸ’‹' }; } /** * 10.7 - Spouse daily dialogue */ getSpouseDialogue() { if (!this.marriageStatus.isMarried) return null; const spouse = this.romanceData.get(this.marriageStatus.spouse); if (!spouse) return null; // TODO: Return random dialogue from pool of 50+ lines const dialogues = [ "Good morning, love! I'll water the crops today.", "How did you sleep? I made you breakfast!", "The farm is looking great! You're amazing!", "I love you so much. Let's have a great day!" ]; return Phaser.Utils.Array.GetRandom(dialogues); } /** * Helper: Show notification */ showNotification(notification) { console.log(`πŸ“’ ${notification.icon} ${notification.title}: ${notification.text}`); const ui = this.scene.scene.get('UIScene'); if (ui && ui.showNotification) { ui.showNotification(notification); } } /** * Helper: Update romance UI */ updateRomanceUI(npcId) { // TODO: Update heart display in UI const romanceState = this.romanceData.get(npcId); if (romanceState) { console.log(`πŸ’• UI Update: ${romanceState.name} - ${romanceState.hearts}/10 hearts`); } } /** * Get romance state for NPC */ getRomanceState(npcId) { return this.romanceData.get(npcId); } /** * Get all romanceable NPCs */ getRomanceableNPCs() { return Array.from(this.romanceData.values()).filter(r => r.isRomanceable); } /** * Check if married */ isMarried() { return this.marriageStatus.isMarried; } /** * Get spouse */ getSpouse() { if (!this.marriageStatus.isMarried) return null; return this.romanceData.get(this.marriageStatus.spouse); } }