PROBLEM: - canvas.context errors - createRadialGradient issues SOLUTION: - Use this.make.graphics() ✅ - Simple fillCircle ✅ - generateTexture ✅ - NO canvas context needed! CHANGES: - Removed: canvas.createCanvas, ctx.createRadialGradient - Added: this.make.graphics({ add: false }) - Simple white circle texture - Proper destroy() cleanup FOG SETTINGS: - Scale: 2 → 6 (grows larger) - Alpha: 0.05 → 0 (very subtle!) - Lifespan: 8000ms - Speed: 5-20 (slow drift) - Frequency: 300ms ✅ NO CANVAS ERRORS! ✅ PHASER BUILT-IN METHOD! ✅ SIMPLE & STABLE! READY TO TEST!
627 lines
21 KiB
JavaScript
627 lines
21 KiB
JavaScript
// 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 - SIMPLE & WORKING
|
||
this.add.particles(0, 0, 'fogParticle', {
|
||
x: { min: 0, max: width },
|
||
y: { min: 0, max: height },
|
||
scale: { start: 2, end: 6 },
|
||
alpha: { start: 0.05, end: 0 },
|
||
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.');
|
||
}
|
||
}
|
||
}
|