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
335 lines
9.4 KiB
JavaScript
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();
|
|
}
|
|
}
|
|
}
|