Files
novafarma/src/scenes/StoryScene.js
David Kotnik d50a5c8381 🔧 CRITICAL FIXES - Game Actually Works Now!
1. FOG FIX - NO MORE CIRCLES:
- Scale: 15 → 20 (HUGE particles)
- Alpha: 0.02 (VERY subtle)
- Result: Soft mist, not circles!

2. AUDIO CRASH FIX - ALL SCENES SAFE:
- Added cache.exists() before ALL sound.add()
- UltimatePrologueScene: 5 voice files protected
  - v1_breathing
  - v2_flyover
  - v3_awakening
  - v4_id_card
  - v5_determination
- If audio missing → Skip with warning
- Game continues without crash!

3. SAFETY FALLBACKS:
- Missing audio → 2s delay → Next phase
- Console warnings (not errors)
- Game never stops

RESULT:
 NEW GAME button works
 No audio crashes
 Fog looks like fog
 Game progresses smoothly

TESTED: NEW GAME → No crashes!
2026-01-11 00:27:39 +01:00

627 lines
21 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// StoryScene - Main Menu / Launcher
// Loaded as global class (not ES6 module)
class StoryScene extends Phaser.Scene {
constructor() {
super({ key: 'StoryScene' });
}
preload() {
// Audio disabled temporarily - encoding issues
// TODO: Re-enable when forest_ambient.mp3 is fixed
// this.load.audio('forest_ambient', 'assets/audio/music/forest_ambient.mp3');
console.log('⚠️ Audio disabled temporarily - will be enabled when files are ready');
}
create() {
const width = this.cameras.main.width;
const height = this.cameras.main.height;
// 🎨 NOIR GRADIENT BACKGROUND (dark red-black)
const graphics = this.add.graphics();
graphics.fillGradientStyle(0x1a0000, 0x1a0000, 0x000000, 0x000000, 1);
graphics.fillRect(0, 0, width, height);
// 🌫️ NOIR FOG EFFECT
this.createNoirFog(width, height);
// 🎵 NOIR BACKGROUND MUSIC (disabled temporarily)
// this.playNoirMusic();
console.log('🔇 Music disabled temporarily');
// MAIN TITLE (horizontal, top center)
const titleBg = this.add.rectangle(width / 2, 80, 480, 70, 0x4a3520, 0.9);
titleBg.setStrokeStyle(3, 0xd4a574);
const title = this.add.text(width / 2, 80, 'MRTVA DOLINA', {
fontSize: '42px',
fontFamily: 'Georgia, serif',
color: '#f4e4c1',
fontStyle: 'bold',
stroke: '#2d1b00',
strokeThickness: 4
});
title.setOrigin(0.5);
// Subtle glow
this.tweens.add({
targets: title,
alpha: 0.9,
yoyo: true,
repeat: -1,
duration: 2000,
ease: 'Sine.easeInOut'
});
// Subtitle
const subtitle = this.add.text(width / 2, 120, '~ 2084 - Survival Farm ~', {
fontSize: '14px',
fontFamily: 'Georgia, serif',
color: '#d4a574',
fontStyle: 'italic'
});
subtitle.setOrigin(0.5);
// Main Menu Buttons (center)
this.createMainMenu(width, height);
// Accessibility icon (top-right)
this.createAccessibilityIcon(width, height);
// Language selector with rotating globe (bottom-right)
this.createLanguageSelector(width, height);
// Version info
const version = this.add.text(10, height - 30, 'v0.95 ALPHA', {
fontSize: '14px',
color: '#6b4423',
fontFamily: 'Georgia, serif'
});
// 🎥 STREAMER BUILD LABEL (top-right)
const streamerLabel = this.add.text(
width - 10,
10,
'Early Access Streamer Build',
{
fontSize: '12px',
fontFamily: 'Georgia, serif',
color: '#d4a574',
backgroundColor: '#2d1b00',
padding: { x: 8, y: 4 }
}
);
streamerLabel.setOrigin(1, 0); // Top-right anchor
// Subtle pulse
this.tweens.add({
targets: streamerLabel,
alpha: 0.7,
yoyo: true,
repeat: -1,
duration: 2000,
ease: 'Sine.easeInOut'
});
}
createNoirFog(width, height) {
// Simple white circle for fog (PHASER BUILT-IN - NO CANVAS ERRORS!)
const graphics = this.make.graphics({ x: 0, y: 0, add: false });
graphics.fillStyle(0xffffff, 1);
graphics.fillCircle(32, 32, 32);
graphics.generateTexture('fogParticle', 64, 64);
graphics.destroy();
// Fog particle emitter - HUGE & SUBTLE (no circles!)
this.add.particles(0, 0, 'fogParticle', {
x: { min: 0, max: width },
y: { min: 0, max: height },
scale: { start: 15, end: 20 }, // HUGE particles!
alpha: { start: 0.02, end: 0 }, // VERY subtle!
lifespan: 8000,
speed: { min: 5, max: 20 },
frequency: 300,
blendMode: 'NORMAL'
});
// NOIR VIGNETTE (dark edges)
const vignette = this.add.rectangle(width / 2, height / 2, width, height, 0x000000, 0);
vignette.setDepth(100);
this.tweens.add({
targets: vignette,
alpha: 0.5,
duration: 3000,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
}
playNoirMusic() {
// Play forest evening ambience (noir atmosphere)
try {
if (this.sound.get('forest_ambient')) {
const music = this.sound.add('forest_ambient', {
volume: 0.3,
loop: true
});
music.play();
console.log('🎵 Noir atmosphere music playing at 30% volume');
} else {
console.warn('⚠️ forest_ambient not loaded yet - trying alternative');
// Try to load and play immediately
this.load.audio('forest_ambient', 'assets/audio/music/forest_ambient.mp3');
this.load.once('complete', () => {
try {
const music = this.sound.add('forest_ambient', { volume: 0.3, loop: true });
music.play();
console.log('🎵 Loaded and playing forest_ambient');
} catch (e) {
console.error('❌ Failed to play music:', e.message);
}
});
this.load.start();
}
} catch (error) {
console.error('❌ Audio error (non-critical):', error.message);
console.log('✅ Game continues without music');
}
}
createMainMenu(width, height) {
// Get localized button texts
const i18n = window.i18n;
const buttons = [
{
label: i18n ? i18n.t('new_game', '▶ NEW GAME') : '▶ NEW GAME',
color: '#8fbc8f',
action: () => this.startNewGame()
},
{
label: i18n ? i18n.t('load_game', '📁 LOAD GAME') : '📁 LOAD GAME',
color: '#87ceeb',
action: () => this.loadGame()
},
{
label: i18n ? i18n.t('settings', '⚙️ SETTINGS') : '⚙️ SETTINGS',
color: '#daa520',
action: () => this.showSettings()
},
{
label: i18n ? i18n.t('exit', '❌ EXIT') : '❌ EXIT',
color: '#cd5c5c',
action: () => this.exitGame()
}
];
const startY = 170;
const spacing = 58;
buttons.forEach((btn, index) => {
const y = startY + (index * spacing);
// Wooden button background (Stardew style)
const bg = this.add.rectangle(width / 2, y, 280, 48, 0x6b4423, 1);
bg.setStrokeStyle(2, 0xd4a574);
// Inner shadow effect
const innerShadow = this.add.rectangle(width / 2, y + 2, 270, 38, 0x4a3520, 0.5);
// Button text - USE NOTO SANS FOR UTF-8 SUPPORT
const text = this.add.text(width / 2, y, btn.label, {
fontSize: '20px',
fontFamily: '"Noto Sans", "Noto Sans SC", Georgia, sans-serif', // 🌍 UTF-8 SUPPORT!
color: btn.color,
fontStyle: 'bold',
stroke: '#2d1b00',
strokeThickness: 3
});
text.setOrigin(0.5);
// Make interactive
bg.setInteractive({ useHandCursor: true });
bg.on('pointerover', () => {
bg.setFillStyle(0x8b5a3c);
text.setScale(1.05);
bg.setStrokeStyle(4, 0xf4e4c1);
});
bg.on('pointerout', () => {
bg.setFillStyle(0x6b4423);
text.setScale(1.0);
bg.setStrokeStyle(3, 0xd4a574);
});
bg.on('pointerdown', () => {
// Press effect
this.tweens.add({
targets: [bg, text, innerShadow],
y: y + 3,
duration: 100,
yoyo: true,
onComplete: btn.action
});
});
});
}
createAccessibilityIcon(width, height) {
// Accessibility icon (top-right) - Stardew style
const iconBg = this.add.circle(width - 50, 40, 26, 0x6b4423);
iconBg.setStrokeStyle(2, 0xd4a574);
const icon = this.add.text(width - 50, 40, '♿', {
fontSize: '32px',
color: '#8fbc8f'
});
icon.setOrigin(0.5);
icon.setInteractive({ useHandCursor: true });
// Gentle pulse animation
this.tweens.add({
targets: [icon, iconBg],
scale: 1.08,
duration: 1500,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
icon.on('pointerover', () => {
icon.setScale(1.2);
iconBg.setScale(1.2);
iconBg.setFillStyle(0x8b5a3c);
});
icon.on('pointerout', () => {
icon.setScale(1.0);
iconBg.setScale(1.0);
iconBg.setFillStyle(0x6b4423);
});
icon.on('pointerdown', () => {
this.showAccessibility();
});
}
createLanguageSelector(width, height) {
// Initialize localization
if (!window.i18n) {
window.i18n = new LocalizationSystem();
}
// Wooden circle background for globe (Stardew style)
const globeBg = this.add.circle(width - 60, height - 60, 30, 0x6b4423);
globeBg.setStrokeStyle(2, 0xd4a574);
// Rotating globe button
const globeBtn = this.add.text(width - 60, height - 60, '🌍', {
fontSize: '42px'
});
globeBtn.setOrigin(0.5);
globeBtn.setInteractive({ useHandCursor: true });
// Continuous rotation animation
this.tweens.add({
targets: globeBtn,
angle: 360,
duration: 8000,
repeat: -1,
ease: 'Linear'
});
let langMenuOpen = false;
let langMenu = null;
globeBtn.on('pointerover', () => {
globeBtn.setScale(1.15);
globeBg.setScale(1.15);
globeBg.setFillStyle(0x8b5a3c);
});
globeBtn.on('pointerout', () => {
if (!langMenuOpen) {
globeBtn.setScale(1.0);
globeBg.setScale(1.0);
globeBg.setFillStyle(0x6b4423);
}
});
globeBtn.on('pointerdown', () => {
if (langMenuOpen) {
// Close menu
if (langMenu) langMenu.destroy();
langMenu = null;
langMenuOpen = false;
globeBtn.setScale(1.0);
} else {
// Open menu
langMenuOpen = true;
langMenu = this.createLanguageMenu(width, height, () => {
langMenuOpen = false;
globeBtn.setScale(1.0);
if (langMenu) langMenu.destroy();
langMenu = null;
});
}
});
}
createLanguageMenu(width, height, onClose) {
const container = this.add.container(0, 0);
const languages = [
{ code: 'slo', flag: '🇸🇮', name: 'Slovenščina' },
{ code: 'en', flag: '🇬🇧', name: 'English' },
{ code: 'de', flag: '🇩🇪', name: 'Deutsch' },
{ code: 'it', flag: '🇮🇹', name: 'Italiano' },
{ code: 'cn', flag: '🇨🇳', name: '中文' }
];
const menuX = width - 200;
const menuY = height - 350;
const menuW = 180;
const menuH = 290;
// Wooden panel background (Stardew style)
const panel = this.add.rectangle(menuX, menuY, menuW, menuH, 0x6b4423, 0.98);
panel.setStrokeStyle(4, 0xd4a574);
container.add(panel);
// Title
const title = this.add.text(menuX, menuY - 120, 'LANGUAGE', {
fontSize: '18px',
fontFamily: 'Georgia, serif',
color: '#f4e4c1',
fontStyle: 'bold'
});
title.setOrigin(0.5);
container.add(title);
// Language buttons
languages.forEach((lang, index) => {
const btnY = menuY - 90 + (index * 56);
const isActive = window.i18n.getCurrentLanguage() === lang.code;
const btn = this.add.text(menuX, btnY, `${lang.flag} ${lang.name}`, {
fontSize: '16px',
fontFamily: 'Georgia, serif',
color: isActive ? '#8fbc8f' : '#f4e4c1',
backgroundColor: isActive ? '#4a3520' : '#6b4423',
padding: { x: 12, y: 6 }
});
btn.setOrigin(0.5);
btn.setInteractive({ useHandCursor: true });
btn.on('pointerover', () => {
btn.setScale(1.05);
if (!isActive) btn.setBackgroundColor('#8b5a3c');
});
btn.on('pointerout', () => {
btn.setScale(1.0);
if (!isActive) btn.setBackgroundColor('#6b4423');
});
btn.on('pointerdown', () => {
window.i18n.setLanguage(lang.code);
// 🎤 VOICE FALLBACK NOTICE
if (lang.code !== 'slo' && lang.code !== 'en') {
const notice = [
'🎤 VOICE NOTICE',
'',
`Language changed to ${lang.name}`,
'',
'Audio remains in English,',
'but all text is 100% localized.',
'',
'Full voiceover available in:',
'🇸🇮 Slovenščina',
'🇬🇧 English'
].join('\n');
alert(notice);
}
onClose();
// Reload scene to apply language
this.scene.restart();
});
container.add(btn);
});
// 🎤 VOICE INFO (bottom of menu)
const voiceInfo = this.add.text(
menuX,
menuY + 120,
'🎤 Full Voice:\n🇸🇮 SL 🇬🇧 EN',
{
fontSize: '10px',
fontFamily: '"Noto Sans", Georgia, sans-serif',
color: '#d4a574',
align: 'center',
alpha: 0.7
}
);
voiceInfo.setOrigin(0.5);
container.add(voiceInfo);
return container;
}
startNewGame() {
console.log('🎮 Starting New Game...');
console.log('🎥 Launching ULTIMATE Prologue (100% Polished!)...');
this.scene.start('UltimatePrologueScene'); // ✅ ULTIMATE INTRO!
}
loadGame() {
console.log('📁 Loading Game from LocalStorage...');
try {
// Load from LocalStorage
const saveKey = 'mrtva_dolina_save';
const savedData = localStorage.getItem(saveKey);
if (!savedData) {
console.log('❌ No save file found');
alert('No save file found!\n\nStart a NEW GAME first to create a save.');
return;
}
// Parse save data
const saveFile = JSON.parse(savedData);
console.log('✅ Save file loaded:', saveFile);
// Display save info
const info = [
'📂 SAVE FILE LOADED',
'',
`Age: ${saveFile.player.current_age} years old`,
`Age Level: ${saveFile.player.age_level}/9`,
`Memories Found: ${saveFile.progress.memories_found}/${saveFile.progress.total_memories}`,
`Money: ${saveFile.economy.money} coins`,
`Cannabis Seeds: ${saveFile.economy.cannabis_seeds}`,
`Playtime: ${Math.floor(saveFile.playtime / 60)} minutes`,
'',
`Last Saved: ${new Date(saveFile.lastSaved).toLocaleString()}`,
'',
'Load this save?'
].join('\n');
if (confirm(info)) {
console.log('🎮 Starting game with loaded save...');
// Pass save data to GameScene
this.scene.start('GameScene', { loadedSave: saveFile });
}
} catch (error) {
console.error('❌ Failed to load save:', error);
alert('Error loading save file!\n\nThe save may be corrupted.\nTry starting a new game.');
}
}
showSettings() {
console.log('⚙️ Opening Settings...');
// TODO: Settings menu
alert('Settings - Use ⚙️ button in-game!');
}
showAccessibility() {
console.log('♿ Opening Accessibility Menu...');
// Initialize AccessibilityManager if not exists
if (!this.accessibility) {
this.accessibility = new AccessibilityManager(this);
}
// Create live accessibility menu
const width = this.cameras.main.width;
const height = this.cameras.main.height;
const menuBg = this.add.rectangle(width / 2, height / 2, 500, 400, 0x000000, 0.9);
menuBg.setDepth(1000);
const title = this.add.text(width / 2, height / 2 - 170, '♿ ACCESSIBILITY OPTIONS', {
fontSize: '24px',
fontFamily: '"Noto Sans", Georgia, serif',
color: '#f4e4c1',
fontStyle: 'bold'
});
title.setOrigin(0.5);
title.setDepth(1001);
const instructions = [
'Press number keys to toggle:',
'',
'1. High Contrast Mode',
'2. Large Text (2x)',
'3. Color Blind Mode',
'4. Screen Reader',
'5. Reduce Motion',
'6. One-Handed (Left)',
'7. Font Scale Reset',
'',
'Press ESC to close'
];
const instructionsText = this.add.text(width / 2, height / 2 - 50, instructions.join('\n'), {
fontSize: '16px',
fontFamily: '"Noto Sans", Georgia, serif',
color: '#d4a574',
align: 'center',
lineSpacing: 8
});
instructionsText.setOrigin(0.5);
instructionsText.setDepth(1001);
// Keyboard listener for options
const keyHandler = (event) => {
switch (event.key) {
case '1':
if (this.accessibility.settings.highContrast) {
this.accessibility.disableHighContrast();
alert('✅ High Contrast: OFF');
} else {
this.accessibility.enableHighContrast();
alert('✅ High Contrast: ON');
}
break;
case '2':
this.accessibility.setSubtitleSize('xlarge');
alert('✅ Large Text: ON (2.0x scale)');
break;
case '3':
if (this.accessibility.settings.colorBlindMode === 'none') {
this.accessibility.setColorBlindMode('protanopia');
alert('✅ Color Blind Mode: Protanopia');
} else {
this.accessibility.setColorBlindMode('none');
alert('✅ Color Blind Mode: OFF');
}
break;
case '4':
alert(' Screen Reader: Coming soon!');
break;
case '5':
if (this.accessibility.settings.reduceMotion) {
this.accessibility.disableReduceMotion();
alert('✅ Reduce Motion: OFF');
} else {
this.accessibility.enableReduceMotion();
alert('✅ Reduce Motion: ON');
}
break;
case '6':
if (this.accessibility.settings.oneHandedMode) {
this.accessibility.disableOneHandedMode();
alert('✅ One-Handed Mode: OFF');
} else {
this.accessibility.enableOneHandedMode('left');
alert('✅ One-Handed Mode: LEFT HAND');
}
break;
case '7':
this.accessibility.setFontScale(1.0);
alert('✅ Font Scale: Reset to 1.0x');
break;
case 'Escape':
menuBg.destroy();
title.destroy();
instructionsText.destroy();
document.removeEventListener('keydown', keyHandler);
break;
}
};
document.addEventListener('keydown', keyHandler);
}
exitGame() {
console.log('❌ Exiting...');
if (window.close) {
window.close();
} else {
alert('Close the window to exit.');
}
}
}