Files
2026-02-25 22:49:56 -05:00

1433 lines
54 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
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.
// Main game class
class Game {
constructor() {
this.state = new GameState();
this.assets = new AssetManager();
this.ui = new UIManager(this);
this.camera = new CameraSystem(this);
this.enemyAI = new EnemyAI(this);
this.input = new InputHandler(this);
// Initialize CameraSystem's EP config (from EnemyAI)
this.camera.initEPConfig();
this.timeInterval = null;
this.powerInterval = null;
this.viewPosition = 0.25;
this.isRotatingLeft = false;
this.isRotatingRight = false;
this.rotationSpeed = 0.015;
this.initElements();
this.bindEvents();
}
initElements() {
this.mainMenu = document.getElementById('main-menu');
this.gameScreen = document.getElementById('game-screen');
this.gameOverElement = document.getElementById('game-over');
this.gameOverText = document.getElementById('game-over-text');
this.tutorialOverlay = document.getElementById('tutorial-overlay');
this.tutorialGotItBtn = document.getElementById('tutorial-got-it');
this.startBtn = document.getElementById('start-game');
this.continueBtn = document.getElementById('continue-game');
this.specialNightBtn = document.getElementById('special-night-btn');
this.customNightBtn = document.getElementById('custom-night-btn');
this.starIcon = document.getElementById('star-icon');
this.starIcon2 = document.getElementById('star-icon-2');
this.starIcon3 = document.getElementById('star-icon-3');
this.restartBtn = document.getElementById('restart');
this.mainMenuBtn = document.getElementById('main-menu-btn');
// 音量控制元素
this.volumeBtn = document.getElementById('volume-btn');
this.volumePanel = document.getElementById('volume-panel');
this.closeVolumePanelBtn = document.getElementById('close-volume-panel');
this.gameBgVolumeSlider = document.getElementById('game-bg-volume');
this.menuMusicVolumeSlider = document.getElementById('menu-music-volume');
this.jumpscareVolumeSlider = document.getElementById('jumpscare-volume');
this.ventCrawlingVolumeSlider = document.getElementById('vent-crawling-volume');
this.masterVolumeSlider = document.getElementById('master-volume');
// 调试:检查元素是否找到
if (!this.volumeBtn) console.error('Volume button not found!');
if (!this.volumePanel) console.error('Volume panel not found!');
// Custom Night 元素
this.customNightMenu = document.getElementById('custom-night-menu');
this.epsteinSlider = document.getElementById('epstein-slider');
this.trumpSlider = document.getElementById('trump-slider');
this.hawkingSlider = document.getElementById('hawking-slider');
this.epsteinValue = document.getElementById('epstein-value');
this.trumpValue = document.getElementById('trump-value');
this.hawkingValue = document.getElementById('hawking-value');
this.startCustomNightBtn = document.getElementById('start-custom-night');
this.backToMenuBtn = document.getElementById('back-to-menu');
// 初始化音量设置
this.initVolumeSettings();
}
initVolumeSettings() {
const volumes = this.assets.getAllVolumes();
this.gameBgVolumeSlider.value = Math.round(volumes.gameBg * 100);
this.menuMusicVolumeSlider.value = Math.round(volumes.menuMusic * 100);
this.jumpscareVolumeSlider.value = Math.round(volumes.jumpscare * 100);
this.ventCrawlingVolumeSlider.value = Math.round(volumes.ventCrawling * 100);
this.masterVolumeSlider.value = Math.round(volumes.master * 100);
// 更新百分比显示
this.updateVolumePercents();
}
updateVolumePercents() {
const sliders = [
this.gameBgVolumeSlider,
this.menuMusicVolumeSlider,
this.jumpscareVolumeSlider,
this.ventCrawlingVolumeSlider,
this.masterVolumeSlider
];
sliders.forEach(slider => {
const percent = slider.parentElement.querySelector('.volume-percent');
if (percent) {
percent.textContent = slider.value + '%';
}
});
}
bindEvents() {
this.startBtn.addEventListener('click', () => this.startGame());
this.continueBtn.addEventListener('click', () => this.continueGame());
this.specialNightBtn.addEventListener('click', () => this.startSpecialNight());
this.customNightBtn.addEventListener('click', () => this.showCustomNightMenu());
this.restartBtn.addEventListener('click', () => this.restartGame());
// 音量面板事件
this.volumeBtn.addEventListener('click', () => {
this.volumePanel.classList.toggle('hidden');
});
this.closeVolumePanelBtn.addEventListener('click', () => {
this.volumePanel.classList.add('hidden');
});
// 音量滑块事件
this.gameBgVolumeSlider.addEventListener('input', (e) => {
this.assets.setVolume('gameBg', parseInt(e.target.value) / 100);
this.updateVolumePercents();
// 立即更新游戏中的背景音效
if (this.state.isGameRunning) {
const ventsSound = this.assets.sounds['vents'];
if (ventsSound && !ventsSound.paused) {
const volumes = this.assets.getAllVolumes();
ventsSound.volume = volumes.gameBg * volumes.master;
}
}
});
this.menuMusicVolumeSlider.addEventListener('input', (e) => {
this.assets.setVolume('menuMusic', parseInt(e.target.value) / 100);
this.updateVolumePercents();
// 立即更新主菜单音乐音量
const menuMusic = document.getElementById('menu-music');
if (menuMusic && !menuMusic.paused) {
const volumes = this.assets.getAllVolumes();
menuMusic.volume = volumes.menuMusic * volumes.master;
}
});
this.jumpscareVolumeSlider.addEventListener('input', (e) => {
this.assets.setVolume('jumpscare', parseInt(e.target.value) / 100);
this.updateVolumePercents();
});
this.ventCrawlingVolumeSlider.addEventListener('input', (e) => {
this.assets.setVolume('ventCrawling', parseInt(e.target.value) / 100);
this.updateVolumePercents();
});
this.masterVolumeSlider.addEventListener('input', (e) => {
this.assets.setVolume('master', parseInt(e.target.value) / 100);
this.updateVolumePercents();
// 立即更新所有正在播放的音效
const menuMusic = document.getElementById('menu-music');
if (menuMusic && !menuMusic.paused) {
const volumes = this.assets.getAllVolumes();
menuMusic.volume = volumes.menuMusic * volumes.master;
}
if (this.state.isGameRunning) {
const ventsSound = this.assets.sounds['vents'];
if (ventsSound && !ventsSound.paused) {
const volumes = this.assets.getAllVolumes();
ventsSound.volume = volumes.gameBg * volumes.master;
}
}
});
this.mainMenuBtn.addEventListener('click', () => this.showMainMenu());
this.tutorialGotItBtn.addEventListener('click', () => this.closeTutorial());
// Custom Night 事件
this.startCustomNightBtn.addEventListener('click', () => this.startCustomNight());
this.backToMenuBtn.addEventListener('click', () => this.hideCustomNightMenu());
// AI滑块事件
this.epsteinSlider.addEventListener('input', (e) => {
this.epsteinValue.textContent = e.target.value;
});
this.trumpSlider.addEventListener('input', (e) => {
this.trumpValue.textContent = e.target.value;
});
this.hawkingSlider.addEventListener('input', (e) => {
this.hawkingValue.textContent = e.target.value;
});
// +/- 按钮事件
document.querySelectorAll('.ai-btn-minus').forEach(btn => {
btn.addEventListener('click', () => {
const aiName = btn.dataset.ai;
const slider = document.getElementById(`${aiName}-slider`);
const value = Math.max(0, parseInt(slider.value) - 1);
slider.value = value;
document.getElementById(`${aiName}-value`).textContent = value;
});
});
document.querySelectorAll('.ai-btn-plus').forEach(btn => {
btn.addEventListener('click', () => {
const aiName = btn.dataset.ai;
const slider = document.getElementById(`${aiName}-slider`);
const value = Math.min(20, parseInt(slider.value) + 1);
slider.value = value;
document.getElementById(`${aiName}-value`).textContent = value;
});
});
}
// 加载保存的进度
loadProgress() {
const savedNight = localStorage.getItem('fnae_current_night');
if (savedNight) {
const night = parseInt(savedNight);
if (night > 1 && night <= this.state.maxNights) {
this.state.currentNight = night;
return true;
}
}
return false;
}
// 保存进度
saveProgress() {
if (this.state.currentNight > 1) {
localStorage.setItem('fnae_current_night', this.state.currentNight.toString());
}
}
// 清除进度
clearProgress() {
localStorage.removeItem('fnae_current_night');
}
// 更新Continue按钮显示
updateContinueButton() {
if (this.loadProgress()) {
this.continueBtn.classList.remove('hidden');
this.continueBtn.textContent = `CONTINUE (NIGHT ${this.state.currentNight})`;
} else {
this.continueBtn.classList.add('hidden');
}
// 检查是否解锁特殊夜晚
const night6Unlocked = localStorage.getItem('night6Unlocked');
if (night6Unlocked === 'true') {
this.specialNightBtn.classList.remove('hidden');
this.starIcon.classList.remove('hidden');
} else {
this.specialNightBtn.classList.add('hidden');
this.starIcon.classList.add('hidden');
}
// 检查是否通关Night 6
const night6Completed = localStorage.getItem('night6Completed');
if (night6Completed === 'true') {
this.starIcon2.classList.remove('hidden');
this.customNightBtn.classList.remove('hidden'); // 通关Night 6后解锁Custom Night
} else {
this.starIcon2.classList.add('hidden');
this.customNightBtn.classList.add('hidden');
}
// 检查是否通关20/20/20 Custom Night
const customNight202020 = localStorage.getItem('customNight202020');
if (customNight202020 === 'true') {
this.starIcon3.classList.remove('hidden');
} else {
this.starIcon3.classList.add('hidden');
}
// 恢复到Night 1(不影响按钮显示)
this.state.currentNight = 1;
}
// 显示Custom Night菜单
showCustomNightMenu() {
this.mainMenu.classList.add('hidden');
this.customNightMenu.classList.remove('hidden');
}
// 隐藏Custom Night菜单
hideCustomNightMenu() {
this.customNightMenu.classList.add('hidden');
this.mainMenu.classList.remove('hidden');
}
// 开始Custom Night
async startCustomNight() {
const epsteinLevel = parseInt(this.epsteinSlider.value);
const trumpLevel = parseInt(this.trumpSlider.value);
const hawkingLevel = parseInt(this.hawkingSlider.value);
// 保存自定义AI等级到state
this.state.customNight = true;
this.state.currentNight = 7; // Custom Night = Night 7
this.state.customAILevels = {
epstein: epsteinLevel,
trump: trumpLevel,
hawking: hawkingLevel
};
console.log('Starting Custom Night with AI levels:', this.state.customAILevels);
this.customNightMenu.classList.add('hidden');
// 隐藏音量按钮和面板
if (this.volumeBtn) {
this.volumeBtn.classList.add('hidden');
}
if (this.volumePanel) {
this.volumePanel.classList.add('hidden');
}
const menuMusic = document.getElementById('menu-music');
if (menuMusic) {
menuMusic.pause();
menuMusic.currentTime = 0;
menuMusic.loop = false;
}
// 重置敌人AI状态
this.enemyAI.reset();
// 直接开始游戏
await this.initGame();
}
// Continue游戏(从保存的关卡开始)
async continueGame() {
if (this.loadProgress()) {
this.mainMenu.classList.add('hidden');
// 隐藏音量按钮和面板
if (this.volumeBtn) {
this.volumeBtn.classList.add('hidden');
}
if (this.volumePanel) {
this.volumePanel.classList.add('hidden');
}
const menuMusic = document.getElementById('menu-music');
if (menuMusic) {
menuMusic.pause();
menuMusic.currentTime = 0;
menuMusic.loop = false;
}
// 重置敌人AI状态
this.enemyAI.reset();
// 直接开始游戏,不播放过场动画
await this.initGame();
}
}
// 开始特殊夜晚(Night 6
async startSpecialNight() {
this.state.currentNight = 6; // 设置为Night 6
this.clearProgress(); // 清除普通进度
this.mainMenu.classList.add('hidden');
// 隐藏音量按钮和面板
if (this.volumeBtn) {
this.volumeBtn.classList.add('hidden');
}
if (this.volumePanel) {
this.volumePanel.classList.add('hidden');
}
const menuMusic = document.getElementById('menu-music');
if (menuMusic) {
menuMusic.pause();
menuMusic.currentTime = 0;
menuMusic.loop = false;
}
// 重置敌人AI状态
this.enemyAI.reset();
// 直接开始游戏,不播放过场动画
await this.initGame();
}
async startGame() {
// NEW GAME总是从Night 1开始
this.state.currentNight = 1;
this.clearProgress(); // 清除之前的进度
this.mainMenu.classList.add('hidden');
// 隐藏音量按钮和面板
if (this.volumeBtn) {
this.volumeBtn.classList.add('hidden');
}
if (this.volumePanel) {
this.volumePanel.classList.add('hidden');
}
const menuMusic = document.getElementById('menu-music');
if (menuMusic) {
menuMusic.pause();
menuMusic.currentTime = 0;
menuMusic.loop = false;
}
// 重置敌人AI状态
this.enemyAI.reset();
const cutscene = document.getElementById('cutscene');
cutscene.classList.remove('hidden');
// 触发淡入效果
setTimeout(() => {
cutscene.classList.add('fade-in');
}, 50);
let cutsceneEnded = false;
const endCutscene = () => {
if (cutsceneEnded) return;
cutsceneEnded = true;
// 淡出效果
cutscene.classList.remove('fade-in');
cutscene.classList.add('fade-out');
// 等待淡出完成后隐藏并开始游戏
setTimeout(() => {
cutscene.classList.add('hidden');
cutscene.classList.remove('fade-out');
// 不在这里显示游戏画面,让initGame处理
this.initGame();
}, 3000);
cutscene.removeEventListener('click', endCutscene);
if (autoEndTimeout) clearTimeout(autoEndTimeout);
};
// 点击跳过
cutscene.addEventListener('click', endCutscene);
// 3秒后自动开始淡出(总共6秒:3秒淡入 + 3秒淡出)
const autoEndTimeout = setTimeout(() => {
endCutscene();
}, 3000);
}
async initGame() {
// console.log('🎮 initGame called, currentNight:', this.state.currentNight);
if (!this.assets.loaded) {
await this.assets.loadAssets();
}
this.state.reset();
// console.log('🎮 After state.reset(), currentNight:', this.state.currentNight);
// 重置摄像头系统的sound按钮计数
this.camera.resetSoundButtonCount();
// 恢复摄像头面板的display(之前可能被强制隐藏)
const cameraPanel = document.getElementById('camera-panel');
if (cameraPanel) {
cameraPanel.style.display = ''; // 恢复默认
// console.log('🎮 Camera panel display restored');
}
// 显示每晚开始场景(在显示游戏画面之前)
await this.showNightIntro();
// console.log('🎮 After showNightIntro(), currentNight:', this.state.currentNight);
// 进场动画结束后才显示游戏画面
this.gameScreen.classList.add('active');
this.ui.currentSceneImg.src = this.assets.images.office.src;
this.ui.currentSceneImg.style.display = 'block';
this.viewPosition = 0.25;
this.ui.updateViewPosition(this.viewPosition);
this.ui.update();
this.ui.createHotspots();
// 初始化风扇状态(通风口默认打开,风扇快速旋转)
this.initVentFanAnimation();
this.startGameLoop();
this.startViewRotation();
// 前3个夜晚:等待指南关闭后再启动敌人 AI
// Night 4+:直接启动敌人 AI
if (this.state.currentNight > 3) {
this.enemyAI.start();
}
this.assets.playSound('vents', true);
// Show tutorial
if (this.state.currentNight === 1) {
this.showTutorial('night1');
} else if (this.state.currentNight === 2) {
this.showTutorial('night2');
} else if (this.state.currentNight === 3) {
this.showTutorial('night3');
}
// console.log('🎮 Before Golden check, currentNight:', this.state.currentNight);
// Night 5: 必定触发 Golden 霍金彩蛋(放在最后,确保游戏已完全初始化)
if (this.state.currentNight === 5) {
// console.log('🌟 Night 5 detected, triggering Golden Stephen...');
setTimeout(() => {
this.showGoldenStephen();
}, 1000); // 进入游戏1秒后触发
} // else {
// console.log('❌ Not Night 5, currentNight is:', this.state.currentNight);
// }
}
// 初始化风扇动画状态
initVentFanAnimation() {
const ventIcon = document.querySelector('.vent-icon');
if (ventIcon) {
if (this.state.ventsClosed) {
// 通风口关闭,风扇停止
ventIcon.classList.add('stopped');
ventIcon.style.animation = 'none';
} else {
// 通风口打开,风扇快速旋转
ventIcon.classList.remove('stopped', 'slowing', 'speeding-up');
ventIcon.style.animation = 'spin-fast 0.333s linear infinite';
}
}
}
showTutorial(type = 'night1') {
const tutorialContent = document.getElementById('tutorial-content');
if (!tutorialContent) return;
if (type === 'night2') {
// Night 2 教程:Trump
tutorialContent.innerHTML = `
<h2>DEFEND YOURSELF AGAINST TRUMP</h2>
<p>
TRUMP WILL TRY TO ATTACK YOU THROUGH THE VENTS IN CAM 1 AND CAM 2, SO IF YOU HEAR BANGING IN THE VENTS HEAD OVER TO THE CONTROL PANEL AND CLOSE THEM.
AFTER CLOSING THEM YOU WILL HEAR BANGING AGAIN AFTER A FEW SECONDS WHICH MEANS HE LEFT THE VENTS. YOU MUST OPEN THE VENTS OTHERWISE YOU WILL DIE FROM LACK OF OXYGEN.
TRUMP CAN BE LURED WITH THE AUDIOS BUT YOUR MAIN PRIORITY WITH THE AUDIO LURES SHOULD BE EPSTEIN.
</p>
<button id="tutorial-got-it">GOT IT</button>
`;
// 重新绑定按钮事件
const gotItBtn = document.getElementById('tutorial-got-it');
if (gotItBtn) {
gotItBtn.addEventListener('click', () => this.closeTutorial());
}
} else if (type === 'night3') {
// Night 3 教程:霍金
tutorialContent.innerHTML = `
<h2>DEFEND YOURSELF AGAINST STEPHEN HAWKING</h2>
<p>
STEPHEN HAWKING ALWAYS STAYS AT CAM 6 AND HE IS NOT AFFECTED BY THE AUDIO LURES.
ELECTROCUTE STEPHEN HAWKING EVERY ONCE IN A WHILE TO PREVENT HIM FROM LEAVING CAM 6.
</p>
<button id="tutorial-got-it">GOT IT</button>
`;
// 重新绑定按钮事件
const gotItBtn = document.getElementById('tutorial-got-it');
if (gotItBtn) {
gotItBtn.addEventListener('click', () => this.closeTutorial());
}
} else {
// Night 1 教程:EP
tutorialContent.innerHTML = `
<h2>DEFEND YOURSELF AGAINST EPSTEIN</h2>
<p>
EPSTEIN ALWAYS STARTS AT CAM 11. USE THE CAMERA'S AUDIO LURE TO KEEP EPSTEIN FAR AWAY FROM YOU.
MAKE SURE THE CAMERA YOU'RE PLAYING THE SOUND IN IS NEXT TO THE CAMERA WHERE EPSTEIN IS.
PLAYING SOUND IN ONLY ONE SPOT WILL NOT WORK IF YOU DO IT TWICE OR MORE IN A ROW.
USING THE AUDIO LURE TOO MUCH WILL LEAD TO THE CAMERAS BREAKING.
TO FIX THEM HEAD TO THE CONTROL PANEL AND RESTART THE CAMERAS LIKE YOU JUST DID.
EPSTEIN DOES NOT ATTACK THROUGH THE VENTS SO DON'T BOTHER CLOSING THEM FOR THIS NIGHT.
</p>
<button id="tutorial-got-it">GOT IT</button>
`;
// 重新绑定按钮事件
const gotItBtn = document.getElementById('tutorial-got-it');
if (gotItBtn) {
gotItBtn.addEventListener('click', () => this.closeTutorial());
}
}
this.tutorialOverlay.classList.remove('hidden');
// Mark tutorial as active (but don't pause game, allow view rotation)
this.state.tutorialActive = true;
}
closeTutorial() {
this.tutorialOverlay.classList.add('hidden');
// Close tutorial
this.state.tutorialActive = false;
// 前3个夜晚:关闭指南后启动敌人 AI
if (this.state.currentNight <= 3) {
console.log('🎮 Tutorial closed, starting enemy AI...');
this.enemyAI.start();
}
}
// Golden 霍金彩蛋效果
showGoldenStephen() {
console.log('🌟 Golden Stephen Hawking appears!');
// 创建全屏金色霍金图层
const goldenOverlay = document.createElement('div');
goldenOverlay.id = 'golden-stephen-overlay';
goldenOverlay.style.position = 'fixed';
goldenOverlay.style.top = '0';
goldenOverlay.style.left = '0';
goldenOverlay.style.width = '100%';
goldenOverlay.style.height = '100%';
goldenOverlay.style.zIndex = '9999';
goldenOverlay.style.pointerEvents = 'none';
goldenOverlay.style.background = 'rgba(0, 0, 0, 0.3)';
// 创建金色霍金图片
const goldenImg = document.createElement('img');
goldenImg.src = 'assets/images/goldenstephen.png';
goldenImg.style.position = 'absolute';
goldenImg.style.top = '50%';
goldenImg.style.left = '50%';
goldenImg.style.transform = 'translate(-50%, -50%)';
goldenImg.style.width = '80%';
goldenImg.style.height = '80%';
goldenImg.style.objectFit = 'contain';
goldenImg.style.opacity = '0';
goldenImg.style.animation = 'golden-flicker 2s ease-in-out';
goldenOverlay.appendChild(goldenImg);
document.body.appendChild(goldenOverlay);
// 播放音效
this.assets.playSound('goldenstephenscare', false, 1.0);
// 2秒后移除
setTimeout(() => {
goldenOverlay.remove();
}, 2000);
}
showNightIntro() {
return new Promise((resolve) => {
const nightIntro = document.getElementById('night-intro');
const nightIntroText = document.getElementById('night-intro-text');
// Update night number text
if (this.state.customNight && this.state.currentNight === 7) {
nightIntroText.textContent = 'CUSTOM NIGHT';
} else {
nightIntroText.textContent = `NIGHT ${this.state.currentNight}`;
}
// Show scene
nightIntro.classList.remove('hidden');
// Fade in effect (1.5s)
setTimeout(() => {
nightIntro.classList.add('fade-in');
}, 50);
// 1.5s fade in + 2s display then start fade out
setTimeout(() => {
nightIntro.classList.remove('fade-in');
nightIntro.classList.add('fade-out');
// After 1.5s fade out complete, hide and continue game
setTimeout(() => {
nightIntro.classList.add('hidden');
nightIntro.classList.remove('fade-out');
resolve();
}, 1500);
}, 3500); // 1500ms fade in + 2000ms display
});
}
startViewRotation() {
const rotationLoop = () => {
if (!this.state.isGameRunning) return;
// If control panel or camera is open, disable rotation
if (!this.state.controlPanelOpen && !this.state.cameraOpen) {
if (this.isRotatingLeft && this.viewPosition > 0) {
this.viewPosition -= this.rotationSpeed;
this.viewPosition = Math.max(0, this.viewPosition);
this.ui.updateViewPosition(this.viewPosition);
}
if (this.isRotatingRight && this.viewPosition < 1) {
this.viewPosition += this.rotationSpeed;
this.viewPosition = Math.min(1, this.viewPosition);
this.ui.updateViewPosition(this.viewPosition);
}
}
requestAnimationFrame(rotationLoop);
};
rotationLoop();
}
startGameLoop() {
this.timeInterval = setInterval(() => {
// 前3个夜晚:如果指南打开,暂停时间
if (this.state.currentNight <= 3 && this.state.tutorialActive) {
return; // 跳过时间更新
}
this.state.currentTime += 1;
this.ui.update();
if (this.state.currentTime >= 6) {
this.winNight();
}
}, 60000);
this.powerInterval = setInterval(() => {
this.updatePower();
}, 1000);
}
updatePower() {
// 前3个夜晚:如果指南打开,暂停氧气消耗
if (this.state.currentNight <= 3 && this.state.tutorialActive) {
return; // 跳过氧气更新
}
if (this.state.ventsClosed) {
// When vents closed, oxygen decreases (faster speed)
this.state.oxygen -= 1.5;
} else {
// When vents open, oxygen quickly recovers to 100%
if (this.state.oxygen < 100) {
this.state.oxygen += 2;
}
}
this.state.oxygen = Math.max(0, Math.min(100, this.state.oxygen));
if (this.state.oxygen <= 0) {
this.oxygenOut();
}
this.ui.update();
}
toggleVents() {
console.log('toggleVents called, controlPanelBusy:', this.state.controlPanelBusy);
// 如果控制面板正忙,不允许操作
if (this.state.controlPanelBusy) {
console.log('Control panel is busy, please wait...');
return;
}
// 标记控制面板为忙碌状态
this.state.controlPanelBusy = true;
this.state.ventsToggling = true;
console.log('Starting vent toggle animation...');
// 播放心电图音效
this.assets.playSound('ekg', false, 0.8);
// 获取风扇图标
const ventIcon = document.querySelector('.vent-icon');
if (this.state.ventsClosed) {
// 当前关闭,要打开 -> 风扇从停止加速到快速
console.log('Opening vents: fan speeding up');
if (ventIcon) {
ventIcon.classList.remove('stopped', 'slowing');
ventIcon.classList.add('speeding-up');
// 逐步加速动画
setTimeout(() => {
ventIcon.style.animation = 'spin-slow 2s linear infinite';
}, 0);
setTimeout(() => {
ventIcon.style.animation = 'spin-slow 1.5s linear infinite';
}, 1000);
setTimeout(() => {
ventIcon.style.animation = 'spin-fast 0.333s linear infinite';
ventIcon.classList.remove('speeding-up');
}, 2000);
}
} else {
// 当前打开,要关闭 -> 风扇从快速减速到停止
console.log('Closing vents: fan slowing down');
if (ventIcon) {
ventIcon.classList.remove('speeding-up');
ventIcon.classList.add('slowing');
// 逐步减速动画
setTimeout(() => {
ventIcon.style.animation = 'spin-slow 1.5s linear infinite';
}, 0);
setTimeout(() => {
ventIcon.style.animation = 'spin-slow 2s linear infinite';
}, 1000);
setTimeout(() => {
ventIcon.style.animation = 'spin-slow 3s linear infinite';
}, 2000);
setTimeout(() => {
ventIcon.style.animation = 'none';
ventIcon.classList.remove('slowing');
ventIcon.classList.add('stopped');
}, 3000);
}
}
// 更新UI显示点动画
this.ui.updateVentsStatus();
// 启动定时更新(每100ms更新一次UI)
const updateInterval = setInterval(() => {
this.ui.updateVentsStatus();
if (!this.state.ventsToggling) {
clearInterval(updateInterval);
}
}, 100);
// 4秒后完成切换
setTimeout(() => {
this.state.ventsClosed = !this.state.ventsClosed;
console.log('Vents:', this.state.ventsClosed ? 'closed' : 'open');
// 通知 EnemyAI 通风口状态变化
this.enemyAI.onVentsChanged(this.state.ventsClosed);
// 解除锁定
this.state.ventsToggling = false;
this.state.controlPanelBusy = false;
console.log('Vent toggle completed');
// 更新UI和控制面板选项文本
this.ui.update();
this.ui.updateVentsStatus();
this.ui.updateControlPanelOptions();
}, 4000);
}
toggleCamera() {
// console.log('🎮 Game.toggleCamera() called');
// console.log('🎮 Current state - cameraOpen:', this.state.cameraOpen, 'tutorialActive:', this.state.tutorialActive);
this.camera.toggle();
}
oxygenOut() {
this.stopGame();
this.assets.stopSound('ambient');
// Oxygen depleted triggers jumpscare
this.enemyAI.triggerJumpscare();
}
gameOver(message) {
this.stopGame();
this.assets.stopSound('ambient');
// 立即隐藏游戏画面
this.gameScreen.classList.remove('active');
// 关闭摄像头面板
if (this.state.cameraOpen) {
this.camera.close();
}
// 隐藏摄像头面板
const cameraPanel = document.getElementById('camera-panel');
if (cameraPanel) {
cameraPanel.classList.add('hidden');
cameraPanel.classList.remove('show');
}
// 清理角色图层
const characterOverlay = document.getElementById('character-overlay');
if (characterOverlay) {
characterOverlay.innerHTML = '';
}
// 隐藏控制面板
const controlPanel = document.getElementById('control-panel');
if (controlPanel) {
controlPanel.classList.add('hidden');
}
this.gameOverScreen(message);
}
winNight() {
this.stopGame();
this.assets.stopSound('ambient');
// 关闭摄像头(如果打开)
if (this.state.cameraOpen) {
this.camera.close();
}
// 强制隐藏摄像头面板,防止闪现
const cameraPanel = document.getElementById('camera-panel');
if (cameraPanel) {
cameraPanel.classList.add('hidden');
cameraPanel.classList.remove('show', 'closing');
cameraPanel.style.display = 'none'; // 强制隐藏
}
// 立即隐藏游戏画面,防止闪烁
this.gameScreen.classList.remove('active');
// 检查是否通关20/20/20 Custom Night
if (this.state.customNight && this.state.currentNight === 7) {
const levels = this.state.customAILevels;
if (levels.epstein === 20 && levels.trump === 20 && levels.hawking === 20) {
console.log('🌟 20/20/20 Custom Night completed!');
localStorage.setItem('customNight202020', 'true');
}
}
// 如果是 Night 6,播放特殊的胜利动画并标记完成
if (this.state.currentNight === 6) {
localStorage.setItem('night6Completed', 'true');
this.playNight6VictoryAnimation();
} else if (this.state.currentNight === 5) {
// Night 5,播放特殊的胜利动画
this.playNight5VictoryAnimation();
} else {
// 其他关卡播放普通的夜晚结束动画
this.playNightEndAnimation();
}
}
// Night 5 特殊胜利动画
playNight5VictoryAnimation() {
// 创建全屏动画容器
const animationContainer = document.createElement('div');
animationContainer.style.position = 'fixed';
animationContainer.style.top = '0';
animationContainer.style.left = '0';
animationContainer.style.width = '100%';
animationContainer.style.height = '100%';
animationContainer.style.backgroundColor = '#000';
animationContainer.style.display = 'flex';
animationContainer.style.alignItems = 'center';
animationContainer.style.justifyContent = 'center';
animationContainer.style.zIndex = '10000';
animationContainer.style.opacity = '0';
animationContainer.style.transition = 'opacity 0.5s';
// 创建时间显示
const timeDisplay = document.createElement('div');
timeDisplay.style.fontSize = '10vw';
timeDisplay.style.fontWeight = 'bold';
timeDisplay.style.color = '#fff';
timeDisplay.style.fontFamily = 'Arial, sans-serif';
timeDisplay.textContent = '5:59 AM';
animationContainer.appendChild(timeDisplay);
document.body.appendChild(animationContainer);
// 淡入
setTimeout(() => {
animationContainer.style.opacity = '1';
}, 50);
// 1秒后变为 6:00 AM 并播放钟声
setTimeout(() => {
timeDisplay.textContent = '6:00 AM';
this.assets.playSound('chimes', false, 1.0);
}, 1000);
// 3秒后淡出时间
setTimeout(() => {
timeDisplay.style.transition = 'opacity 0.5s';
timeDisplay.style.opacity = '0';
setTimeout(() => {
// 移除时间显示
animationContainer.removeChild(timeDisplay);
// 创建 "RESCUE ARRIVE" 文字
const rescueText = document.createElement('div');
rescueText.style.fontSize = '8vw';
rescueText.style.fontWeight = 'bold';
rescueText.style.color = '#0f0'; // 绿色,表示救援
rescueText.style.fontFamily = 'Arial, sans-serif';
rescueText.style.textAlign = 'center';
rescueText.style.opacity = '0';
rescueText.style.transition = 'opacity 1s';
rescueText.textContent = 'RESCUE ARRIVE';
animationContainer.appendChild(rescueText);
// 淡入 "RESCUE ARRIVE"
setTimeout(() => {
rescueText.style.opacity = '1';
}, 50);
// 2秒后淡出 "RESCUE ARRIVE",显示胜利画面
setTimeout(() => {
rescueText.style.opacity = '0';
setTimeout(() => {
// 移除 "RESCUE ARRIVE" 文字
animationContainer.removeChild(rescueText);
// 创建胜利画面
const winScreen = document.createElement('img');
winScreen.src = 'assets/images/winscreen.png';
winScreen.style.width = '100%';
winScreen.style.height = '100%';
winScreen.style.objectFit = 'contain';
winScreen.style.opacity = '0';
winScreen.style.transition = 'opacity 1s';
animationContainer.appendChild(winScreen);
// 播放胜利音乐
this.assets.playSound('win', false, 1.0);
// 淡入胜利画面
setTimeout(() => {
winScreen.style.opacity = '1';
}, 50);
// 5秒后淡出并返回主菜单
setTimeout(() => {
animationContainer.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(animationContainer);
// Night 5 通关后,解锁 Night 6Special Night
localStorage.setItem('night6Unlocked', 'true');
// 返回主菜单
this.clearProgress();
this.showMainMenu();
}, 500);
}, 5000); // 显示5秒
}, 1000); // "RESCUE ARRIVE" 淡出1秒
}, 2000); // 显示 "RESCUE ARRIVE" 2秒
}, 500); // 时间淡出0.5秒
}, 3000); // 显示 "6:00 AM" 2秒
}
// Night 6 特殊胜利动画
playNight6VictoryAnimation() {
// 创建全屏动画容器
const animationContainer = document.createElement('div');
animationContainer.style.position = 'fixed';
animationContainer.style.top = '0';
animationContainer.style.left = '0';
animationContainer.style.width = '100%';
animationContainer.style.height = '100%';
animationContainer.style.backgroundColor = '#000';
animationContainer.style.display = 'flex';
animationContainer.style.alignItems = 'center';
animationContainer.style.justifyContent = 'center';
animationContainer.style.zIndex = '10000';
animationContainer.style.opacity = '0';
animationContainer.style.transition = 'opacity 0.5s';
// 创建时间显示
const timeDisplay = document.createElement('div');
timeDisplay.style.fontSize = '10vw';
timeDisplay.style.fontWeight = 'bold';
timeDisplay.style.color = '#fff';
timeDisplay.style.fontFamily = 'Arial, sans-serif';
timeDisplay.textContent = '5:59 AM';
animationContainer.appendChild(timeDisplay);
document.body.appendChild(animationContainer);
// 淡入
setTimeout(() => {
animationContainer.style.opacity = '1';
}, 50);
// 1秒后变为 6:00 AM 并播放钟声
setTimeout(() => {
timeDisplay.textContent = '6:00 AM';
this.assets.playSound('chimes', false, 1.0);
}, 1000);
// 3秒后淡出时间,显示night6.png图片
setTimeout(() => {
timeDisplay.style.transition = 'opacity 0.5s';
timeDisplay.style.opacity = '0';
setTimeout(() => {
// 移除时间显示
animationContainer.removeChild(timeDisplay);
// 创建night6.png图片
const night6Image = document.createElement('img');
night6Image.src = 'assets/images/night6.png';
night6Image.style.width = '100%';
night6Image.style.height = '100%';
night6Image.style.objectFit = 'contain';
night6Image.style.opacity = '0';
night6Image.style.transition = 'opacity 1s';
animationContainer.appendChild(night6Image);
// 播放goldenstephenscare.ogg音乐
this.assets.playSound('goldenstephenscare', false, 1.0);
// 淡入图片
setTimeout(() => {
night6Image.style.opacity = '1';
}, 50);
// 5秒后淡出并返回主菜单
setTimeout(() => {
animationContainer.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(animationContainer);
// 返回主菜单
this.showMainMenu();
}, 500);
}, 5000);
}, 500);
}, 3000);
}
// Night end animation: 5:59AM -> 6:00AM -> Days until rescue
playNightEndAnimation() {
// Create fullscreen animation container
const animationContainer = document.createElement('div');
animationContainer.style.position = 'fixed';
animationContainer.style.top = '0';
animationContainer.style.left = '0';
animationContainer.style.width = '100%';
animationContainer.style.height = '100%';
animationContainer.style.backgroundColor = '#000';
animationContainer.style.display = 'flex';
animationContainer.style.alignItems = 'center';
animationContainer.style.justifyContent = 'center';
animationContainer.style.zIndex = '10000';
animationContainer.style.opacity = '0';
animationContainer.style.transition = 'opacity 0.5s';
// 创建时间显示
const timeDisplay = document.createElement('div');
timeDisplay.style.fontSize = '10vw';
timeDisplay.style.fontWeight = 'bold';
timeDisplay.style.color = '#fff';
timeDisplay.style.fontFamily = 'Arial, sans-serif';
timeDisplay.textContent = '5:59 AM';
animationContainer.appendChild(timeDisplay);
document.body.appendChild(animationContainer);
// Fade in
setTimeout(() => {
animationContainer.style.opacity = '1';
}, 50);
// After 1s change to 6:00AM and play sound effect
setTimeout(() => {
timeDisplay.textContent = '6:00 AM';
this.assets.playSound('chimes', false, 1.0);
}, 1000);
// After 2s more, show message
setTimeout(() => {
// 淡出时间(不改变容器透明度,保持黑色背景)
timeDisplay.style.transition = 'opacity 0.5s';
timeDisplay.style.opacity = '0';
setTimeout(() => {
// Custom Night 通关显示
if (this.state.customNight && this.state.currentNight === 7) {
timeDisplay.textContent = 'CUSTOM NIGHT COMPLETE';
timeDisplay.style.fontSize = '5vw';
timeDisplay.style.color = '#0f0'; // 绿色表示完成
}
// 如果还有下一关,显示剩余天数(故事设定是5晚,所以总是显示5-当前关卡)
else if (this.state.currentNight < this.state.maxNights) {
const daysRemaining = 5 - this.state.currentNight; // 固定按5晚计算
timeDisplay.textContent = `${daysRemaining} ${daysRemaining === 1 ? 'day' : 'days'} until rescue`;
timeDisplay.style.fontSize = '5vw';
timeDisplay.style.color = '#fff';
} else {
// 所有关卡完成,显示TO BE CONTINUED
timeDisplay.innerHTML = 'TO BE CONTINUED...<br><span style="font-size: 3vw; color: #f00;">Web version port in progress</span>';
timeDisplay.style.fontSize = '5vw';
timeDisplay.style.color = '#fff';
}
timeDisplay.style.opacity = '1';
}, 500);
// 再过3秒后淡出
setTimeout(() => {
animationContainer.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(animationContainer);
// Custom Night 通关后返回主菜单
if (this.state.customNight && this.state.currentNight === 7) {
this.showMainMenu();
}
// 如果还有下一关,直接进入下一关
else if (this.state.currentNight < this.state.maxNights) {
this.state.currentNight++;
this.continueToNextNight();
} else {
// 所有关卡完成,清除进度并返回主菜单
this.clearProgress();
this.showMainMenu();
}
}, 500);
}, 3000); // 改为3秒,让玩家有时间看消息
}, 3000);
}
gameOverScreen(message, win = false) {
this.gameOverText.textContent = message;
const subtitle = document.getElementById('game-over-subtitle');
const gameOverStatic = document.getElementById('game-over-static');
const restartBtn = document.getElementById('restart');
const mainMenuBtn = document.getElementById('main-menu-btn');
// 隐藏按钮
if (restartBtn) restartBtn.style.display = 'none';
if (mainMenuBtn) mainMenuBtn.style.display = 'none';
// Play static video
if (gameOverStatic) {
gameOverStatic.currentTime = 0;
gameOverStatic.play().catch(e => console.log('Failed to play game over static:', e));
}
if (win) {
// Only increase night number if not at max level
if (this.state.currentNight < this.state.maxNights) {
this.state.currentNight++;
// Hide subtitle, will continue to next night
subtitle.classList.add('hidden');
// Show game over screen
this.gameOverElement.classList.remove('hidden');
// Auto continue to next night after 3 seconds
setTimeout(() => {
// 隐藏游戏结束画面
this.gameOverElement.classList.add('hidden');
this.gameScreen.classList.remove('active');
// 停止静态视频
if (gameOverStatic) {
gameOverStatic.pause();
gameOverStatic.currentTime = 0;
}
this.continueToNextNight();
}, 3000);
} else {
// All available levels completed, show "to be continued" message
subtitle.textContent = 'TO BE CONTINUED... (Web version port in progress)';
subtitle.classList.remove('hidden');
this.gameOverElement.classList.remove('hidden');
// 3秒后自动返回主菜单
setTimeout(() => {
this.gameOverElement.classList.add('hidden');
this.showMainMenu();
}, 3000);
}
} else {
// On failure hide subtitle
subtitle.classList.add('hidden');
this.gameOverElement.classList.remove('hidden');
// 保存进度(如果在Night 2或更高关卡死亡)
this.saveProgress();
// 3秒后自动返回主菜单
setTimeout(() => {
this.gameOverElement.classList.add('hidden');
this.showMainMenu();
}, 3000);
}
}
// Continue to next night (without cutscene)
async continueToNextNight() {
if (!this.assets.loaded) {
await this.assets.loadAssets();
}
this.state.reset();
this.enemyAI.reset(); // 重置敌人AI状态
// 重置摄像头系统的sound按钮计数
this.camera.resetSoundButtonCount();
// 恢复摄像头面板的display(之前被强制隐藏)
const cameraPanel = document.getElementById('camera-panel');
if (cameraPanel) {
cameraPanel.style.display = ''; // 恢复默认
}
// 显示每晚开始场景
await this.showNightIntro();
// 进场动画结束后才显示游戏画面
this.gameScreen.classList.add('active');
this.ui.currentSceneImg.src = this.assets.images.office.src;
this.ui.currentSceneImg.style.display = 'block';
this.viewPosition = 0.25;
this.ui.updateViewPosition(this.viewPosition);
this.ui.update();
this.ui.createHotspots();
// 初始化风扇状态
this.initVentFanAnimation();
this.startGameLoop();
this.startViewRotation();
// 前3个夜晚:等待指南关闭后再启动敌人 AI
// Night 4+:直接启动敌人 AI
if (this.state.currentNight > 3) {
this.enemyAI.start();
}
this.assets.playSound('vents', true);
// Show tutorial for specific nights
if (this.state.currentNight === 2) {
this.showTutorial('night2');
} else if (this.state.currentNight === 3) {
this.showTutorial('night3');
}
// Night 5: 必定触发 Golden 霍金彩蛋
if (this.state.currentNight === 5) {
console.log('🌟 Night 5 detected (continueToNextNight), triggering Golden Stephen...');
setTimeout(() => {
this.showGoldenStephen();
}, 1000);
}
}
stopGame() {
this.state.isGameRunning = false;
clearInterval(this.timeInterval);
clearInterval(this.powerInterval);
this.enemyAI.stop();
}
restartGame() {
this.gameOverElement.classList.add('hidden');
// Hide game screen, prepare to restart
this.gameScreen.classList.remove('active');
// 如果是Custom Night,重新开始Custom Night
if (this.state.customNight && this.state.currentNight === 7) {
this.startCustomNight();
} else {
this.startGame();
}
}
showMainMenu() {
this.gameOverElement.classList.add('hidden');
this.gameScreen.classList.remove('active');
// 显示音量按钮
if (this.volumeBtn) {
this.volumeBtn.classList.remove('hidden');
}
// 关闭摄像头面板
if (this.state.cameraOpen) {
this.camera.close();
}
// 隐藏摄像头面板
const cameraPanel = document.getElementById('camera-panel');
if (cameraPanel) {
cameraPanel.classList.add('hidden');
cameraPanel.classList.remove('show');
}
// 清理角色图层
const characterOverlay = document.getElementById('character-overlay');
if (characterOverlay) {
characterOverlay.innerHTML = '';
}
// 隐藏控制面板
const controlPanel = document.getElementById('control-panel');
if (controlPanel) {
controlPanel.classList.add('hidden');
}
this.mainMenu.classList.remove('hidden');
this.stopGame();
// 更新Continue按钮显示
this.updateContinueButton();
this.assets.stopSound('vents');
this.assets.stopSound('static');
this.assets.stopSound('staticLoop');
this.assets.stopSound('ventCrawling');
const menuMusic = document.getElementById('menu-music');
if (menuMusic) {
menuMusic.loop = true;
menuMusic.currentTime = 0;
menuMusic.play().catch(e => console.log('Menu music playback failed:', e));
}
}
}