Files
novafarma/src/scenes/StoryScene.js
David Kotnik 7264ec6fc0 feat: complete Style 32 overhaul & Tiled integration fix
- Enforced 'Style 32 - Dark Chibi Vector' for all ground assets.
- Fixed critical Prologue-to-Game crash (function renaming).
- Implemented Tiled JSON/TMX auto-conversion.
- Updated Asset Manager to visualize 1800+ assets.
- Cleaned up project structure (new assets/grounds folder).
- Auto-Ground logic added to GameScene.js.
2026-01-11 20:08:56 +01:00

629 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 (Fixed per instructions)
this.createNoirFog(width, height);
// 🎵 NOIR BACKGROUND MUSIC
this.playNoirMusic();
// 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 - FIXED PER USER REQUEST: Huge, slow, mist-like
this.add.particles(0, 0, 'fogParticle', {
x: { min: 0, max: width },
y: { min: 0, max: height },
scale: { start: 15, end: 20 }, // HUGE particles! (15-20x)
alpha: { start: 0.02, end: 0 }, // VERY subtle! (0.02)
lifespan: 15000, // Slower (longer life)
speed: { min: 2, max: 10 }, // Slow movement
frequency: 500,
blendMode: 'ADD'
});
// 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)
const key = 'forest_ambient';
try {
// STRICT CHECK: Only play if audio exists in cache
if (this.cache.audio.exists(key)) {
// Prevent duplicate playback
// Get all playing sounds to check if already playing
let isPlaying = false;
if (this.sound.sounds) {
this.sound.sounds.forEach(s => {
if (s.key === key && s.isPlaying) isPlaying = true;
});
}
if (!isPlaying) {
this.sound.play(key, {
volume: 0.3,
loop: true
});
console.log('🎵 Noir atmosphere music playing at 30% volume');
}
} else {
console.warn(`⚠️ Audio key not found: ${key} - Skipping to prevent crash.`);
}
} catch (error) {
console.error('❌ Audio error (handled):', error.message);
}
}
createMainMenu(width, height) {
// Get localized button texts
// Ensure i18n is initialized
if (!window.i18n) window.i18n = new LocalizationSystem();
const i18n = window.i18n;
// Use keys from localization.json (menu.new_game etc.)
const buttons = [
{
label: i18n.t('new_game', '▶ NEW GAME'),
color: '#8fbc8f',
action: () => this.startNewGame()
},
{
label: i18n.t('load_game', '📁 LOAD GAME'),
color: '#87ceeb',
action: () => this.loadGame()
},
{
label: i18n.t('settings', '⚙️ SETTINGS'),
color: '#daa520',
action: () => this.showSettings()
},
{
label: i18n.t('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 Amnesia Start Sequence...');
this.scene.start('IntroScene'); // ✅ AMNESIA START
}
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.');
}
}
}