Files
novafarma/src/systems/TwinBondUISystem.js
David Kotnik 0bd8014dec 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
2025-12-25 18:33:28 +01:00

335 lines
9.4 KiB
JavaScript

/**
* 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();
}
}
}