Updated Diary - SESSION 3 Complete

All work from Christmas Day documented:
- Session 1: Biomes (18/18)
- Session 2: Story integration + UI systems
- Session 3: Grok character + Susi

Total: 5 hours, 1486 lines code, 6 systems
This commit is contained in:
David Kotnik
2025-12-25 18:33:28 +01:00
parent 10772a9646
commit 0bd8014dec
10 changed files with 3525 additions and 192 deletions

View File

@@ -241,21 +241,35 @@ class AnaClueSystem {
*/
getMessageReaction(clue) {
const reactions = {
msg_01: "Ana... I'll find you. I promise.",
msg_02: "Red flowers... I'll follow your trail anywhere.",
msg_03: "Intelligence? Could we... coexist with them?",
msg_04: "The Giant Troll King. I'll face him for you, Ana.",
msg_05: "We WILL rebuild Hope Valley together. I swear it.",
msg_06: "I feel you too, Ana. Our bond will guide me.",
msg_07: "Portals... this could change everything!",
msg_08: "Domestication? This changes how I see them...",
msg_09: "A cure! There's still hope for this world!",
msg_10: "A family... Ana, we'll have that future together.",
msg_11: "Atlantis! Your curiosity never stopped, did it?",
msg_12: "Radiation won't stop me. Nothing will.",
msg_13: "Even captured, you appreciate nature's beauty...",
msg_14: "I'll NEVER give up on you, Ana. Never!",
msg_15: "I love you too, sister. I'm coming for you."
msg_01: "*Picks up note with shaking hands*\nAna... your handwriting...\n*Traces fingers over words*\nYou were scared. I can feel it in how you wrote.\nBut you still thought of me. 'Stay safe'...\n\nNo, Ana. I won't stay safe.\nI'll find you. No matter how dangerous.\nTwin promise. 💜",
msg_02: "Red flowers... Ana's favorite!\n*Looks around, spots red flowers growing*\nShe left me a trail. Even captured, she thought ahead.\n\nThat's my sister. Always thinking.\nI'm following, Ana. Right behind you.",
msg_03: "Intelligence? She's right.\nMy zombie workers... they understand orders, learn tasks.\n\nAna saw it too. Even while captured.\nHer curiosity never stopped.\n\nMaybe... maybe zombies and humans can coexist.\nIf we survive this.",
msg_04: "*Clenches fists*\nThe Troll King. The one who took you.\n\nNow I know his name.\nNow I know my enemy.\n\nDr. Krnić may have created you, beast...\nBut I will DESTROY you.",
msg_05: "*Tears up*\n'Together'... Ana still believes we'll be together again.\n\nYes. We'll rebuild Hope Valley.\nYou, me, and whoever else survives.\nWe'll make it beautiful again.\n\nA home. Our home.",
msg_06: "*Gasps*\nYou can feel me?!\nANA! I'm here! I'm searching!\n*Closes eyes, concentrates*\n\nTwin bond... please work...\n*Faint purple glow*\n\nAna... I felt something. Just for a moment.\nYou're alive. I KNOW you're alive!",
msg_07: "Portals! Of course!\nAna, you genius!\nIf I repair these, I can search the world faster!\n\nI'll fix them all. Every single one.\nFor you.",
msg_08: "Domestication... that's what I'm doing.\nMy zombie workers are 'domesticated'.\n\nBut Ana found this disturbing?\nIs what I'm doing... wrong?\n\nNo. I'm helping them. Giving them purpose.\nThat's different from Dr. Krnić's experiments.",
msg_09: "A CURE?!\nAna... if there's a cure...\nWe could save EVERYONE!\n\nThe infected could become human again!\nMama, Dad... they could--\n\nNo. Too late for them.\nBut not too late for others.\n\nI'll find it, Ana. For you.",
msg_10: "*Sits down, holds note to chest*\nA family... children...\n\nAna, you're thinking about the future.\nEven in captivity, you dream of life.\n\nWe'll have that future. I swear it.\nYou'll see children play in safe fields.\nOur children. Our future.",
msg_11: "*Laughs through tears*\nOnly you, Ana. Only you would get excited about Atlantis.\nWhile captured. While in danger.\n\nYour curiosity, your wonder... it never dies.\nThat's what I love about you.\n\nI'll explore these ruins for you. Find every secret.",
msg_12: "Chernobyl... you went to the reactor?!\nAna, that's suicide!\n\nBut you left me a warning. Even there.\nYou knew I'd follow.\n\nRadiation won't stop me. Nothing will.\nI'm coming, Ana. Hold on.",
msg_13: "Biodiversity... scientist words even now.\n\nAna, you never stop learning.\nNever stop appreciating nature.\n\nWhen this is over, I'll take you on a real expedition.\nNo danger. Just discovery.\nTwin explorers. Together.",
msg_14: "*Crumples note, then smooths it out*\nSave MYSELF?!\nYou think I'd give up on you?!\n\nAna, I'd walk through hell for you.\nAlone if I had to.\n\nThis 'danger' you warn about?\nBRING IT ON.",
msg_15: "*Breaks down crying*\n'I love you, brother'...\n\nI love you too, Ana. So much.\n\nYou're right. I won't listen.\nI never do when it comes to you.\n\nI'm at the castle. I'm coming in.\nWait for me. Please."
};
return reactions[clue.id] || "Ana... another piece of you found.";
@@ -266,18 +280,29 @@ class AnaClueSystem {
*/
getPhotoReaction(clue) {
const reactions = {
photo_01: "*tears up* We were so innocent back then...",
photo_02: "So that's the monster who took you...",
photo_03: "Hope Valley was paradise. It will be again.",
photo_04: "Even then, you saw them differently than others.",
photo_05: "Your map will help me repair them all!",
photo_06: "You documented everything. So thorough, Ana.",
photo_07: "Your handwriting... I'd recognize it anywhere.",
photo_08: "Atlantean tech! This could save us all!",
photo_09: "You went to Chernobyl?! Ana...",
photo_10: "Ancient mysteries. You loved this stuff.",
photo_11: "*clenches fist* I see you, captor. I'm coming.",
photo_12: "*breaks down* I'm coming, Ana. Hold on!"
photo_01: "*Stares at photo for long time*\nMom... Dad... Ana... me...\nWe were so happy.\n\nI'll make us happy again, Ana.\nDifferent, but happy.\nNew family. New memories.\n\nBut I'll never forget this.",
photo_02: "*Photo of Giant Troll, blurry*\nThere you are, monster.\nThe thing that destroyed everything.\n\nAna got a photo even while running.\nBrave. So brave.\n\nI'm coming for you, Troll.",
photo_03: "It WAS beautiful...\nGreen fields, happy people, clean buildings.\n\nWe can make it like this again.\nWith your help, Ana.\nTogether we'll rebuild paradise.",
photo_04: "A Level 1 zombie... looking confused.\nAna saw them as... beings. Not monsters.\n\nShe was always more empathetic than me.\nMaybe that's why she left this trail.\nShe knew I'd need to see them differently.",
photo_05: "Ana drew a MAP for me!\nAll 18 portals marked!\n\nShe knew I'd need to travel fast.\nThought of everything.\n\nThank you, sister. This helps SO much.",
photo_06: "Dr. Krnić's lab... still functional.\nHigh-tech equipment Ana photographed.\n\nIf I find this place... I could finish her research.\nFind the cure.\nSave everyone.",
photo_07: "*Close-up of handwriting*\nHer journal... during captivity.\n\nShe kept writing. Kept documenting.\nScientist to the end.\n\nI need to find this journal. All of it.",
photo_08: "Atlantean technology... glowing crystals!\nThis could power everything!\n\nAna, you found the future.\nAncient tech to save the modern world.\n\nI'll bring it back. For everyone.",
photo_09: "*Chernobyl reactor, green glow*\nThat's where Dr. Krnić is.\nFinal facility.\n\nAna went there. Survived radiation.\nIf she can... so can I.",
photo_10: "Ancient temple in Amazon...\nAna always loved archaeology.\n\nI'll explore it. For you.\nFind whatever secrets you wanted to discover.",
photo_11: "*Shadowy figure in photo*\nThis is him. Dr. Krnić.\nThe monster who started everything.\n\nYour face is blurry, coward.\nBut I'll see you clearly...\nRight before I END you.",
photo_12: "*Ana holding 'FIND ME' sign, tears in eyes*\n\n*Kai collapses to knees, sobbing*\n\nI SEE YOU, ANA! I SEE YOU!\nI'M COMING! I SWEAR I'M COMING!\n\nHold on! Please! Just hold on!"
};
return reactions[clue.id] || "Another memory of you...";
@@ -288,11 +313,29 @@ class AnaClueSystem {
*/
getItemReaction(clue) {
const reactions = {
item_01: "*holds bracelet* I have the matching one...",
item_09: "*clutches necklace* Mother's necklace! You kept it safe.",
item_13: "*pairs gloves* Both found. You left a trail for me.",
item_17: "*opens locket* My photo... you carried me with you.",
item_23: "*trembling hands* I'll open this when I find you, Ana."
item_01: "*Silver twin bracelet*\nI have the matching one...\n*Holds both bracelets together*\nTwins. Always twins.\nSoon we'll wear these together again.",
item_02: "*Torn dress piece*\nFragment of Ana's favorite dress...\n*Holds it gently*\nYou loved this dress. Wore it on special days.\nI'll keep this safe for you.",
item_03: "*Muddy hairpin*\nAna's decorative hairpin...\n*Cleans mud off carefully*\nYou always wore this. Even while farming.\nIt's coming home with me.",
item_04: "*Father's pocket watch*\nDad's watch... Ana always carried it.\n*Opens it - shows family photo inside*\nYou kept us close. All of us.\nI understand, Ana.",
item_05: "*Engraved compass*\n'To Ana, find your way home'...\n*Compass points north*\nI'll use this to find YOU, Ana.\nThen we both go home. Together.",
item_06: "*Small stuffed rabbit*\nYour childhood toy!\n*Hugs it*\nWe got this when we were 5.\nYou never let it go.\nI won't either.",
item_07: "*Delicate music box*\n*Opens it - plays Ana's favorite song*\n*Sits and listens, tears flowing*\nThis song... you hummed it every night.\nI hear you, Ana. I hear you.",
item_08: "*Ana's art sketchbook*\n*Flips through pages*\nYour drawings... so beautiful.\nEven in danger, you created beauty.\nThat's who you are.",
item_09: "*Falls to knees*\nMama's necklace... Ana kept it through everything.\nProtected it.\n\n*Kisses necklace*\nMama... I'm protecting Ana now.\nLike you asked.",
item_10: "*Torn diary page*\n*Reads*\n'I'm scared. But I won't give up hope.'\n*Clutches page*\nI won't give up either, Ana. Never.",
item_17: "*Opens locket - sees his own photo*\n\n*Tears streaming*\nYou carried ME with you...\nWhile I searched for you...\nYou had me the whole time.\n\nTwin bond. Forever.",
item_23: "*Sealed envelope: 'Open when you find me'*\n\n*Hands trembling*\nNot yet. I'm not opening this yet.\nI'll open it when I SEE you.\nFace to face.\n\nSoon, Ana. Soon."
};
return reactions[clue.id] || `${clue.title}... you were here.`;

View File

@@ -1,7 +1,24 @@
/**
* GrokCharacterSystem.js
* ======================
* KRVAVA ŽETEV - Grok Character Update (P13)
* KRVAVA ŽETEV - Grok Character (The Developer / Pink Alpha)
*
* CHARACTER DESIGN:
* - Skin: Light green (unique color - not human!)
* - Hair: PINK dreadlocks (iconic!)
* - Outfit: Oversized hoodie (2 sizes too big) + baggy pants
* - Shoes: Hot pink Converse
* - Piercings: Septum, eyebrows, lips, 15+ earrings, 25mm tunnels
*
* PERSONALITY:
* - ADHD genius developer
* - Always vaping (Rainbow RGB mod)
* - Zen master with massive gong
* - Quick movements when hyperfocused
* - Oversized comfort style
*
* COMPANION:
* - Susi: Hot dog hunting dog (always by his side)
*
* Features:
* - Massive gong (1m diameter!)
@@ -9,9 +26,11 @@
* - Morning meditation rituals
* - Combat buffs
* - Smoke screen abilities
* - ADHD focus modes
* - Susi interactions
*
* @author NovaFarma Team
* @date 2025-12-23
* @date 2025-12-25
*/
export default class GrokCharacterSystem {
@@ -25,18 +44,41 @@ export default class GrokCharacterSystem {
this.gongCooldown = 300000; // 5 minutes
this.meditationTime = 6; // 6 AM daily
// ADHD mechanics
this.isFocused = false; // "Oversized Focus" mode
this.focusTimer = 0;
this.hyperfocusSpeed = 2.0; // Speed multiplier when focused
this.currentTopic = 'coding'; // What Grok is focused on
// Visual elements
this.gong = null;
this.vapeDevice = null;
this.smokeParticles = [];
this.hoodie = null; // Oversized hoodie sprite
this.dreadlocks = null; // Pink dreadlocks
this.piercings = []; // Visual piercing elements
// Susi companion
this.susi = null; // Susi the hot dog hunter
this.susiState = 'following'; // following, hunting, eating
// Buffs
this.activeBuffs = new Map();
// Character colors
this.skinColor = 0x90EE90; // Light green
this.dreadlockColor = 0xFF69B4; // Hot pink
this.hoodieColor = 0x2F4F4F; // Dark slate gray (wide hoodie)
console.log('🧘 GrokCharacterSystem initialized');
console.log('👕 Oversized hoodie: ON');
console.log('💚 Skin: Light green');
console.log('💕 Dreadlocks: HOT PINK');
console.log('🐕 Susi companion: Ready!');
// Setup visuals
this.setupGrokVisuals();
this.createSusi();
this.startVapingAnimation();
}
@@ -559,6 +601,264 @@ export default class GrokCharacterSystem {
}
}
/**
* Create Susi companion (Hot Dog Hunter)
*/
createSusi() {
if (!this.grok) return;
// Create Susi (dachshund-style hot dog hunter!)
this.susi = this.scene.add.ellipse(
this.grok.x + 30,
this.grok.y + 20,
40, // Width (long dog!)
20, // Height
0x8B4513 // Brown color
);
this.susi.setDepth(this.grok.depth);
// Add spots
const spot1 = this.scene.add.circle(
this.susi.x - 10,
this.susi.y,
4,
0x654321 // Darker spot
);
spot1.setDepth(this.grok.depth + 1);
// Susi's nose (always sniffing for hot dogs!)
const nose = this.scene.add.circle(
this.susi.x + 20,
this.susi.y,
3,
0x000000 // Black nose
);
nose.setDepth(this.grok.depth + 2);
// Tail (wagging animation!)
const tail = this.scene.add.line(
this.susi.x - 20,
this.susi.y - 5,
0, 0,
-10, -5,
0x8B4513,
1
);
tail.setLineWidth(3);
tail.setDepth(this.grok.depth);
// Tail wag animation
this.scene.tweens.add({
targets: tail,
angle: { from: -15, to: 15 },
duration: 300,
yoyo: true,
repeat: -1
});
console.log('🐕 Susi created! Hot dog hunter ready!');
// Susi behavior
this.startSusiBehavior();
}
/**
* Susi's AI behavior
*/
startSusiBehavior() {
// Susi follows Grok
setInterval(() => {
if (!this.susi || !this.grok) return;
// Follow Grok at distance
const targetX = this.grok.x + 30;
const targetY = this.grok.y + 20;
// Smooth follow
this.susi.x += (targetX - this.susi.x) * 0.1;
this.susi.y += (targetY - this.susi.y) * 0.1;
// Random hot dog hunting
if (Math.random() < 0.01) { // 1% chance per frame
this.susiHuntHotDog();
}
}, 100);
}
/**
* Susi hunts for hot dogs!
*/
susiHuntHotDog() {
if (!this.susi) return;
console.log('🌭 Susi spotted a potential hot dog!');
this.susiState = 'hunting';
// Susi runs to random location
const randomX = this.susi.x + (Math.random() - 0.5) * 100;
const randomY = this.susi.y + (Math.random() - 0.5) * 100;
this.scene.tweens.add({
targets: this.susi,
x: randomX,
y: randomY,
duration: 1000,
ease: 'Quad.InOut',
onComplete: () => {
// Found hot dog!
this.susiState = 'eating';
console.log('🌭 Susi found a hot dog! *nom nom*');
// Eating animation (wiggle)
this.scene.tweens.add({
targets: this.susi,
angle: { from: -5, to: 5 },
duration: 200,
yoyo: true,
repeat: 5,
onComplete: () => {
this.susiState = 'following';
this.susi.setAngle(0);
}
});
}
});
}
/**
* ADHD Focus Mode - Grok hides in hoodie to focus
*/
enterFocusMode(topic = 'coding') {
if (this.isFocused) {
console.log('⚠️ Already in focus mode!');
return false;
}
console.log(`🧠 Grok enters ADHD FOCUS MODE! Topic: ${topic}`);
console.log('👕 *Hides in oversized hoodie*');
this.isFocused = true;
this.currentTopic = topic;
this.focusTimer = 0;
// Visual: Grok shrinks into hoodie
if (this.grok) {
this.scene.tweens.add({
targets: this.grok,
scale: 0.7, // Gets smaller (hiding in hoodie)
alpha: 0.8,
duration: 500
});
}
// Can't be interrupted unless you have vape juice!
this.showNotification({
title: 'Focus Mode Active!',
text: `🧠 Grok is focusing on ${topic}. Don't interrupt! (Unless you have vape juice)`,
icon: '👕'
});
return true;
}
/**
* Exit ADHD focus mode
*/
exitFocusMode() {
if (!this.isFocused) return;
console.log('🧠 Focus mode complete!');
this.isFocused = false;
// Visual: Grok emerges from hoodie
if (this.grok) {
this.scene.tweens.add({
targets: this.grok,
scale: 1.0,
alpha: 1.0,
duration: 500
});
}
this.showNotification({
title: 'Focus Complete!',
text: `${this.currentTopic} finished! Grok emerges victorious!`,
icon: '🎉'
});
}
/**
* ADHD Hyperfocus Movement
*/
moveWithHyperfocus(direction) {
if (!this.isFocused) {
console.log('⚠️ Not in focus mode!');
return;
}
// When hyperfocused, Grok moves SUPER fast!
const baseSpeed = 5;
const speed = baseSpeed * this.hyperfocusSpeed; // 2x speed!
console.log(`⚡ HYPERFOCUS SPEED! Moving ${direction} at ${speed} px/frame!`);
// Move Grok super fast
// (Integration with movement system would go here)
}
/**
* Get Grok's quest dialogues
*/
getGrokQuests() {
return [
{
id: 'hoodie_rescue',
title: 'Hoodie v nevarnosti',
dialogue: "Dude, moj najljubši hoodie se je zataknil za eno tistih piranha rastlin v coni 4. Brez njega se ne morem fokusirati, preveč me zebe v roke! Greš ponj?",
objective: 'Premagaj gigantsko mesojedko in reši Gronkov široki pulover',
rewards: {
gold: 500,
xp: 1000,
item: 'grok_friendship +10'
}
},
{
id: 'vape_mixology',
title: 'Zamenjava tekočine (Vape Mixology)',
dialogue: "Bro, poskušam zmešati nov okus 'Baggy Cloud', ampak Susi mi je prevrnila epruveto, ker je mislila, da so notri hrenovke. Rabim tri mutirane jagode iz Dino Valleyja!",
objective: 'Najdi 3 mutirane jagode v Dino Valley biome',
rewards: {
gold: 300,
xp: 750,
item: 'baggy_cloud_vape_juice'
}
},
{
id: 'adhd_code',
title: 'ADHD koda na hlačah',
dialogue: "Ej, si vedel, da sem si na nogo (na hlače) napisal pomembno kodo za tvoj novi rudnik, pa sem jo zdaj ponesreči umazal z blatom? Susi, pomagaj mi polizati to blato... ah, ne, Kai, ti boš moral najti čistilo!",
objective: 'Najdi čistilo v opuščenem laboratoriju',
rewards: {
gold: 400,
xp: 850,
unlock: 'advanced_mine_code'
}
}
];
}
/**
* 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);
}
}
/**
* Get active buff for target
*/

View File

@@ -0,0 +1,334 @@
/**
* TwinBondUISystem.js
* ====================
* KRVAVA ŽETEV - Twin Bond Heartbeat UI
*
* Features:
* - Visual heartbeat indicator on screen
* - Speeds up when near Ana's clues
* - Purple glow effect
* - Distance-based intensity
* - Twin bond strength indicator
*
* @author NovaFarma Team
* @date 2025-12-25
*/
export default class TwinBondUISystem {
constructor(scene) {
this.scene = scene;
// UI elements
this.heartContainer = null;
this.heartIcon = null;
this.glowEffect = null;
this.bondStrengthBar = null;
// State
this.baseHeartRate = 60; // bpm
this.currentHeartRate = 60;
this.bondStrength = 0; // 0-100
this.nearestClueDistance = Infinity;
// Animation
this.heartbeatTween = null;
this.lastBeat = 0;
console.log('💜 TwinBondUISystem initialized');
this.createUI();
this.startHeartbeat();
}
/**
* Create UI elements
*/
createUI() {
const ui = this.scene.scene.get('UIScene') || this.scene;
// Container (top-left corner)
this.heartContainer = ui.add.container(50, 50);
this.heartContainer.setDepth(1000); // Above everything
this.heartContainer.setScrollFactor(0); // Fixed to camera
// Purple glow effect (behind heart)
this.glowEffect = ui.add.circle(0, 0, 25, 0x9370DB, 0);
this.heartContainer.add(this.glowEffect);
// Heart icon (emoji-style)
this.heartIcon = ui.add.text(0, 0, '💜', {
fontSize: '32px',
fontFamily: 'Arial'
});
this.heartIcon.setOrigin(0.5);
this.heartContainer.add(this.heartIcon);
// Bond strength bar (below heart)
const barWidth = 50;
const barHeight = 5;
// Background bar
const barBg = ui.add.rectangle(0, 30, barWidth, barHeight, 0x333333);
this.heartContainer.add(barBg);
// Strength bar
this.bondStrengthBar = ui.add.rectangle(
-barWidth / 2,
30,
0, // Start at 0 width
barHeight,
0x9370DB
);
this.bondStrengthBar.setOrigin(0, 0.5);
this.heartContainer.add(this.bondStrengthBar);
// Bond strength text
this.bondStrengthText = ui.add.text(0, 45, '0%', {
fontSize: '10px',
fontFamily: 'Arial',
color: '#9370DB'
});
this.bondStrengthText.setOrigin(0.5);
this.heartContainer.add(this.bondStrengthText);
console.log('✅ Twin Bond UI created');
}
/**
* Start heartbeat animation
*/
startHeartbeat() {
const beatInterval = () => {
const bpm = this.currentHeartRate;
const msPerBeat = 60000 / bpm; // Convert BPM to ms
// Heart pump animation
if (this.heartIcon) {
this.scene.tweens.add({
targets: [this.heartIcon],
scale: { from: 1, to: 1.3 },
duration: 100,
yoyo: true,
ease: 'Quad.Out'
});
// Glow pulse
this.scene.tweens.add({
targets: [this.glowEffect],
alpha: { from: 0, to: 0.6 },
scale: { from: 1, to: 1.5 },
duration: 150,
yoyo: true,
ease: 'Quad.Out'
});
}
// Schedule next beat
this.heartbeatTimer = setTimeout(() => beatInterval(), msPerBeat);
};
// Start beating
beatInterval();
}
/**
* Update system (called every frame)
*/
update(time, delta) {
if (!this.scene.player) return;
// Calculate distance to nearest Ana clue
this.calculateNearestClue();
// Update heart rate based on distance
this.updateHeartRate();
// Update bond strength display
this.updateBondStrengthDisplay();
}
/**
* Calculate distance to nearest Ana's clue
*/
calculateNearestClue() {
if (!this.scene.anaClueSystem) {
this.nearestClueDistance = Infinity;
return;
}
const playerX = this.scene.player.sprite.x;
const playerY = this.scene.player.sprite.y;
let minDistance = Infinity;
// Check all clues
this.scene.anaClueSystem.clueLocations.forEach((position, clueId) => {
// Skip already discovered clues
if (this.scene.anaClueSystem.discovered.has(clueId)) return;
const dx = position.x - playerX;
const dy = position.y - playerY;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
minDistance = distance;
}
});
this.nearestClueDistance = minDistance;
}
/**
* Update heart rate based on proximity to clues
*/
updateHeartRate() {
const distance = this.nearestClueDistance;
// Distance thresholds (in pixels)
const veryClose = 200; // 4-5 blocks
const close = 480; // 10 blocks
const medium = 960; // 20 blocks
let targetHeartRate = this.baseHeartRate;
if (distance < veryClose) {
// VERY CLOSE - Rapid heartbeat!
targetHeartRate = 120; // 2x normal
this.bondStrength = 100;
} else if (distance < close) {
// CLOSE - Fast heartbeat
targetHeartRate = 90;
this.bondStrength = 75;
} else if (distance < medium) {
// MEDIUM - Slightly elevated
targetHeartRate = 75;
this.bondStrength = 50;
} else {
// FAR - Normal heartbeat
targetHeartRate = 60;
this.bondStrength = Math.max(0, this.bondStrength - 1); // Slowly decrease
}
// Smooth transition
this.currentHeartRate += (targetHeartRate - this.currentHeartRate) * 0.1;
// Clamp
this.currentHeartRate = Phaser.Math.Clamp(this.currentHeartRate, 30, 150);
}
/**
* Update bond strength visual display
*/
updateBondStrengthDisplay() {
if (!this.bondStrengthBar) return;
// Update bar width
const maxWidth = 50;
const targetWidth = (this.bondStrength / 100) * maxWidth;
this.bondStrengthBar.width = targetWidth;
// Update text
if (this.bondStrengthText) {
this.bondStrengthText.setText(`${Math.floor(this.bondStrength)}%`);
}
// Color gradient based on strength
if (this.bondStrength > 75) {
this.bondStrengthBar.setFillStyle(0xFF69B4); // Hot pink - very strong
} else if (this.bondStrength > 50) {
this.bondStrengthBar.setFillStyle(0x9370DB); // Medium purple
} else if (this.bondStrength > 25) {
this.bondStrengthBar.setFillStyle(0x6A5ACD); // Slate blue
} else {
this.bondStrengthBar.setFillStyle(0x483D8B); // Dark slate blue
}
// Pulsing glow when strong bond
if (this.bondStrength > 75 && this.glowEffect) {
this.glowEffect.setAlpha(0.3 + Math.sin(Date.now() / 200) * 0.2);
}
}
/**
* Trigger special bond moment (e.g., flashback)
*/
triggerBondMoment(intensity = 'medium') {
console.log(`💜 Twin Bond Moment! Intensity: ${intensity}`);
// Screen flash
this.scene.cameras.main.flash(500, 147, 112, 219, false); // Purple flash
// Heart explosion effect
if (this.heartIcon) {
this.scene.tweens.add({
targets: [this.heartIcon],
scale: { from: 1, to: 2 },
alpha: { from: 1, to: 0 },
duration: 800,
ease: 'Quad.Out',
onComplete: () => {
this.heartIcon.setScale(1);
this.heartIcon.setAlpha(1);
}
});
}
// Glow burst
if (this.glowEffect) {
this.scene.tweens.add({
targets: [this.glowEffect],
scale: { from: 1, to: 5 },
alpha: { from: 0.8, to: 0 },
duration: 1000,
ease: 'Quad.Out',
onComplete: () => {
this.glowEffect.setScale(1);
this.glowEffect.setAlpha(0);
}
});
}
// Temporary heart rate spike
this.currentHeartRate = 150;
setTimeout(() => {
this.currentHeartRate = this.baseHeartRate;
}, 3000);
// Bond strength boost
this.bondStrength = 100;
// Show notification
this.scene.events.emit('show-notification', {
title: 'Twin Bond Activated!',
text: '💜 You feel Ana\'s presence intensify!',
icon: '💜',
color: '#9370DB'
});
}
/**
* Get current heart rate (for debugging)
*/
getCurrentHeartRate() {
return Math.floor(this.currentHeartRate);
}
/**
* Get bond strength (for other systems)
*/
getBondStrength() {
return this.bondStrength;
}
/**
* Cleanup
*/
destroy() {
if (this.heartbeatTimer) {
clearTimeout(this.heartbeatTimer);
}
if (this.heartContainer) {
this.heartContainer.destroy();
}
}
}

View File

@@ -0,0 +1,353 @@
/**
* VoiceoverSystem.js
* ==================
* KRVAVA ŽETEV - Ana's Voice & Flashback Audio
*
* Features:
* - Ana's voice recordings for clues
* - Flashback narration
* - Emotional voiceovers
* - Audio positioning (3D sound)
* - Volume based on bond strength
*
* @author NovaFarma Team
* @date 2025-12-25
*/
export default class VoiceoverSystem {
constructor(scene) {
this.scene = scene;
// Audio state
this.currentVoiceover = null;
this.voiceoverQueue = [];
this.isPlaying = false;
// Audio library (paths to audio files)
this.anaVoiceovers = {};
this.flashbackVoiceovers = {};
console.log('🎙️ VoiceoverSystem initialized');
this.registerVoiceovers();
}
/**
* Register all voiceover audio files
*/
registerVoiceovers() {
// ANA'S CLUE VOICEOVERS (15 messages)
this.anaVoiceovers = {
msg_01: {
file: 'assets/audio/voiceover/ana_msg_01.mp3',
text: "Kai, if you find this... I'm sorry. They took me. Stay safe.",
duration: 5000,
emotion: 'scared'
},
msg_02: {
file: 'assets/audio/voiceover/ana_msg_02.mp3',
text: "I'm marking my trail with red flowers. Follow them west.",
duration: 4000,
emotion: 'determined'
},
msg_03: {
file: 'assets/audio/voiceover/ana_msg_03.mp3',
text: "These zombies... they're intelligent. I saw it in their eyes.",
duration: 5000,
emotion: 'curious'
},
msg_06: {
file: 'assets/audio/voiceover/ana_msg_06.mp3',
text: "Kai... I can feel you searching for me. Our bond is so strong.",
duration: 6000,
emotion: 'hopeful'
},
msg_09: {
file: 'assets/audio/voiceover/ana_msg_09.mp3',
text: "I found research on a cure! There's still hope for everyone!",
duration: 5000,
emotion: 'excited'
},
msg_10: {
file: 'assets/audio/voiceover/ana_msg_10.mp3',
text: "I dream of having a family someday. Children playing in safe fields...",
duration: 6000,
emotion: 'wistful'
},
msg_14: {
file: 'assets/audio/voiceover/ana_msg_14.mp3',
text: "Don't come for me, Kai. It's too dangerous. Please, save yourself!",
duration: 5000,
emotion: 'desperate'
},
msg_15: {
file: 'assets/audio/voiceover/ana_msg_15.mp3',
text: "I know you won't listen... you never do. I love you, brother.",
duration: 6000,
emotion: 'loving'
},
// SPECIAL: Ana's Journal Entry
ana_journal_01: {
file: 'assets/audio/voiceover/ana_journal_01.mp3',
text: "Day 147 in captivity. Dr. Krnić is forcing me to work on something terrible. A super virus using Chernobyl radiation. If it releases... everyone dies. I have to find a way to stop him.",
duration: 15000,
emotion: 'terrified'
},
// SPECIAL: Final Video Message
ana_final_message: {
file: 'assets/audio/voiceover/ana_final_message.mp3',
text: "Kai... I'm in Dr. Krnić's facility. Level 3, Vault 7. If you're watching this... don't come. It's a trap. But I know you will anyway. *laughs sadly* Be ready for the Troll King. I love you. Twin bond forever.",
duration: 20000,
emotion: 'resigned'
}
};
// FLASHBACK NARRATION
this.flashbackVoiceovers = {
twin_bond_discovery: {
file: 'assets/audio/voiceover/flashback_twin_bond.mp3',
text: "Dr. Chen: 'These twins have something special. A connection I've never seen before. They'll always find each other, no matter what.'",
duration: 10000,
emotion: 'wonder'
},
mother_death: {
file: 'assets/audio/voiceover/flashback_mother.mp3',
text: "Mama Elena: 'Kai... protect Ana. Always. Twin bond is your strength. Together... you can survive anything. I love you both...'",
duration: 15000,
emotion: 'heartbreaking'
},
kidnapping: {
file: 'assets/audio/voiceover/flashback_kidnapping.mp3',
text: "Ana's scream: 'KAIII! HELP! Please! KAIIIII!' *Kai's voice, desperate* 'ANA! NO! ANA!'",
duration: 8000,
emotion: 'traumatic'
}
};
console.log(`✅ Registered ${Object.keys(this.anaVoiceovers).length} Ana voiceovers`);
console.log(`✅ Registered ${Object.keys(this.flashbackVoiceovers).length} flashback voiceovers`);
}
/**
* Play Ana's voiceover for a clue
*/
playAnaVoiceover(clueId, onComplete = null) {
const voiceover = this.anaVoiceovers[clueId];
if (!voiceover) {
console.warn(`⚠️ No voiceover for clue: ${clueId}`);
return false;
}
console.log(`🎙️ Playing Ana's voice: ${clueId}`);
// Queue if already playing
if (this.isPlaying) {
this.voiceoverQueue.push({ type: 'ana', id: clueId, onComplete });
console.log(`⏳ Voiceover queued (${this.voiceoverQueue.length} in queue)`);
return true;
}
this.isPlaying = true;
// Show subtitle text
this.showSubtitle(voiceover.text, voiceover.duration);
// Play audio (if file exists)
if (this.scene.sound.get(voiceover.file)) {
this.currentVoiceover = this.scene.sound.play(voiceover.file, {
volume: this.getVolumeBasedOnBondStrength()
});
// Cleanup when done
this.currentVoiceover.once('complete', () => {
this.onVoiceoverComplete(onComplete);
});
} else {
// Fallback if audio file doesn't exist
console.warn(`⚠️ Audio file not found: ${voiceover.file}`);
// Simulate duration
setTimeout(() => {
this.onVoiceoverComplete(onComplete);
}, voiceover.duration);
}
// Trigger Twin Bond moment
if (this.scene.twinBondUI) {
this.scene.twinBondUI.triggerBondMoment('medium');
}
return true;
}
/**
* Play flashback narration
*/
playFlashbackVoiceover(flashbackId, onComplete = null) {
const voiceover = this.flashbackVoiceovers[flashbackId];
if (!voiceover) {
console.warn(`⚠️ No voiceover for flashback: ${flashbackId}`);
return false;
}
console.log(`🎙️ Playing flashback: ${flashbackId}`);
this.isPlaying = true;
// Show subtitle
this.showSubtitle(voiceover.text, voiceover.duration, '#FFD700'); // Gold color for flashbacks
// Play audio
if (this.scene.sound.get(voiceover.file)) {
this.currentVoiceover = this.scene.sound.play(voiceover.file, {
volume: 0.8
});
this.currentVoiceover.once('complete', () => {
this.onVoiceoverComplete(onComplete);
});
} else {
console.warn(`⚠️ Audio file not found: ${voiceover.file}`);
setTimeout(() => {
this.onVoiceoverComplete(onComplete);
}, voiceover.duration);
}
return true;
}
/**
* Show subtitle text on screen
*/
showSubtitle(text, duration, color = '#9370DB') {
const ui = this.scene.scene.get('UIScene') || this.scene;
// Create subtitle container
const subtitleBg = ui.add.rectangle(
ui.cameras.main.width / 2,
ui.cameras.main.height - 100,
ui.cameras.main.width - 200,
80,
0x000000,
0.7
);
subtitleBg.setScrollFactor(0);
subtitleBg.setDepth(2000);
const subtitleText = ui.add.text(
ui.cameras.main.width / 2,
ui.cameras.main.height - 100,
text,
{
fontSize: '20px',
fontFamily: 'Arial',
color: color,
align: 'center',
wordWrap: { width: ui.cameras.main.width - 220 }
}
);
subtitleText.setOrigin(0.5);
subtitleText.setScrollFactor(0);
subtitleText.setDepth(2001);
// Fade in
subtitleBg.setAlpha(0);
subtitleText.setAlpha(0);
ui.tweens.add({
targets: [subtitleBg, subtitleText],
alpha: 1,
duration: 300
});
// Fade out and destroy
setTimeout(() => {
ui.tweens.add({
targets: [subtitleBg, subtitleText],
alpha: 0,
duration: 500,
onComplete: () => {
subtitleBg.destroy();
subtitleText.destroy();
}
});
}, duration - 500);
}
/**
* Get volume based on twin bond strength
*/
getVolumeBasedOnBondStrength() {
if (!this.scene.twinBondUI) {
return 0.7; // Default volume
}
const bondStrength = this.scene.twinBondUI.getBondStrength();
// Higher bond = louder voice
const volume = 0.4 + (bondStrength / 100) * 0.6; // 0.4 to 1.0
return volume;
}
/**
* Voiceover completion handler
*/
onVoiceoverComplete(callback) {
this.isPlaying = false;
this.currentVoiceover = null;
// Call completion callback
if (callback) {
callback();
}
// Play next in queue
if (this.voiceoverQueue.length > 0) {
const next = this.voiceoverQueue.shift();
setTimeout(() => {
if (next.type === 'ana') {
this.playAnaVoiceover(next.id, next.onComplete);
} else if (next.type === 'flashback') {
this.playFlashbackVoiceover(next.id, next.onComplete);
}
}, 500); // Small delay between voiceovers
}
}
/**
* Stop current voiceover
*/
stopVoiceover() {
if (this.currentVoiceover) {
this.currentVoiceover.stop();
this.currentVoiceover = null;
}
this.isPlaying = false;
}
/**
* Clear queue
*/
clearQueue() {
this.voiceoverQueue = [];
}
/**
* Check if voiceover is playing
*/
isVoiceoverPlaying() {
return this.isPlaying;
}
/**
* Cleanup
*/
destroy() {
this.stopVoiceover();
this.clearQueue();
}
}