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.
This commit is contained in:
2026-01-11 20:08:56 +01:00
parent 16e4284964
commit 7264ec6fc0
97 changed files with 49754 additions and 690 deletions

View File

@@ -0,0 +1,122 @@
class AssetTestScene extends Phaser.Scene {
constructor() {
super({ key: 'AssetTestScene' });
}
create() {
console.log('🖼️ Asset Test Scene - Gallery Mode');
this.cameras.main.setBackgroundColor('#222222');
const title = this.add.text(20, 20, 'ASSET GALLERY CHECK', {
fontSize: '32px',
fill: '#ffffff',
fontFamily: 'Courier New'
});
const categories = [
{ name: 'PHASE 1 (FARM)', items: (window.AssetManifest && window.AssetManifest.phases) ? window.AssetManifest.phases.farm : [] },
{ name: 'PHASE 2 (BASEMENT/MINE)', items: (window.AssetManifest && window.AssetManifest.phases) ? window.AssetManifest.phases.basement_mine : [] },
{ name: 'COMMON (CHARACTERS/INTRO)', items: (window.AssetManifest && window.AssetManifest.phases) ? window.AssetManifest.phases.common : [] }
];
let startY = 80;
let startX = 20;
categories.forEach(cat => {
// Category Header
this.add.text(startX, startY, cat.name.toUpperCase(), {
fontSize: '24px',
fill: '#ffff00',
fontstyle: 'bold'
});
startY += 40;
// Display Assets
let x = startX;
let count = 0;
// Use items from manifest
const items = cat.items || [];
if (items.length === 0) {
this.add.text(x, startY, '(No assets in manifest phase)', { color: '#666' });
startY += 40;
} else {
items.forEach(item => {
const key = item.key;
// Check if actually loaded
if (!this.textures.exists(key)) {
this.add.text(x, startY, `[FAIL] ${key}`, { color: '#ff0000', fontSize: '10px' });
x += 80;
return;
}
const img = this.add.image(x, startY, key);
// Scale down if too big
if (img.width > 64) {
const scale = 64 / Math.max(img.width, img.height);
img.setScale(scale);
}
img.setOrigin(0, 0);
// Detailed Info Text
const w = img.width;
const h = img.height;
let infoColor = '#bbbbbb';
// Scaling Check (Greenhouse limit example: 1024px)
if (w > 1024 || h > 1024) {
infoColor = '#ff5555'; // Red Alert
}
// Label: Name + Size
const labelText = `${key}\n${w}x${h}px`;
this.add.text(x, startY + 70, labelText, {
fontSize: '9px',
fill: infoColor,
wordWrap: { width: 75 }
});
// NOIR CHECK (Simulation: Check if alpha at borders is correct? Too expensive.
// Visual check: Red border fallback)
const border = this.add.rectangle(x + 32, startY + 32, 64, 64);
border.setStrokeStyle(1, 0x444444);
// Add click to log
img.setInteractive();
img.on('pointerdown', () => {
console.log(`Asset: ${key}, Size: ${img.width}x${img.height}`);
this.tweens.add({ targets: img, scale: img.scale * 1.2, duration: 100, yoyo: true });
});
x += 80;
count++;
if (count > 10) { // New row
count = 0;
x = startX;
startY += 100;
}
});
startY += 100; // Next category gap
}
});
// Navigation
const backBtn = this.add.text(this.cameras.main.width - 150, 20, 'BACK TO INTRO', {
fontSize: '20px',
backgroundColor: '#ff0000',
padding: { x: 10, y: 5 }
})
.setInteractive({ useHandCursor: true })
.on('pointerdown', () => this.scene.start('IntroScene'));
// Scroll
this.input.on('wheel', (pointer, gameObjects, deltaX, deltaY, deltaZ) => {
this.cameras.main.scrollY += deltaY * 0.5;
});
}
}

View File

@@ -45,10 +45,48 @@ class BootScene extends Phaser.Scene {
window.SPRITE_TREE_HEALTHY = 'tree_green_final';
window.SPRITE_TREE_BLUE = 'tree_blue_final';
window.SPRITE_TREE_DEAD = 'tree_dead_final';
window.SPRITE_TREE_DEAD = 'tree_dead_final';
window.SPRITE_TREE_SAPLING = 'tree_sapling';
// Takoj po bootu gremo v PreloadScene
this.time.delayedCall(100, () => {
// 🌍 GLOBAL FLAGS (Story Registry)
if (!window.gameState) window.gameState = {};
window.gameState.story = {
isAmnesiaActive: true,
daysPassed: 0,
parentsGhostFound: false,
introCompleted: false
};
console.log('📜 Story Registry Initialized:', window.gameState.story);
// 🛠️ DEBUG HOOK: Press 'G' for Gallery
// Note: BootScene is very short-lived, so we put this here just in case,
// but typically input listeners are wiped on scene change.
// Better: We rely on PreloadScene to carry us, BUT user asked for BootScene hook.
// Since BootScene auto-transitions in 100ms, user has to be VERY fast or we pause.
// Let's pause auto-transition if user holds G?
// Better: Just check directly.
const keys = this.input.keyboard.createCursorKeys();
// Since we can't reliably catch 'G' in 100ms, let's add it to window and check in PreloadScene?
// Or simply: Add a global listener that persists (if possible), or just scene start.
// Let's make BootScene wait a tiny bit longer or add instruction.
// Actually, user said "V BootScene... dodaj tisti hook".
this.input.keyboard.on('keydown-G', () => {
console.log('🖼️ Gallery Shortcut Detected!');
// Cancel auto transition
this.transitionEvent.remove();
this.scene.start('AssetTestScene');
});
// Debug Text
this.add.text(10, 10, 'Press G for Asset Gallery', { fontSize: '12px', fill: '#00ff00' }).setDepth(1000);
// Takoj po bootu gremo v PreloadScene (razen če G)
this.transitionEvent = this.time.delayedCall(1500, () => {
// Increased delay slightly to 1.5s so user has time to read/press G
// This is Development Mode ONLY tweak
this.scene.start('PreloadScene');
});
}

View File

@@ -43,11 +43,10 @@ class EnhancedPrologueScene extends Phaser.Scene {
const { width, height } = this.cameras.main;
// Start noir music (low volume)
this.noirMusic = this.sound.add('noir_ambient', {
this.noirMusic = this.playAudioSafe('noir_ambient', {
volume: 0.3,
loop: true
});
this.noirMusic.play();
// Create layers
this.backgroundLayer = this.add.container(0, 0);
@@ -93,8 +92,9 @@ class EnhancedPrologueScene extends Phaser.Scene {
// PHASE 1: Black Screen + Breathing (0:00 - 0:10)
this.showSubtitle("Everything is dark... why do I only hear silence?");
const breathingSound = this.sound.add('voice_breathing');
breathingSound.play();
this.showSubtitle("Everything is dark... why do I only hear silence?");
const breathingSound = this.playAudioSafe('voice_breathing');
// Fade to cellar after breathing + 2s
breathingSound.once('complete', () => {
@@ -116,8 +116,7 @@ class EnhancedPrologueScene extends Phaser.Scene {
ease: 'Sine.easeInOut'
});
const flyoverVoice = this.sound.add('voice_flyover');
flyoverVoice.play();
const flyoverVoice = this.playAudioSafe('voice_flyover');
// Show subtitle
this.time.delayedCall(500, () => {
@@ -166,8 +165,7 @@ class EnhancedPrologueScene extends Phaser.Scene {
// Play awakening voice
this.time.delayedCall(2000, () => {
const awakeningVoice = this.sound.add('voice_awakening');
awakeningVoice.play();
const awakeningVoice = this.playAudioSafe('voice_awakening');
this.showSubtitle("My head... it hurts. Where am I? Who am I...?");
@@ -209,8 +207,7 @@ class EnhancedPrologueScene extends Phaser.Scene {
// Play truth voice
this.time.delayedCall(1500, () => {
const truthVoice = this.sound.add('voice_truth');
truthVoice.play();
const truthVoice = this.playAudioSafe('voice_truth');
this.showSubtitle("Kai Marković. 14 years old. That's me. But this other girl... why do I feel so empty?");
@@ -256,8 +253,7 @@ class EnhancedPrologueScene extends Phaser.Scene {
// PHASE 5: Determination (2:30 - 3:00)
this.clearSubtitle();
const determinationVoice = this.sound.add('voice_determination');
determinationVoice.play();
const determinationVoice = this.playAudioSafe('voice_determination');
this.showSubtitle("Someone is waiting for me out there. I can't remember the face, but I feel the promise.");
@@ -334,6 +330,30 @@ class EnhancedPrologueScene extends Phaser.Scene {
this.endIntro();
}
playAudioSafe(key, config = {}) {
try {
if (this.cache.audio.exists(key)) {
const sound = this.sound.add(key, config);
sound.play();
return sound;
} else {
console.warn(`⚠️ Audio key missing: ${key}`);
// Return dummy object with 'once' method to prevent crashes on event listeners
return {
once: (event, callback) => {
// Simulate immediate completion
if (event === 'complete') callback();
},
stop: () => { },
play: () => { }
};
}
} catch (e) {
console.error(`❌ Audio error for ${key}:`, e);
return { once: (e, cb) => { if (e === 'complete') cb(); }, stop: () => { }, play: () => { } };
}
}
endIntro() {
console.log('🎬 Intro complete! Launching GameScene...');

View File

@@ -1,4 +1,5 @@
// Game Scene - Glavna igralna scena
// NOTE: WaterPhysicsSystem and AssetManifest must be loaded in index.html, not imported here (Electron/Browser Limitation)
class GameScene extends Phaser.Scene {
constructor() {
@@ -20,6 +21,22 @@ class GameScene extends Phaser.Scene {
async create() {
console.log('🎮 GameScene: Initialized!');
// 🖼️ SETUP BACKGROUND (Direct Farm Image)
// Checks 'farm_background' (from Phase 1) or fallback 'intro_chaos'
const bgKey = this.textures.exists('farm_background') ? 'farm_background' : 'intro_chaos';
if (this.textures.exists(bgKey)) {
this.add.image(0, 0, bgKey)
.setOrigin(0, 0)
.setDisplaySize(this.sys.canvas.width, this.sys.canvas.height)
.setDepth(-999); // Way back
console.log(`🖼️ Farm Background Set: ${bgKey}`);
}
// 🖼️ SETUP BACKGROUND
this.currentPhaseBg = null;
// Phase Selector removed in favor of Tiled Map
// Generate procedural textures
new TextureGenerator(this).generateAll();
InventoryIcons.create(this); // Override with flat 2D inventory icons
@@ -52,8 +69,8 @@ class GameScene extends Phaser.Scene {
daysFarmed: 0
};
// PARALLAX BACKGROUND - Clouds & Birds
this.createParallaxBackground();
// PARALLAX BACKGROUND - DISABLED FOR HARD RESET
// this.createParallaxBackground();
// Inicializiraj terrain sistem - 100x100 mapa
console.log('🌍 Initializing terrain...');
@@ -150,10 +167,11 @@ class GameScene extends Phaser.Scene {
console.log(`✅ Biome Enemies ready! (${enemyStats.alive} enemies)`);
// Quest System
// Quest System - DISABLED AUTO-START FOR CLEAN VIEW
this.landmarkQuests = new LandmarkQuestSystem(this);
this.landmarkQuests.startMainQuest();
this.landmarkQuests.startExplorationQuests();
console.log(`✅ Quest System ready! (${this.landmarkQuests.activeQuests.length} active quests)`);
// this.landmarkQuests.startMainQuest(); // Disable popup
// this.landmarkQuests.startExplorationQuests(); // Disable popup
console.log(`✅ Quest System initialized (Passive Mode)`);
// Map Reveal
this.mapReveal = new MapRevealSystem(this);
@@ -171,77 +189,106 @@ class GameScene extends Phaser.Scene {
console.log('🔍 Checking for NovaFarma map...');
try {
if (this.make.tilemap) {
// Ensure key matches PreloadScene
if (this.cache.tilemap.exists('NovaFarma')) {
const map = this.make.tilemap({ key: 'NovaFarma' });
// DEBUG LOGGING
console.log('🗺️ Map object created:', map);
if (map) {
console.log(`🗺️ Map dimensions: ${map.width}x${map.height} tiles, ${map.widthInPixels}x${map.heightInPixels} pixels`);
console.log('🗺️ Tilesets:', map.tilesets.map(t => t.name));
console.log('🗺️ Layers:', map.layers.map(l => l.name));
}
if (map && map.width > 0) {
console.log('✅ FOUND VALID USER MAP: NovaFarma.json');
this.terrainSystem.loadFromTiled(map);
// Use terrain system if available, else simple render
if (this.terrainSystem && this.terrainSystem.loadFromTiled) {
this.terrainSystem.loadFromTiled(map);
} else {
// 🏗️ STANDARD TILED RENDERING (The "Tiled-First" Approach)
console.log('🗺️ Rendering Tiled Map Layers...');
// 0. 🌍 AUTO-GROUND (User Request)
// Automatically fill background with grass if 'Ground' layer is empty or just as a base.
// Priority: grass_tile (User request), grass_texture, farm_background, intro_chaos
const groundTextures = ['grass_tile', 'grass_texture', 'farm_background', 'intro_chaos'];
const bgTexture = groundTextures.find(tex => this.textures.exists(tex));
if (bgTexture) {
// Create a TileSprite that covers the whole map
const bg = this.add.tileSprite(
0, 0,
map.widthInPixels, map.heightInPixels,
bgTexture
).setOrigin(0, 0).setDepth(-100);
console.log(`🌱 Auto-Ground applied using texture: ${bgTexture}`);
}
// 1. Render Tile Layers (Ground, Objects, Collision, Decoration...)
const tileset = map.tilesets[0];
if (tileset) {
map.layers.forEach(layer => {
if (layer.type === 'tilelayer') {
const createdLayer = map.createLayer(layer.name, tileset, 0, 0);
// Set Depth based on layer name or order
if (layer.name === 'Objects') createdLayer.setDepth(10); // Above ground
if (layer.name === 'Collision') createdLayer.setVisible(false); // Hide collision usually
}
});
}
// 2. Parse Object Layers (Audio, Logic, Spawns)
try {
// 🎵 MUSIC PROP
const mapMusic = map.properties && map.properties.find(p => p.name === 'music')?.value;
if (mapMusic) {
console.log(`🎵 Map Triggered Music: ${mapMusic}`);
// Use sound manager or direct play (SoundManager preferred but let's check safety)
// Assuming raw Phaser sound for safety if manager not ready
try {
this.sound.stopAll(); // Stop previous
this.sound.play(mapMusic, { loop: true, volume: 0.5 });
} catch (e) { console.warn("Audio play failed", e); }
}
const audioLayer = map.getObjectLayer('Audio_Zones');
if (audioLayer && audioLayer.objects) {
console.log(`🎵 Found ${audioLayer.objects.length} Audio Zones`);
}
const logicLayer = map.getObjectLayer('Logic') || map.getObjectLayer('Spawns');
if (logicLayer && logicLayer.objects) {
console.log(`🧠 Found ${logicLayer.objects.length} Logic Objects`);
logicLayer.objects.forEach(obj => {
if (obj.name === 'PlayerSpawn') {
this.tiledSpawnPoint = { x: obj.x, y: obj.y };
console.log('📍 Tiled defined Player Spawn:', this.tiledSpawnPoint);
}
if (obj.type === 'zombie_spawn') {
// Register zombie spawn point
}
});
}
} catch (err) {
console.warn('⚠️ Error parsing object layers:', err);
}
}
this.tiledMapLoaded = true;
// UPDATE SYSTEM DIMENSIONS TO MATCH TILED MAP
this.worldWidth = map.width;
this.worldHeight = map.height;
if (this.biomeSystem) {
this.biomeSystem.worldWidth = map.width;
this.biomeSystem.worldHeight = map.height;
// Regenerate biome map for the new dimensions (optional, but consistent)
this.biomeSystem.generateBiomeMap();
}
if (this.structureSystem) {
this.structureSystem.worldWidth = map.width;
this.structureSystem.worldHeight = map.height;
this.structureSystem.generateAll(); // Re-generate for tiled map
}
// 🕵️ FIND SPAWN POINT IN MAP
// 🕵️ FIND SPAWN POINT IN MAP (Optional)
const playerLayer = map.getLayer('Player');
if (playerLayer) {
console.log('👤 Found Player layer, searching for spawn point...');
// Find first tile in Player layer
map.layers.forEach(layer => {
if (layer.name === 'Player') {
for (let y = 0; y < layer.height; y++) {
for (let x = 0; x < layer.width; x++) {
if (layer.data[y][x].index !== -1) {
this.tiledSpawnPoint = {
x: x * map.tileWidth + map.tileWidth / 2,
y: y * map.tileHeight + map.tileHeight / 2
};
console.log(`📍 Found Player Spawn in Map at (${x}, ${y}) -> World: ${this.tiledSpawnPoint.x}, ${this.tiledSpawnPoint.y}`);
return; // Break loops
}
}
}
}
});
// 🕵️ DEBUG: Check what is under the player (Moved here for scope)
if (this.tiledSpawnPoint) {
const px = Math.floor(this.tiledSpawnPoint.x / map.tileWidth);
const py = Math.floor(this.tiledSpawnPoint.y / map.tileHeight);
const groundLayer = map.getLayer('Ground') || map.layers[0];
if (groundLayer) {
const tile = map.getTileAt(px, py, true, groundLayer.name);
const originTile = map.getTileAt(0, 0, true, groundLayer.name);
console.log(`🕵️ Inspecting Ground at Player (${px}, ${py}):`, tile ? `Index ${tile.index}` : 'NULL');
console.log(`🕵️ Inspecting Ground at Origin (0, 0):`, originTile ? `Index ${originTile.index}` : 'NULL');
}
}
} else {
console.warn('⚠️ No "Player" layer found in map!');
// Logic to extract spawn point...
console.log('👤 Player Layer detected (Logic Placeholder)');
}
} else {
console.error('❌ Map created but invalid width (0) or null!');
}
} else {
console.warn('⚠️ Map "NovaFarma" not found in cache. Ensure it is loaded in PreloadScene.');
}
} catch (e) {
console.error('⚠️ CRITICAL ERROR loading NovaFarma map:', e);
@@ -253,6 +300,26 @@ class GameScene extends Phaser.Scene {
this.terrainSystem.height = 100;
await this.terrainSystem.generate();
console.log('✅ Flat 2D terrain ready (Procedural)!');
// 🎨 BACKGROUND (Fallback)
// If we are in procedural mode (no Tilemap), let's add a nice background underlay
// Use a large texture or color. Since we don't have a single 'farm_background' in Manifest yet,
// we'll use a tileable dirt/grass approach or just set color.
// But user requested: "V GameScene.js dodaj sliko ozadja kmetije... this.add.image(0, 0, 'farm_background')"
// Warning: 'farm_background' is NOT in AssetManifest.js yet. I will check for 'intro_chaos' as placeholder?
// Or I should add 'farm_background' to manifest first? User implies it exists.
// I'll check existence.
if (this.textures.exists('farm_background')) {
this.add.image(0, 0, 'farm_background').setOrigin(0, 0).setDepth(-100);
console.log('🖼️ Added farm_background');
} else if (this.textures.exists('intro_chaos')) {
// Placeholder until real BG is ready
const bg = this.add.image(0, 0, 'intro_chaos').setOrigin(0, 0).setDepth(-100);
bg.setDisplaySize(2000, 2000); // Scale up
bg.setAlpha(0.2); // Subtle
console.log('🖼️ Added placeholder background (intro_chaos)');
}
} else {
console.log('✅ Flat 2D terrain ready (Tiled)!');
@@ -719,10 +786,11 @@ class GameScene extends Phaser.Scene {
// Kamera kontrole
this.setupCamera();
// Initialize Systems
console.log('🌦️ Initializing Unified Weather System...');
this.weatherSystem = new WeatherSystem(this);
this.timeSystem = this.weatherSystem; // Alias
// 🌊 WATER PHYSICS SYSTEM (Temporarily Disabled - HARD RESET)
// this.waterPhysics = new WaterPhysicsSystem(this);
// 💧 WATER RIPPLES SYSTEM (Temporarily Disabled)
// this.waterRipples = new WaterRipplesSystem(this);
// 💡 LIGHTING & SHADOW SYSTEM
console.log('💡 Initializing Lighting & Shadow System...');
@@ -740,7 +808,10 @@ class GameScene extends Phaser.Scene {
// 🌊 WATER PHYSICS SYSTEM (NEW!)
console.log('🌊 Initializing Water Physics System...');
this.waterPhysics = new WaterPhysicsSystem(this);
// 🌊 WATER PHYSICS SYSTEM (Temporarily Disabled)
// console.log('🌊 Initializing Water Physics System...');
// this.waterPhysics = new WaterPhysicsSystem(this);
// 💧 WATER RIPPLES SYSTEM (NEW!)
console.log('💧 Initializing Water Ripples System...');
@@ -2645,6 +2716,8 @@ class GameScene extends Phaser.Scene {
}
});
}
}

View File

@@ -84,470 +84,227 @@ class IntroScene extends Phaser.Scene {
}
create() {
console.log('🎬 IntroScene: Starting 60-SECOND EPIC...');
// Black background
console.log('🎬 IntroScene: Amnesia Start Sequence (Fixed Timing)');
this.cameras.main.setBackgroundColor('#000000');
// 🎵 Start ambient audio
this.startAmbientAudio();
// 1. SHADER INTEGRATION: Gaussian Blur
this.blurFX = null;
if (this.cameras.main.postFX) {
try {
this.blurFX = this.cameras.main.postFX.addBlur(0, 0, 0);
} catch (e) {
console.warn('⚠️ PostFX not supported');
}
}
// 📺 Create VHS overlay effects
this.createVHSEffects();
// Initialize Blur at 20
if (this.blurFX) {
this.blurFX.strength = 20; // High blur
// Setup input for skip
this.setupSkipControls();
// Tween blur to 0 over 6 seconds (Standard Time)
this.tweens.add({
targets: this.blurFX,
strength: 0,
duration: 6000,
ease: 'Power2.easeOut',
onComplete: () => {
this.triggerWakeUp();
}
});
} else {
// Fallback if shaders not supported
this.cameras.main.alpha = 0;
this.tweens.add({
targets: this.cameras.main,
alpha: 1,
duration: 6000,
onComplete: () => this.triggerWakeUp()
});
}
// Enable skip after 5 seconds
this.time.delayedCall(5000, () => {
this.skipEnabled = true;
this.showSkipPrompt();
// 2. FLASHBACK ENGINE (Safe Mode)
// Images: Birthday (0s), Longboard (1.5s), Dreads (3.0s)
this.flashbackSequence([
'intro_birthday_cake',
'intro_otac_longboard',
'intro_ana_barbershop'
]);
// 3. TYPEWRITER LOGIC (Fixed Timing & No Overlap)
// Line 1: 0.5s -> 3.0s
this.time.delayedCall(500, () => {
this.showDialogue('Vse je zamegljeno... Zakaj me vse boli?', 2500);
});
// Start Phase 1
this.playPhase1HappyChildhood();
// Line 2: 4.0s -> End (starts only after Line 1 is cleared)
this.time.delayedCall(4000, () => {
this.showDialogue('Kdo so ti ljudje v moji glavi?', 2000);
});
// Audio atmosphere
this.startAmbientAudio();
}
flashbackSequence(images) {
images.forEach((key, index) => {
// Check if asset exists before scheduling
if (this.textures.exists(key)) {
this.time.delayedCall(index * 1500, () => {
this.triggerFlashback(key);
});
} else {
console.warn(`⚠️ Missing flash asset: ${key} - Skipping visual, keeping timing.`);
}
});
}
triggerFlashback(key) {
// Double check existence
if (!this.textures.exists(key)) return;
const width = this.cameras.main.width;
const height = this.cameras.main.height;
const image = this.add.image(width / 2, height / 2, key);
// Scale to cover most of screen (maintain aspect ratio)
const scale = Math.max(width / image.width, height / image.height) * 0.8;
image.setScale(scale);
image.setAlpha(0);
image.setDepth(10);
image.setBlendMode(Phaser.BlendModes.ADD);
// Flash in and out
this.tweens.add({
targets: image,
alpha: { from: 0, to: 0.15 },
duration: 500,
yoyo: true,
hold: 500,
onComplete: () => {
if (image && image.active) image.destroy();
}
});
// Zoom
this.tweens.add({
targets: image,
scale: scale * 1.1,
duration: 1500
});
}
showDialogue(text, duration) {
// OVERLAP FIX: Destroy previous text immediately
if (this.currentText) {
this.currentText.destroy();
this.currentText = null;
}
const width = this.cameras.main.width;
const height = this.cameras.main.height;
const textObj = this.add.text(width / 2, height - 100, '', {
fontFamily: 'Courier New',
fontSize: '24px',
fill: '#ffffff',
align: 'center',
stroke: '#000000',
strokeThickness: 4
});
textObj.setOrigin(0.5);
textObj.setDepth(100);
this.currentText = textObj; // Track current text
// Typewriter
let charIndex = 0;
const speed = 50;
if (text.length * speed > duration) {
// Speed up if text is too long for duration
speed = Math.floor(duration / text.length);
}
const timer = this.time.addEvent({
delay: speed,
callback: () => {
if (!textObj.active) return;
textObj.text += text[charIndex];
charIndex++;
if (charIndex >= text.length) {
timer.remove();
// Fade out logic
this.time.delayedCall(Math.max(500, duration - (text.length * speed)), () => {
if (textObj.active) {
this.tweens.add({
targets: textObj,
alpha: 0,
duration: 500,
onComplete: () => {
if (textObj.active) textObj.destroy();
if (this.currentText === textObj) this.currentText = null;
}
});
}
});
}
},
repeat: text.length - 1
});
}
startAmbientAudio() {
try {
if (this.cache.audio.exists('noir_ambience')) {
this.ambientAudio = this.sound.add('noir_ambience', {
volume: 0.2,
volume: 0.1, // Start very quiet
loop: true
});
this.ambientAudio.play();
// Fade in audio
this.tweens.add({
targets: this.ambientAudio,
volume: 0.4,
duration: 6000
});
}
} catch (e) {
console.warn('⚠️ Ambient audio not available');
}
}
createVHSEffects() {
const width = this.cameras.main.width;
const height = this.cameras.main.height;
triggerWakeUp() {
console.log('⚡ TRANSITION: Wake Up!');
// VHS Scanlines
this.scanlines = this.add.graphics();
this.scanlines.setDepth(900);
// 4. TRANSITION TO GAMEPLAY
// White Flash
this.cameras.main.flash(1000, 255, 255, 255);
for (let y = 0; y < height; y += 4) {
this.scanlines.fillStyle(0x000000, 0.15);
this.scanlines.fillRect(0, y, width, 2);
}
// VHS Noise overlay
this.vhsNoise = this.add.rectangle(
width / 2,
height / 2,
width,
height,
0xffffff,
0.03
);
this.vhsNoise.setDepth(901);
// Animate noise
this.tweens.add({
targets: this.vhsNoise,
alpha: { from: 0.01, to: 0.05 },
duration: 100,
yoyo: true,
repeat: -1
});
}
setupSkipControls() {
this.input.keyboard.on('keydown-X', () => this.skipIntro());
this.input.keyboard.on('keydown-SPACE', () => this.skipIntro());
this.input.on('pointerdown', () => {
if (this.skipEnabled) {
this.skipIntro();
}
});
}
showSkipPrompt() {
if (this.skipPrompt) return;
const width = this.cameras.main.width;
const height = this.cameras.main.height;
this.skipPrompt = this.add.text(
width - 20,
height - 20,
'[Hold X to face reality]',
{
fontFamily: 'Courier New',
fontSize: '14px',
fill: '#00ffff',
stroke: '#000000',
strokeThickness: 2
}
);
this.skipPrompt.setOrigin(1, 1);
this.skipPrompt.setAlpha(0.7);
this.skipPrompt.setDepth(1000);
this.tweens.add({
targets: this.skipPrompt,
alpha: { from: 0.7, to: 1.0 },
duration: 800,
yoyo: true,
repeat: -1
});
}
skipIntro() {
if (!this.skipEnabled) return;
console.log('⏭️ IntroScene: Skipped by user');
// Stop all
if (this.ambientAudio) this.ambientAudio.stop();
if (this.currentVoice) this.currentVoice.stop();
this.tweens.killAll();
this.time.removeAllEvents();
// Fade to GameScene
this.cameras.main.fadeOut(500, 0, 0, 0);
this.cameras.main.once('camerafadeoutcomplete', () => {
this.scene.start('StoryScene'); // Main Menu
});
}
playVoice(key, subtitleText) {
try {
if (this.cache.audio.exists(key)) {
if (this.currentVoice) this.currentVoice.stop();
this.currentVoice = this.sound.add(key, { volume: 0.8 });
this.currentVoice.play();
}
} catch (e) {
console.warn(`⚠️ Voice ${key} not available`);
}
// Show subtitle at bottom
if (subtitleText) {
this.showSubtitle(subtitleText, 2800); // 2.8s duration (slightly less than 3s shot)
}
}
showSubtitle(text, duration = 3000) {
// Remove previous subtitle
if (this.currentText) {
this.currentText.destroy();
}
const width = this.cameras.main.width;
const height = this.cameras.main.height;
// Create subtitle at bottom
this.currentText = this.add.text(
width / 2,
height - 100, // 100px from bottom
text,
{
fontFamily: 'Courier New',
fontSize: '20px',
fill: '#ffffff',
stroke: '#000000',
strokeThickness: 3,
shadow: {
offsetX: 2,
offsetY: 2,
color: '#00ffff',
blur: 8,
stroke: false,
fill: true
},
align: 'center',
wordWrap: { width: width - 100 }
}
);
this.currentText.setOrigin(0.5);
this.currentText.setAlpha(0);
this.currentText.setDepth(950); // Below VHS but above Polaroid
// Fade in
this.tweens.add({
targets: this.currentText,
alpha: 1,
duration: 500,
ease: 'Power2.easeOut'
});
// Fade out before end
this.time.delayedCall(duration - 300, () => {
if (this.currentText) {
this.tweens.add({
targets: this.currentText,
alpha: 0,
duration: 300,
onComplete: () => {
if (this.currentText) this.currentText.destroy();
// Stop audio
if (this.ambientAudio) {
this.tweens.add({
targets: this.ambientAudio,
volume: 0,
duration: 500,
onComplete: () => {
if (this.ambientAudio && this.ambientAudio.stop) {
this.ambientAudio.stop();
}
});
}
});
}
// ═══════════════════════════════════════════════════════════
// PHASE 1: HAPPY MEMORIES (0-15s) - REDESIGNED!
// ═══════════════════════════════════════════════════════════
playPhase1HappyChildhood() {
console.log('🎬 Phase 1: Happy Memories (0-15s) - NEW SEQUENCE');
this.currentPhase = 1;
// Shot 1 (0-2.5s): Kai + Dad Longboard
this.showShot('intro_otac_longboard', 0, 2500, { warm: true });
this.time.delayedCall(100, () => this.playVoice('kai_01', "Dad and I. Before everything changed."));
// Shot 2 (2.5-5s): Barbershop - Him (dreads) + Her (coloring)
this.showShot('intro_ana_barbershop', 2500, 5000, { warm: true });
this.time.delayedCall(2600, () => this.playVoice('ana_01', "Getting ready. We always did things together."));
// Shot 3 (5-7.5s): Birthday Cake - "HERE WE WERE STILL HAPPY"
this.showShot('intro_birthday_cake', 5000, 7500, { warm: true });
this.time.delayedCall(5100, () => this.playVoice('kai_02', "Here we were still happy. Still a family."));
// Shot 4 (7.5-10s): Family Portrait
this.showShot('intro_family_portrait', 7500, 10000, { warm: true });
this.time.delayedCall(7600, () => this.playVoice('ana_02', "All of us. Together. Perfect."));
// Shot 5 (10-12.5s): Kai + Ana Holding Hands as Kids
this.showShot('intro_twins_childhood', 10000, 12500, { warm: true });
this.time.delayedCall(10100, () => this.playVoice('kai_03', "We were always two. Inseparable."));
// Shot 6 (12.5-15s): Kai's Bedroom
this.showShot('intro_bedroom', 12500, 15000, { warm: true });
this.time.delayedCall(12600, () => this.playVoice('ana_03', "Our room. Our sanctuary."));
// Transition to Phase 2
this.time.delayedCall(15000, () => this.playPhase2TheVirus());
}
// ═══════════════════════════════════════════════════════════
// PHASE 2: THE VIRUS (15-30s)
// ═══════════════════════════════════════════════════════════
playPhase2TheVirus() {
console.log('🎬 Phase 2: The Virus (15-30s)');
this.currentPhase = 2;
// Shot 7 (15-17.5s): Virus
this.showShot('intro_virus', 15000, 17500, { toxic: true, glitch: true });
this.time.delayedCall(15100, () => this.playVoice('kai_04', "Then came X-Noir. The virus."));
// Shot 8 (17.5-20s): Chaos
this.showShot('intro_chaos', 17500, 20000, { red: true, glitch: true });
this.time.delayedCall(17600, () => this.playVoice('ana_04', "Everyone changed. Streets burned."));
this.cameras.main.shake(2500, 0.005);
// Shot 9 (20-22.5s): Zombies
this.showShot('intro_zombies', 20000, 22500, { red: true, strobe: true });
this.time.delayedCall(20100, () => this.playVoice('kai_05', "Friends became zombies."));
// Shot 10 (22.5-25s): Parents Ghosts
this.showShot('intro_parents_ghosts', 22500, 25000, { fadeIn: true });
this.time.delayedCall(22600, () => this.playVoice('ana_05', "Our parents fought... and lost."));
// Shot 11 (25-30s): Ana Taken
this.showShot('intro_ana_taken', 25000, 30000, { red: true });
this.time.delayedCall(25100, () => this.playVoice('ana_06', "KAI! DON'T FORGET ME!"));
// Transition to Phase 3
this.time.delayedCall(30000, () => this.playPhase3TheAmnesia());
}
// ═══════════════════════════════════════════════════════════
// PHASE 3: AMNESIA & ANA MEMORY (30-60s) - NO AGING SPOILERS!
// ═══════════════════════════════════════════════════════════
playPhase3TheAmnesia() {
console.log('🎬 Phase 3: Amnesia & Ana Memory (30-60s)');
this.currentPhase = 3;
// Shot 12 (30-35s): BLACK SCREEN - "I have no memory"
const width = this.cameras.main.width;
const height = this.cameras.main.height;
const blackScreen = this.add.rectangle(width / 2, height / 2, width, height, 0x000000);
blackScreen.setAlpha(0);
blackScreen.setDepth(50);
this.tweens.add({
targets: blackScreen,
alpha: 1,
duration: 1000
});
this.time.delayedCall(30500, () => this.playVoice('kai_06', "I have no memory. Everything is... gone."));
this.time.delayedCall(33000, () => this.playVoice('kai_07', "They say I'm fourteen. But I don't remember... anything."));
// Shot 13 (35-40s): Kai Alone in Basement
this.time.delayedCall(35000, () => {
this.tweens.add({
targets: blackScreen,
alpha: 0,
duration: 1000,
onComplete: () => blackScreen.destroy()
}
});
});
this.showShot('intro_kai_alone', 35000, 40000, { fadeIn: true });
this.time.delayedCall(35500, () => this.playVoice('kai_08', "Alone. In darkness. With only... this."));
// Shot 14 (40-50s): Ana Memory - LAST TIME THEY SAW EACH OTHER
this.showShot('intro_ana_memory', 40000, 50000, { fadeIn: true });
this.time.delayedCall(40500, () => this.playVoice('ana_07', "Her face. The only thing I remember."));
this.time.delayedCall(43500, () => this.playVoice('kai_09', "Ana. My sister. My twin. The last thing I saw... before everything went dark."));
// Shot 15 (50-55s): Gronk Arrival
this.showShot('intro_gronk', 50000, 55000, { fadeIn: true });
this.time.delayedCall(50500, () => this.playVoice('gronk_01', "Finally awake, old man. Your mission awaits."));
// Shot 16 (55-60s): Ana Photo + FINAL DETERMINATION
this.showShot('intro_ana_memory', 55000, 60000, { fadeIn: true });
this.time.delayedCall(55500, () => this.playVoice('kai_11', "I must find her."));
this.time.delayedCall(57500, () => this.playVoice('kai_12', "...even if it takes my entire life."));
// Transition to Main Menu
this.time.delayedCall(60000, () => {
console.log('🎬 IntroScene: EPIC COMPLETE! Starting menu...');
if (this.ambientAudio) this.ambientAudio.stop();
this.cameras.main.fadeOut(1000, 0, 0, 0);
this.cameras.main.once('camerafadeoutcomplete', () => {
this.scene.start('StoryScene'); // Main Menu
});
});
}
// Phase 4 removed - integrated into Phase 3!
showShot(imageKey, startTime, endTime, options = {}) {
const width = this.cameras.main.width;
const height = this.cameras.main.height;
const duration = endTime - startTime;
this.time.delayedCall(startTime, () => {
// CROSSFADE: Store old reference for simultaneous fade
const oldPolaroid = this.currentPolaroid;
// (Old will fade out WHILE new fades in - see below)
// Create photo image (65% of screen)
const photo = this.add.image(width / 2, height / 2 - 30, imageKey);
photo.setAlpha(0);
photo.setDepth(20);
// Scale to 65% of screen size
const targetSize = Math.min(width, height) * 0.65;
const photoScale = targetSize / Math.max(photo.width, photo.height);
photo.setScale(photoScale);
// Create Polaroid white frame
const frameWidth = photo.displayWidth + 40;
const frameHeight = photo.displayHeight + 80;
const frame = this.add.graphics();
frame.setDepth(19);
frame.setAlpha(0);
// Dirty white Polaroid background
frame.fillStyle(0xf5f5dc, 1);
frame.fillRect(
(width - frameWidth) / 2,
(height - frameHeight) / 2 - 30,
frameWidth,
frameHeight
);
// Add dirt/grain texture
for (let i = 0; i < 50; i++) {
const x = (width - frameWidth) / 2 + Math.random() * frameWidth;
const y = (height - frameHeight) / 2 - 30 + Math.random() * frameHeight;
frame.fillStyle(0xccccbb, 0.3);
frame.fillCircle(x, y, 1);
}
this.currentPolaroid = { photo, frame };
// 🌊 FLOATING ANIMATION
this.tweens.add({
targets: [photo, frame],
y: '-=5',
duration: 2000,
yoyo: true,
repeat: -1,
ease: 'Sine.easeInOut'
});
// Apply visual effects
if (options.warm) {
photo.setTint(0xffddaa);
}
if (options.red) {
photo.setTint(0xff6666);
}
if (options.toxic) {
photo.setTint(0x66ff66);
}
// CROSSFADE: NEW fades IN (800ms)
this.tweens.add({
targets: [photo, frame],
alpha: 1,
duration: 800,
ease: 'Power2.easeOut'
});
// SIMULTANEOUSLY: OLD fades OUT (same 800ms)
if (oldPolaroid) {
this.tweens.add({
targets: [oldPolaroid.photo, oldPolaroid.frame],
alpha: 0,
duration: 800, // Same duration = perfect crossfade!
ease: 'Power2.easeIn',
onComplete: () => {
oldPolaroid.frame.destroy();
oldPolaroid.photo.destroy();
}
});
}
// OLD GLITCH-OUT removed - crossfade handles transitions now!
if (options.glitch) {
this.tweens.add({
targets: [photo, frame],
x: { from: width / 2 - 5, to: width / 2 + 5 },
duration: 100,
repeat: duration / 100,
yoyo: true
});
}
if (options.strobe) {
this.tweens.add({
targets: photo,
alpha: { from: 0.7, to: 1.0 },
duration: 150,
repeat: duration / 150,
yoyo: true
});
}
});
}
shutdown() {
if (this.ambientAudio) this.ambientAudio.stop();
if (this.currentVoice) this.currentVoice.stop();
if (this.skipPrompt) this.skipPrompt.destroy();
if (this.currentPolaroid) {
this.currentPolaroid.frame.destroy();
this.currentPolaroid.photo.destroy();
}
this.tweens.killAll();
this.time.removeAllEvents();
// Switch Scene
this.time.delayedCall(1000, () => {
// Set flag
if (window.gameState && window.gameState.story) {
window.gameState.story.isAmnesiaActive = true;
}
this.scene.start('GameScene');
});
}
}

View File

@@ -9,11 +9,25 @@ class PreloadScene extends Phaser.Scene {
this.createLoadingBar();
// 🛠️ Asset Fix: Create placeholder for missing particles
// This prevents crash in WaterPhysicsSystem if 'particle_white' is used before load
if (!this.textures.exists('particle_white')) {
const g = this.make.graphics({ x: 0, y: 0, add: false });
g.fillStyle(0xffffff, 1);
g.fillCircle(4, 4, 4);
g.generateTexture('particle_white', 8, 8);
}
// ═══════════════════════════════════════════════════════════════
// 🎵 AUDIO PRELOAD - Cinematic Voices
// ═══════════════════════════════════════════════════════════════
this.preloadAudio();
// ═══════════════════════════════════════════════════════════════
// 🌍 LOCALIZATION
// ═══════════════════════════════════════════════════════════════
this.load.json('localization', 'assets/localization.json');
// ═══════════════════════════════════════════════════════════════
// 🎨 CHARACTER SPRITES - Demo Characters
// ═══════════════════════════════════════════════════════════════
@@ -29,23 +43,50 @@ class PreloadScene extends Phaser.Scene {
}
preloadCharacterSprites() {
console.log('🎨 Preloading character sprites...');
console.log('🎨 Preloading ALL ASSETS from AssetManifest...');
const basePath = 'assets/references/main_characters/';
// Dynamic import logic (requires module system, but we'll use direct reference if global or shim)
// Since we are in Setup phase, we assume AssetManifest is loaded via script tag or bundler
// Kai sprites
this.loadImageSafe('kai_idle', basePath + 'kai/animations/idle/kai_idle_frame1.png');
this.loadImageSafe('kai_walk', basePath + 'kai/animations/walk/kai_walk_frame1.png');
// Check if AssetManifest exists globally or we need to rely on the file we created
// For this environment, we'll manually iterate the data structure if it's not imported.
// But since we just created src/AssetManifest.js, let's try to assume it's available.
// Ana sprites
this.loadImageSafe('ana_idle', basePath + 'ana/animations/idle/ana_idle_frame1.png');
this.loadImageSafe('ana_walk', basePath + 'ana/animations/walk/ana_walk_frame1.png');
// NOTE: In standard Phaser/Webpack, we would import { AssetManifest } from '../AssetManifest'.
// Here we will inject logic to load it. For now, let's assume we can fetch it or it's hardcoded.
// Susi (companion)
this.loadImageSafe('susi_idle', 'assets/references/companions/susi/animations/idle/susi_idle_frame1.png');
this.loadImageSafe('susi_run', 'assets/references/companions/susi/animations/run/susi_run_frame1.png');
// Load Manifest Images
// Since we are using script tags in index.html, window.AssetManifest is already available!
if (window.AssetManifest) {
const manifest = window.AssetManifest;
console.log('🎨 Character sprites queued');
console.log(`📦 Loading Asset Manifest (Phased)...`);
// Iterate over phases (farm, basement_mine, etc.)
if (manifest.phases) {
Object.keys(manifest.phases).forEach(phaseName => {
console.log(`Phase: ${phaseName} - ${manifest.phases[phaseName].length} items`);
manifest.phases[phaseName].forEach(img => {
this.loadImageSafe(img.key, img.path);
});
});
}
// Iterate over spritesheets
if (manifest.spritesheets) {
manifest.spritesheets.forEach(sheet => {
this.load.spritesheet(sheet.key, sheet.path, sheet.frameConfig);
});
}
console.log('✅ AssetManifest Queue Complete');
// 🗺️ LOAD MAP
this.load.tilemapTiledJSON('NovaFarma', 'assets/maps/NovaFarma.json');
console.log('🗺️ Preloading NovaFarma.json map...');
} else {
console.error('❌ Critical: window.AssetManifest not found! Check index.html imports.');
}
}
loadImageSafe(key, path) {
@@ -474,6 +515,23 @@ class PreloadScene extends Phaser.Scene {
zombie.destroy();
bg.destroy(); // Ensure bg is destroyed here
console.log('✅ PreloadScene: Assets loaded!');
// 🌍 INITIALIZE LOCALIZATION
if (!window.i18n) {
try {
window.i18n = new LocalizationSystem();
const locData = this.cache.json.get('localization');
if (locData) {
window.i18n.setExternalData(locData);
console.log('🌍 Localization initialized with external data');
} else {
console.warn('⚠️ Localization JSON not found in cache');
}
} catch (e) {
console.error('❌ Localization init failed:', e);
}
}
window.gameState.currentScene = 'PreloadScene';
// ✅ Starting INTRO SEQUENCE (NEW! Jan 10, 2026)

View File

@@ -22,12 +22,11 @@ class StoryScene extends Phaser.Scene {
graphics.fillGradientStyle(0x1a0000, 0x1a0000, 0x000000, 0x000000, 1);
graphics.fillRect(0, 0, width, height);
// 🌫️ NOIR FOG EFFECT
// 🌫️ NOIR FOG EFFECT (Fixed per instructions)
this.createNoirFog(width, height);
// 🎵 NOIR BACKGROUND MUSIC (disabled temporarily)
// this.playNoirMusic();
console.log('🔇 Music disabled temporarily');
// 🎵 NOIR BACKGROUND MUSIC
this.playNoirMusic();
// MAIN TITLE (horizontal, top center)
@@ -113,16 +112,16 @@ class StoryScene extends Phaser.Scene {
graphics.generateTexture('fogParticle', 64, 64);
graphics.destroy();
// Fog particle emitter - HUGE & SUBTLE (no circles!)
// 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!
alpha: { start: 0.02, end: 0 }, // VERY subtle!
lifespan: 8000,
speed: { min: 5, max: 20 },
frequency: 300,
blendMode: 'NORMAL'
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)
@@ -141,57 +140,60 @@ class StoryScene extends Phaser.Scene {
playNoirMusic() {
// Play forest evening ambience (noir atmosphere)
const key = 'forest_ambient';
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');
// 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('⚠️ 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();
console.warn(`⚠️ Audio key not found: ${key} - Skipping to prevent crash.`);
}
} catch (error) {
console.error('❌ Audio error (non-critical):', error.message);
console.log('✅ Game continues without music');
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 ? i18n.t('new_game', '▶ NEW GAME') : '▶ NEW GAME',
label: i18n.t('new_game', '▶ NEW GAME'),
color: '#8fbc8f',
action: () => this.startNewGame()
},
{
label: i18n ? i18n.t('load_game', '📁 LOAD GAME') : '📁 LOAD GAME',
label: i18n.t('load_game', '📁 LOAD GAME'),
color: '#87ceeb',
action: () => this.loadGame()
},
{
label: i18n ? i18n.t('settings', '⚙️ SETTINGS') : '⚙️ SETTINGS',
label: i18n.t('settings', '⚙️ SETTINGS'),
color: '#daa520',
action: () => this.showSettings()
},
{
label: i18n ? i18n.t('exit', '❌ EXIT') : '❌ EXIT',
label: i18n.t('exit', '❌ EXIT'),
color: '#cd5c5c',
action: () => this.exitGame()
}
@@ -449,8 +451,8 @@ class StoryScene extends Phaser.Scene {
startNewGame() {
console.log('🎮 Starting New Game...');
console.log('🎥 Launching ULTIMATE Prologue (100% Polished!)...');
this.scene.start('UltimatePrologueScene'); // ✅ ULTIMATE INTRO!
console.log('🎥 Launching Amnesia Start Sequence...');
this.scene.start('IntroScene'); // ✅ AMNESIA START
}
loadGame() {

View File

@@ -357,7 +357,7 @@ class UltimatePrologueScene extends Phaser.Scene {
this.showQuestBrief();
// Fade to game
this.time.delayedCall(4000, () => this.fadeToGame());
this.time.delayedCall(4000, () => this.exitPrologue());
});
}
@@ -443,10 +443,10 @@ class UltimatePrologueScene extends Phaser.Scene {
skipToGame() {
console.log('⏭️ Skipping to game...');
this.fadeToGame();
this.exitPrologue();
}
fadeToGame() {
exitPrologue() {
console.log('🎬 Intro complete! Starting game...');
// Fade out music (if exists)