diff --git a/config.jsonc b/config.jsonc
index 2dc56e0e..6ca68ec0 100644
--- a/config.jsonc
+++ b/config.jsonc
@@ -391,6 +391,11 @@
],
"categories": []
},
+ "Five Nights at Epstein's": {
+ "path": "five-nights-at-epsteins",
+ "aliases": [],
+ "categories": []
+ },
"Flash Chess": {
"path": "flash/?game=flash-chess",
"aliases": [],
diff --git a/games/five-nights-at-epsteins/assets/images/Cam1.png b/games/five-nights-at-epsteins/assets/images/Cam1.png
new file mode 100644
index 00000000..b13d163e
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam1.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/Cam10.png b/games/five-nights-at-epsteins/assets/images/Cam10.png
new file mode 100644
index 00000000..409dab28
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam10.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/Cam11.png b/games/five-nights-at-epsteins/assets/images/Cam11.png
new file mode 100644
index 00000000..7f9fb5c0
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam11.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/Cam2.png b/games/five-nights-at-epsteins/assets/images/Cam2.png
new file mode 100644
index 00000000..2cf59fa3
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam2.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/Cam3.png b/games/five-nights-at-epsteins/assets/images/Cam3.png
new file mode 100644
index 00000000..6de24844
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam3.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/Cam4.png b/games/five-nights-at-epsteins/assets/images/Cam4.png
new file mode 100644
index 00000000..bbbb0303
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam4.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/Cam5.png b/games/five-nights-at-epsteins/assets/images/Cam5.png
new file mode 100644
index 00000000..b63bb164
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam5.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/Cam6.png b/games/five-nights-at-epsteins/assets/images/Cam6.png
new file mode 100644
index 00000000..0b5e8c9b
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam6.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/Cam7.png b/games/five-nights-at-epsteins/assets/images/Cam7.png
new file mode 100644
index 00000000..15ff79c3
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam7.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/Cam8.png b/games/five-nights-at-epsteins/assets/images/Cam8.png
new file mode 100644
index 00000000..43f6d908
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam8.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/Cam9.png b/games/five-nights-at-epsteins/assets/images/Cam9.png
new file mode 100644
index 00000000..9f63f8b1
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/Cam9.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/FNAE-Map-layout.png b/games/five-nights-at-epsteins/assets/images/FNAE-Map-layout.png
new file mode 100644
index 00000000..b79c3892
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/FNAE-Map-layout.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/cutscene.png b/games/five-nights-at-epsteins/assets/images/cutscene.png
new file mode 100644
index 00000000..840294b8
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/cutscene.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/enemyep1.png b/games/five-nights-at-epsteins/assets/images/enemyep1.png
new file mode 100644
index 00000000..c87943c6
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/enemyep1.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/enemyep4.png b/games/five-nights-at-epsteins/assets/images/enemyep4.png
new file mode 100644
index 00000000..a3485d34
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/enemyep4.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/ep1.png b/games/five-nights-at-epsteins/assets/images/ep1.png
new file mode 100644
index 00000000..85883dc4
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/ep1.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/ep4.png b/games/five-nights-at-epsteins/assets/images/ep4.png
new file mode 100644
index 00000000..ddce0dfb
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/ep4.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/fa3.png b/games/five-nights-at-epsteins/assets/images/fa3.png
new file mode 100644
index 00000000..1a21a199
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/fa3.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/goldenstephen.png b/games/five-nights-at-epsteins/assets/images/goldenstephen.png
new file mode 100644
index 00000000..bc4e4fa4
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/goldenstephen.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/jump.png b/games/five-nights-at-epsteins/assets/images/jump.png
new file mode 100644
index 00000000..6462f3e6
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/jump.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/jumptrump.png b/games/five-nights-at-epsteins/assets/images/jumptrump.png
new file mode 100644
index 00000000..77251281
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/jumptrump.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/menubackground.png b/games/five-nights-at-epsteins/assets/images/menubackground.png
new file mode 100644
index 00000000..9289856e
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/menubackground.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/original.png b/games/five-nights-at-epsteins/assets/images/original.png
new file mode 100644
index 00000000..42d7fd36
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/original.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/scaryep.png b/games/five-nights-at-epsteins/assets/images/scaryep.png
new file mode 100644
index 00000000..3f8c79b4
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/scaryep.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/scaryhawk.png b/games/five-nights-at-epsteins/assets/images/scaryhawk.png
new file mode 100644
index 00000000..35bb35a2
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/scaryhawk.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/scaryhawking.png b/games/five-nights-at-epsteins/assets/images/scaryhawking.png
new file mode 100644
index 00000000..eb774cbd
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/scaryhawking.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/scarytrump.png b/games/five-nights-at-epsteins/assets/images/scarytrump.png
new file mode 100644
index 00000000..11690856
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/scarytrump.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/star.png b/games/five-nights-at-epsteins/assets/images/star.png
new file mode 100644
index 00000000..134ce2d3
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/star.png differ
diff --git a/games/five-nights-at-epsteins/assets/images/winscreen.png b/games/five-nights-at-epsteins/assets/images/winscreen.png
new file mode 100644
index 00000000..3fd88d1f
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/images/winscreen.png differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/1.ogg b/games/five-nights-at-epsteins/assets/sounds/1.ogg
new file mode 100644
index 00000000..365ed27d
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/1.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/2.ogg b/games/five-nights-at-epsteins/assets/sounds/2.ogg
new file mode 100644
index 00000000..e6b42729
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/2.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/Blip.ogg b/games/five-nights-at-epsteins/assets/sounds/Blip.ogg
new file mode 100644
index 00000000..d15412a0
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/Blip.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/Crank1.ogg b/games/five-nights-at-epsteins/assets/sounds/Crank1.ogg
new file mode 100644
index 00000000..8226aa99
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/Crank1.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/Crank2.ogg b/games/five-nights-at-epsteins/assets/sounds/Crank2.ogg
new file mode 100644
index 00000000..98e7de24
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/Crank2.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/Static_sound.ogg b/games/five-nights-at-epsteins/assets/sounds/Static_sound.ogg
new file mode 100644
index 00000000..4e314d8a
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/Static_sound.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/chimes.ogg b/games/five-nights-at-epsteins/assets/sounds/chimes.ogg
new file mode 100644
index 00000000..0dafa375
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/chimes.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/ekg.wav b/games/five-nights-at-epsteins/assets/sounds/ekg.wav
new file mode 100644
index 00000000..93f3e64d
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/ekg.wav differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/goldenstephenscare.ogg b/games/five-nights-at-epsteins/assets/sounds/goldenstephenscare.ogg
new file mode 100644
index 00000000..85071a36
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/goldenstephenscare.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/hawking_shock.wav b/games/five-nights-at-epsteins/assets/sounds/hawking_shock.wav
new file mode 100644
index 00000000..dcf2d9b7
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/hawking_shock.wav differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/jumpcare.ogg b/games/five-nights-at-epsteins/assets/sounds/jumpcare.ogg
new file mode 100644
index 00000000..8d490ec8
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/jumpcare.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/music.ogg b/games/five-nights-at-epsteins/assets/sounds/music.ogg
new file mode 100644
index 00000000..a5c18f23
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/music.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/music3.ogg b/games/five-nights-at-epsteins/assets/sounds/music3.ogg
new file mode 100644
index 00000000..afeab609
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/music3.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/stephenjumpscare.ogg b/games/five-nights-at-epsteins/assets/sounds/stephenjumpscare.ogg
new file mode 100644
index 00000000..0e2cc5c3
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/stephenjumpscare.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/vent-crawling.mp3 b/games/five-nights-at-epsteins/assets/sounds/vent-crawling.mp3
new file mode 100644
index 00000000..2c4c20ff
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/vent-crawling.mp3 differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/vents.ogg b/games/five-nights-at-epsteins/assets/sounds/vents.ogg
new file mode 100644
index 00000000..3214eea4
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/vents.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/sounds/winmusic.ogg b/games/five-nights-at-epsteins/assets/sounds/winmusic.ogg
new file mode 100644
index 00000000..ea175e72
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/sounds/winmusic.ogg differ
diff --git a/games/five-nights-at-epsteins/assets/vedio/202512191935.webm b/games/five-nights-at-epsteins/assets/vedio/202512191935.webm
new file mode 100644
index 00000000..a084d954
Binary files /dev/null and b/games/five-nights-at-epsteins/assets/vedio/202512191935.webm differ
diff --git a/games/five-nights-at-epsteins/favicon.ico b/games/five-nights-at-epsteins/favicon.ico
new file mode 100644
index 00000000..718d6fea
Binary files /dev/null and b/games/five-nights-at-epsteins/favicon.ico differ
diff --git a/games/five-nights-at-epsteins/index.html b/games/five-nights-at-epsteins/index.html
new file mode 100644
index 00000000..7908bfb1
--- /dev/null
+++ b/games/five-nights-at-epsteins/index.html
@@ -0,0 +1,246 @@
+
+
+
+
+
+
+ Five Nights At Epstein's - Web Version
+
+
+
+
+
+
+
+
+
FIVE NIGHTS AT EPSTEIN'S
+
+
LOADING...
+
+
0%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
100 % O2
+
+
+
+
+
+
+
+
CAM 11
+
ERR
+
+
+
+
ELECTROCUTE
+
PLAY SOUND
+
+
+
+
+
+
+
+
+
+
Click to continue...
+
+
+
+
+
+
+
+
+
+
+
+
FIVE NIGHTS AT EPSTEIN'S
+
NEW GAME
+
CONTINUE
+
EPSTEIN'S SPECIAL NIGHT
+
CUSTOM NIGHT
+
+
v1.2.3
+
+
+
+
SOUND SETTINGS
+
+
+
+
VOLUME SETTINGS
+
+
+
Master Volume
+
+
+ 70%
+
+
+
+
+
Game Background Music
+
+
+ 70%
+
+
+
+
+
Menu Music
+
+
+ 70%
+
+
+
+
+
Jumpscare Sounds
+
+
+ 70%
+
+
+
+
+
Trump Vent Crawling
+
+
+ 70%
+
+
+
+
CLOSE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Restart
+
Main Menu
+
+
+
+
+
+
+
DEFEND YOURSELF AGAINST EPSTEIN
+
+ 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.
+
+
GOT IT
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/games/five-nights-at-epsteins/js/AssetManager.js b/games/five-nights-at-epsteins/js/AssetManager.js
new file mode 100644
index 00000000..71f255c8
--- /dev/null
+++ b/games/five-nights-at-epsteins/js/AssetManager.js
@@ -0,0 +1,169 @@
+// 资源管理器
+class AssetManager {
+ constructor() {
+ this.images = {};
+ this.sounds = {};
+ this.loaded = false;
+
+ // 分类音量设置
+ this.volumeSettings = this.loadVolumeSettings();
+ }
+
+ // 从 localStorage 加载音量设置
+ loadVolumeSettings() {
+ const saved = localStorage.getItem('fnae_volume_settings');
+ if (saved) {
+ return JSON.parse(saved);
+ }
+ // 默认音量设置
+ return {
+ master: 0.7,
+ gameBg: 0.7,
+ menuMusic: 0.7,
+ jumpscare: 0.7,
+ ventCrawling: 0.7
+ };
+ }
+
+ // 保存音量设置
+ saveVolumeSettings() {
+ localStorage.setItem('fnae_volume_settings', JSON.stringify(this.volumeSettings));
+ }
+
+ // 设置特定类型的音量
+ setVolume(type, volume) {
+ this.volumeSettings[type] = Math.max(0, Math.min(1, volume));
+ this.saveVolumeSettings();
+ }
+
+ // 获取特定类型的音量
+ getVolume(type) {
+ return this.volumeSettings[type] || 0.7;
+ }
+
+ // 获取所有音量设置
+ getAllVolumes() {
+ return this.volumeSettings;
+ }
+
+ async loadAssets() {
+ // 获取当前脚本的基础路径
+ const basePath = './';
+
+ // 从 Unity 提取的资源
+ const imagePaths = {
+ office: `${basePath}assets/images/original.png`,
+ cam1: `${basePath}assets/images/Cam1.png`,
+ cam2: `${basePath}assets/images/Cam2.png`,
+ cam3: `${basePath}assets/images/Cam3.png`,
+ cam4: `${basePath}assets/images/Cam4.png`,
+ cam5: `${basePath}assets/images/Cam5.png`,
+ cam6: `${basePath}assets/images/Cam6.png`,
+ cam7: `${basePath}assets/images/Cam7.png`,
+ cam8: `${basePath}assets/images/Cam8.png`,
+ cam9: `${basePath}assets/images/Cam9.png`,
+ cam10: `${basePath}assets/images/Cam10.png`,
+ cam11: `${basePath}assets/images/Cam11.png`,
+ jumpscare: `${basePath}assets/images/jump.png`, // EP跳杀图片
+ trumpJumpscare: `${basePath}assets/images/jumptrump.png`, // Trump跳杀图片
+ hawkingJumpscare: `${basePath}assets/images/scaryhawking.png`, // Hawking跳杀图片
+ };
+
+ const soundPaths = {
+ ambient: `${basePath}assets/sounds/music.ogg`,
+ static: `${basePath}assets/sounds/Static_sound.ogg`,
+ staticLoop: `${basePath}assets/sounds/Static_sound.ogg`,
+ vents: `${basePath}assets/sounds/vents.ogg`,
+ ventCrawling: `${basePath}assets/sounds/vent-crawling.mp3`,
+ jumpscare: `${basePath}assets/sounds/jumpcare.ogg`,
+ hawkingJumpscare: `${basePath}assets/sounds/stephenjumpscare.ogg`, // Hawking跳杀音效
+ blip: `${basePath}assets/sounds/Blip.ogg`,
+ win: `${basePath}assets/sounds/winmusic.ogg`,
+ chimes: `${basePath}assets/sounds/chimes.ogg`,
+ crank1: `${basePath}assets/sounds/Crank1.ogg`,
+ crank2: `${basePath}assets/sounds/Crank2.ogg`,
+ ekg: `${basePath}assets/sounds/ekg.wav`,
+ hawking_shock: `${basePath}assets/sounds/hawking_shock.wav`,
+ goldenstephenscare: `${basePath}assets/sounds/goldenstephenscare.ogg`, // Golden 霍金音效
+ };
+
+ // 加载图片
+ for (const [key, path] of Object.entries(imagePaths)) {
+ try {
+ this.images[key] = await this.loadImage(path);
+ } catch (e) {
+ console.warn(`Failed to load image: ${path}`);
+ }
+ }
+
+ // 加载音频
+ for (const [key, path] of Object.entries(soundPaths)) {
+ try {
+ this.sounds[key] = new Audio(path);
+ } catch (e) {
+ console.warn(`Failed to load sound: ${path}`);
+ }
+ }
+
+ this.loaded = true;
+ }
+
+ loadImage(src) {
+ return new Promise((resolve, reject) => {
+ const img = new Image();
+ img.onload = () => resolve(img);
+ img.onerror = reject;
+ img.src = src;
+ });
+ }
+
+ playSound(key, loop = false, volume = 1.0) {
+ if (this.sounds[key]) {
+ this.sounds[key].loop = loop;
+
+ // 根据音效类型应用对应的音量
+ let categoryVolume = this.volumeSettings.master;
+
+ if (key === 'music' || key === 'music3') {
+ categoryVolume *= this.volumeSettings.menuMusic;
+ } else if (key === 'jumpscare' || key === 'hawkingJumpscare' || key === 'trumpJumpscare') {
+ categoryVolume *= this.volumeSettings.jumpscare;
+ } else if (key === 'ventCrawling') {
+ categoryVolume *= this.volumeSettings.ventCrawling;
+ } else if (key === 'vents' || key === 'ambience' || key === 'staticLoop' || key === 'static' || key === 'blip' || key === 'Blip') {
+ // 游戏背景音乐:包括通风口声音、静态噪声、摄像机切换声等
+ categoryVolume *= this.volumeSettings.gameBg;
+ }
+
+ this.sounds[key].volume = Math.min(1, volume * categoryVolume);
+ this.sounds[key].play();
+ }
+ }
+
+ stopSound(key) {
+ if (this.sounds[key]) {
+ this.sounds[key].pause();
+ this.sounds[key].currentTime = 0;
+ }
+ }
+
+ setSoundVolume(key, volume) {
+ if (this.sounds[key]) {
+ // 根据音效类型应用对应的音量
+ let categoryVolume = this.volumeSettings.master;
+
+ if (key === 'music' || key === 'music3') {
+ categoryVolume *= this.volumeSettings.menuMusic;
+ } else if (key === 'jumpscare' || key === 'hawkingJumpscare' || key === 'trumpJumpscare') {
+ categoryVolume *= this.volumeSettings.jumpscare;
+ } else if (key === 'ventCrawling') {
+ categoryVolume *= this.volumeSettings.ventCrawling;
+ } else if (key === 'vents' || key === 'ambience' || key === 'staticLoop' || key === 'static' || key === 'blip' || key === 'Blip') {
+ // 游戏背景音乐:包括通风口声音、静态噪声、摄像机切换声等
+ categoryVolume *= this.volumeSettings.gameBg;
+ }
+
+ this.sounds[key].volume = Math.min(1, volume * categoryVolume);
+ }
+ }
+}
diff --git a/games/five-nights-at-epsteins/js/CameraSystem.js b/games/five-nights-at-epsteins/js/CameraSystem.js
new file mode 100644
index 00000000..fd77c644
--- /dev/null
+++ b/games/five-nights-at-epsteins/js/CameraSystem.js
@@ -0,0 +1,1045 @@
+// Camera system management
+class CameraSystem {
+ constructor(game) {
+ this.game = game;
+ this.cameraPanel = document.getElementById('camera-panel');
+ this.currentCamLabel = document.getElementById('current-cam-label');
+ this.cameraErrorLabel = document.getElementById('camera-error-label');
+ this.playSoundBtn = document.getElementById('play-sound-btn');
+ this.shockHawkingBtn = document.getElementById('shock-hawking-btn');
+ this.currentSoundToggle = false;
+ this.staticVideo = document.getElementById('camera-static-video');
+
+ // 播放声音按钮状态
+ this.soundButtonCooldown = false;
+ this.soundButtonUseCount = 0;
+ this.maxSoundUses = 5; // 连续使用5次后摄像头故障
+ this.cooldownTime = 8000; // 8秒冷却
+ this.cooldownInterval = null; // 冷却动画定时器
+
+ // 每个位置的连续吸引计数
+ this.locationAttractCount = {}; // { 'cam11': 2, 'cam8': 1, ... }
+ this.maxLocationAttractCount = 2; // 同一位置最多连续吸引2次
+ this.lastEpLocation = null; // 记录EP的上一个位置,用于检测移动
+
+ // EP 角色配置 - 直接引用 EnemyAI 的配置(游戏初始化后会设置)
+ this.characterImages = null;
+ this.characterPositions = null;
+ this.characterBrightness = null;
+ this.characterRotation = null;
+
+ this.bindEvents();
+ }
+
+ // Initialize EP config (from EnemyAI)
+ initEPConfig() {
+ if (this.game.enemyAI) {
+ this.characterImages = this.game.enemyAI.characterImages;
+ this.characterPositions = this.game.enemyAI.characterPositions;
+ this.characterBrightness = this.game.enemyAI.characterBrightness;
+ this.characterRotation = this.game.enemyAI.characterRotation;
+ console.log('EP config initialized from EnemyAI');
+ }
+ }
+
+ bindEvents() {
+ if (this.playSoundBtn) {
+ this.playSoundBtn.addEventListener('click', () => this.playAmbientSound());
+ }
+ if (this.shockHawkingBtn) {
+ this.shockHawkingBtn.addEventListener('click', () => this.shockHawking());
+ }
+ }
+
+ toggle() {
+ // console.log('📷 Camera toggle called, current state:', this.game.state.cameraOpen);
+ if (this.game.state.cameraOpen) {
+ this.close();
+ } else {
+ this.open();
+ }
+ }
+
+ open() {
+ // console.log('📷 Opening camera...');
+ // console.log('📷 Camera panel element:', this.cameraPanel);
+ // console.log('📷 Camera panel classes before:', this.cameraPanel.className);
+
+ this.game.state.cameraOpen = true;
+ this.cameraPanel.classList.remove('hidden');
+ this.cameraPanel.classList.add('show');
+
+ // console.log('📷 Camera panel classes after:', this.cameraPanel.className);
+ // console.log('📷 Camera panel display:', window.getComputedStyle(this.cameraPanel).display);
+ // console.log('📷 Camera panel opacity:', window.getComputedStyle(this.cameraPanel).opacity);
+ // console.log('📷 Camera panel transform:', window.getComputedStyle(this.cameraPanel).transform);
+
+ this.game.assets.playSound('crank1');
+
+ // Start looping low volume static sound
+ this.game.assets.playSound('staticLoop', true, 0.3);
+
+ this.createCameraGrid();
+
+ // 更新电击按钮显示
+ this.updateShockButtonVisibility();
+
+ // 更新霍金警告位置(从风扇左边移到地图上)
+ if (this.game.enemyAI && this.game.enemyAI.hawking.active) {
+ this.game.enemyAI.updateHawkingWarningDisplay();
+ }
+
+ // If camera failed, show failure effect
+ if (this.game.state.cameraFailed) {
+ console.log('📷 Camera is failed, showing failure effect');
+ this.showCameraFailure();
+ } else {
+ console.log('📷 Camera is normal, showing normal view');
+ // Normal state, ensure all failure effects removed
+ this.cameraPanel.classList.remove('transitioning');
+
+ // Hide ERR label
+ if (this.cameraErrorLabel) {
+ this.cameraErrorLabel.classList.remove('active');
+ }
+
+ // Stop static
+ this.stopStatic();
+
+ // Show map
+ const cameraGrid = document.getElementById('camera-grid');
+ if (cameraGrid) {
+ cameraGrid.style.display = 'block';
+ }
+
+ // Update view
+ this.updateView();
+ }
+
+ // Stop view rotation
+ this.game.isRotatingLeft = false;
+ this.game.isRotatingRight = false;
+ }
+
+ // Show camera failure effect
+ showCameraFailure() {
+ console.log('Showing camera failure effect...');
+
+ // Night 5: 30% 概率触发 Golden 霍金彩蛋
+ if (this.game.state.currentNight === 5 && Math.random() < 0.3) {
+ this.game.showGoldenStephen();
+ }
+
+ // Hide background image and characters
+ this.cameraPanel.classList.add('transitioning');
+
+ // Hide map
+ const cameraGrid = document.getElementById('camera-grid');
+ if (cameraGrid) {
+ cameraGrid.style.display = 'none';
+ console.log('Camera grid hidden');
+ }
+
+ // Show ERR label
+ if (this.cameraErrorLabel) {
+ this.cameraErrorLabel.classList.add('active');
+ console.log('ERR label shown');
+ }
+
+ // Show and play static video
+ if (this.staticVideo) {
+ console.log('Starting static video...');
+ this.staticVideo.classList.add('active');
+ this.staticVideo.currentTime = 0; // Play from beginning
+ this.staticVideo.play().catch(e => console.log('Video playback failed:', e));
+ } else {
+ console.error('Static video element not found!');
+ }
+ }
+
+ // Stop static effect
+ stopStatic() {
+ if (this.staticVideo) {
+ this.staticVideo.classList.remove('active');
+ this.staticVideo.pause();
+ this.staticVideo.currentTime = 0;
+ }
+ }
+
+ // Start static effect (for switching cameras)
+ startStatic() {
+ if (this.staticVideo) {
+ this.staticVideo.classList.add('active');
+ this.staticVideo.play().catch(e => console.log('Video playback failed:', e));
+ }
+ }
+
+ // Restore camera normal display
+ restoreCameraView() {
+ console.log('Restoring camera view...');
+
+ // Stop static
+ this.stopStatic();
+ console.log('Static video stopped');
+
+ // Remove failure state
+ this.cameraPanel.classList.remove('transitioning');
+ console.log('Removed transitioning class');
+
+ // Hide ERR label
+ if (this.cameraErrorLabel) {
+ this.cameraErrorLabel.classList.remove('active');
+ console.log('ERR label hidden');
+ }
+
+ // Show map
+ const cameraGrid = document.getElementById('camera-grid');
+ if (cameraGrid) {
+ cameraGrid.style.display = 'block';
+ console.log('Camera grid shown');
+ }
+
+ // Update view
+ this.updateView();
+ console.log('View updated');
+ }
+
+ // Fix camera
+ restartCamera() {
+ // 如果控制面板正忙,不允许操作
+ if (this.game.state.controlPanelBusy) {
+ console.log('Control panel is busy, cannot restart camera');
+ return;
+ }
+
+ console.log('Restarting camera system...');
+ this.game.state.cameraRestarting = true;
+ this.game.state.controlPanelBusy = true; // 锁定控制面板
+
+ // 播放心电图音效
+ this.game.assets.playSound('ekg', false, 0.8);
+
+ // Restore after 4 seconds
+ setTimeout(() => {
+ // 无论之前是否故障,重启后都恢复正常
+ this.game.state.cameraFailed = false;
+ this.game.state.cameraRestarting = false;
+ this.game.state.controlPanelBusy = false; // 解锁控制面板
+
+ // Stop static noise (如果有的话)
+ this.game.assets.stopSound('static');
+
+ // Reset sound button count (恢复5次使用次数)
+ this.resetSoundButtonCount();
+
+ console.log('Camera system restored!');
+
+ // If camera is open, immediately restore display
+ if (this.game.state.cameraOpen) {
+ console.log('Camera is open, restoring view...');
+ this.restoreCameraView();
+ }
+ }, 4000);
+ }
+
+ close() {
+ this.game.state.cameraOpen = false;
+ this.cameraPanel.classList.add('closing');
+ this.cameraPanel.classList.remove('show');
+
+ // Stop looping static sound
+ this.game.assets.stopSound('staticLoop');
+
+ // Clear character display
+ const characterOverlay = document.getElementById('character-overlay');
+ if (characterOverlay) {
+ characterOverlay.innerHTML = '';
+ console.log('Character overlay cleared');
+ }
+
+ // 更新霍金警告位置(从地图移到风扇左边)
+ if (this.game.enemyAI && this.game.enemyAI.hawking.active) {
+ this.game.enemyAI.updateHawkingWarningDisplay();
+ }
+
+ setTimeout(() => {
+ this.cameraPanel.classList.add('hidden');
+ this.cameraPanel.classList.remove('closing');
+ }, 400);
+
+ this.game.assets.playSound('crank2');
+ }
+
+ switchCamera(camNum) {
+ // If camera failed, cannot switch
+ if (this.game.state.cameraFailed) {
+ console.log('Camera system is offline! Cannot switch cameras.');
+ return;
+ }
+
+ // Add transition state, hide background image
+ this.cameraPanel.classList.add('transitioning');
+
+ // Hide map
+ const cameraGrid = document.getElementById('camera-grid');
+ if (cameraGrid) {
+ cameraGrid.style.display = 'none';
+ }
+
+ // 隐藏角色
+ const characterOverlay = document.getElementById('character-overlay');
+ if (characterOverlay) {
+ characterOverlay.style.display = 'none';
+ }
+
+ // 暂时降低循环静态音的音量
+ this.game.assets.setSoundVolume('staticLoop', 0.1);
+
+ // 播放正常音量的静态音效
+ this.game.assets.playSound('static', false, 1.0);
+
+ // 1000ms 后停止静态音效
+ setTimeout(() => {
+ this.game.assets.stopSound('static');
+ }, 1000);
+
+ // Show static effect
+ this.startStatic();
+
+ // Switch camera after 500ms
+ setTimeout(() => {
+ // If camera already failed, stop switch animation, show failure effect
+ if (this.game.state.cameraFailed) {
+ console.log('Camera failed during switch, showing failure effect');
+ this.showCameraFailure();
+ return;
+ }
+
+ this.game.state.currentCam = `cam${camNum}`;
+ this.updateView();
+ this.createCameraGrid();
+
+ // After another 500ms fade out static, restore background
+ setTimeout(() => {
+ // Check again if failed
+ if (this.game.state.cameraFailed) {
+ console.log('Camera failed during switch, showing failure effect');
+ this.showCameraFailure();
+ return;
+ }
+
+ this.stopStatic();
+ this.cameraPanel.classList.remove('transitioning');
+
+ // 显示地图
+ if (cameraGrid) {
+ cameraGrid.style.display = 'block';
+ }
+
+ // 显示角色
+ if (characterOverlay) {
+ characterOverlay.style.display = 'block';
+ }
+
+ // 更新电击按钮显示(根据当前摄像头)
+ this.updateShockButtonVisibility();
+
+ // 恢复循环静态音的音量
+ this.game.assets.setSoundVolume('staticLoop', 0.3);
+ }, 500);
+ }, 500);
+ }
+
+ updateView() {
+ // If camera failed, don't update view
+ if (this.game.state.cameraFailed) {
+ return;
+ }
+
+ // Update camera panel background image
+ if (this.game.assets.images[this.game.state.currentCam]) {
+ this.cameraPanel.style.backgroundImage = `url('${this.game.assets.images[this.game.state.currentCam].src}')`;
+ }
+
+ // 更新摄像头标签
+ const camNum = this.game.state.currentCam.replace('cam', '');
+ this.currentCamLabel.textContent = `CAM ${camNum}`;
+
+ // 更新角色显示
+ this.updateCharacterDisplay();
+
+ // 更新电击按钮显示
+ this.updateShockButtonVisibility();
+ }
+
+ // 更新角色显示(支持多个敌人)
+ updateCharacterDisplay() {
+ const currentCam = this.game.state.currentCam;
+ const epLocation = this.game.enemyAI.getCurrentLocation();
+ const trumpLocation = this.game.enemyAI.getTrumpCurrentLocation();
+ const hawkingActive = this.game.enemyAI.hawking.active;
+
+ console.log(`updateCharacterDisplay - Current Cam: ${currentCam}, EP: ${epLocation}, Trump: ${trumpLocation}, Hawking: ${hawkingActive}, Night: ${this.game.state.currentNight}`);
+
+ // 打印所有相关元素的z-index
+ console.log('🔍 Z-Index Debug:');
+ console.log(' - cameraPanel:', window.getComputedStyle(this.cameraPanel).zIndex);
+ const staticVideo = document.getElementById('camera-static-video');
+ if (staticVideo) {
+ console.log(' - staticVideo:', window.getComputedStyle(staticVideo).zIndex);
+ }
+ const existingOverlay = document.getElementById('character-overlay');
+ if (existingOverlay) {
+ console.log(' - characterOverlay:', window.getComputedStyle(existingOverlay).zIndex);
+ console.log(' - characterOverlay display:', window.getComputedStyle(existingOverlay).display);
+ console.log(' - characterOverlay children count:', existingOverlay.children.length);
+ }
+
+ // 获取或创建角色容器
+ let characterOverlay = document.getElementById('character-overlay');
+ if (!characterOverlay) {
+ characterOverlay = document.createElement('div');
+ characterOverlay.id = 'character-overlay';
+ characterOverlay.style.position = 'absolute';
+ characterOverlay.style.top = '0';
+ characterOverlay.style.left = '0';
+ characterOverlay.style.width = '100%';
+ characterOverlay.style.height = '100%';
+ characterOverlay.style.pointerEvents = 'none';
+ characterOverlay.style.zIndex = '5';
+ characterOverlay.style.overflow = 'hidden';
+ this.cameraPanel.appendChild(characterOverlay);
+ }
+
+ // 清空之前的角色
+ characterOverlay.innerHTML = '';
+
+ console.log('🔍 Character overlay cleared, checking EP display conditions...');
+ console.log('🔍 EP hasSpawned:', this.game.enemyAI.epstein.hasSpawned);
+ console.log('🔍 EP location matches current cam:', epLocation === currentCam);
+ console.log('🔍 Has characterImages:', !!this.characterImages);
+ console.log('🔍 Has image for current cam:', this.characterImages ? !!this.characterImages[currentCam] : 'N/A');
+
+ // 显示霍金(如果激活且在cam6)
+ if (hawkingActive && currentCam === 'cam6') {
+ const hawkingImg = document.createElement('img');
+ hawkingImg.src = '/assets/images/mrstephen.png';
+ hawkingImg.style.position = 'absolute';
+ hawkingImg.className = 'visible hawking-character';
+ hawkingImg.style.zIndex = '3'; // Hawking 在最上层
+ hawkingImg.style.left = '59.6%';
+ hawkingImg.style.bottom = '0.9%';
+ hawkingImg.style.width = '37%';
+ hawkingImg.style.transform = 'translateX(-50%) rotate(-5deg)';
+ hawkingImg.style.filter = 'brightness(0.33) contrast(1) saturate(1)';
+
+ characterOverlay.appendChild(hawkingImg);
+ console.log(`✓ Displaying Hawking at cam6`);
+ }
+
+ // 显示 EP(如果已出场且在当前摄像头)
+ // console.log('🔍 EP Display Check:', {
+ // hasSpawned: this.game.enemyAI.epstein.hasSpawned,
+ // epLocation: epLocation,
+ // currentCam: currentCam,
+ // match: epLocation === currentCam,
+ // hasImage: !!this.characterImages,
+ // imageForCam: this.characterImages ? !!this.characterImages[currentCam] : 'N/A'
+ // });
+
+ if (this.game.enemyAI.epstein.hasSpawned && epLocation === currentCam && this.characterImages && this.characterImages[currentCam]) {
+ // 创建EP容器(用于包含EP图片和电眼)
+ const epContainer = document.createElement('div');
+ epContainer.className = 'ep-container';
+ epContainer.style.position = 'absolute';
+ epContainer.style.zIndex = '1';
+
+ const pos = this.characterPositions[currentCam];
+ if (pos) {
+ if (pos.left) {
+ epContainer.style.left = pos.left;
+ epContainer.style.right = 'auto';
+ } else if (pos.right) {
+ epContainer.style.right = pos.right;
+ epContainer.style.left = 'auto';
+ }
+
+ epContainer.style.bottom = pos.bottom;
+ epContainer.style.width = pos.width;
+ epContainer.style.transform = pos.transform || 'none';
+ }
+
+ // EP图片
+ const epImg = document.createElement('img');
+ epImg.src = this.characterImages[currentCam];
+ epImg.style.position = 'relative';
+ epImg.style.width = '100%';
+ epImg.style.height = 'auto';
+ epImg.style.display = 'block';
+ epImg.className = 'visible ep-character';
+
+ // 应用明暗度
+ const brightness = this.characterBrightness[currentCam] || 100;
+ epImg.style.filter = `brightness(${brightness}%)`;
+
+ epContainer.appendChild(epImg);
+ characterOverlay.appendChild(epContainer);
+ console.log(`✓ Displaying EP at ${currentCam}`);
+
+ // Night 6: 渲染电眼特效(作为EP容器的子元素)
+ if (this.game.state.currentNight === 6) {
+ this.renderLightningEyes(epContainer, currentCam);
+ }
+ }
+
+ // 显示 Trump(如果已出场且在当前摄像头,且不在爬行状态,且当前夜晚有Trump配置)
+ if (this.game.enemyAI.trump.hasSpawned && !this.game.enemyAI.trump.isCrawling && trumpLocation === currentCam && this.game.enemyAI.currentTrumpConfig) {
+ const trumpImages = this.game.enemyAI.trumpImages;
+ const trumpPositions = this.game.enemyAI.trumpPositions;
+ const trumpBrightness = this.game.enemyAI.trumpBrightness;
+
+ if (trumpImages[currentCam]) {
+ const trumpImg = document.createElement('img');
+ trumpImg.src = trumpImages[currentCam];
+ trumpImg.style.position = 'absolute';
+ trumpImg.className = 'visible trump-character';
+ trumpImg.style.zIndex = '2'; // Trump 在上层
+
+ const pos = trumpPositions[currentCam];
+ if (pos) {
+ if (pos.left) {
+ trumpImg.style.left = pos.left;
+ trumpImg.style.right = 'auto';
+ } else if (pos.right) {
+ trumpImg.style.right = pos.right;
+ trumpImg.style.left = 'auto';
+ }
+
+ trumpImg.style.bottom = pos.bottom;
+ trumpImg.style.width = pos.width;
+ trumpImg.style.transform = pos.transform || 'none';
+ }
+
+ const brightness = trumpBrightness[currentCam] || 100;
+ trumpImg.style.filter = `brightness(${brightness}%)`;
+
+ characterOverlay.appendChild(trumpImg);
+ console.log(`✓ Displaying Trump at ${currentCam}`);
+ }
+ }
+
+ if (characterOverlay.children.length === 0) {
+ console.log(`✗ No characters at current camera (viewing ${currentCam})`);
+ }
+ }
+
+ createCameraGrid() {
+ const grid = document.getElementById('camera-grid');
+ grid.innerHTML = '';
+
+ // 创建地图容器
+ const mapContainer = document.createElement('div');
+ mapContainer.style.position = 'relative';
+ mapContainer.style.width = '100%';
+ mapContainer.style.height = '100%';
+
+ // 添加地图图片
+ const mapImg = document.createElement('img');
+ mapImg.src = '/assets/images/FNAE-Map-layout.png';
+ mapImg.style.width = '100%';
+ mapImg.style.height = 'auto';
+ mapImg.style.display = 'block';
+ mapContainer.appendChild(mapImg);
+
+ // 添加 YOU 标记(玩家位置)
+ const youMarker = document.createElement('div');
+ youMarker.style.position = 'absolute';
+ youMarker.style.left = '7.0%';
+ youMarker.style.top = '82.6%';
+ youMarker.style.width = '13.0%';
+ youMarker.style.height = '8.0%';
+ youMarker.style.display = 'flex';
+ youMarker.style.alignItems = 'center';
+ youMarker.style.justifyContent = 'center';
+ youMarker.style.fontSize = '0.7vw';
+ youMarker.style.fontWeight = 'bold';
+ youMarker.style.color = '#fff';
+ youMarker.style.textShadow = '1px 1px 2px #000';
+ youMarker.style.fontFamily = 'Arial, sans-serif';
+ youMarker.style.background = 'rgba(0, 0, 0, 0.5)';
+ youMarker.style.borderRadius = '4px';
+ youMarker.textContent = 'YOU';
+ mapContainer.appendChild(youMarker);
+
+ // 定义每个摄像头在地图上的位置(百分比)
+ const cameraPositions = [
+ { cam: 1, x: 25.7, y: 84.3, width: 13.0, height: 8.0 },
+ { cam: 2, x: 35.0, y: 56.6, width: 13.0, height: 8.0 },
+ { cam: 3, x: 51.5, y: 77.6, width: 13.0, height: 8.0 },
+ { cam: 4, x: 57.7, y: 44.9, width: 12.9, height: 8.0 },
+ { cam: 5, x: 75.4, y: 60.3, width: 12.9, height: 8.0 },
+ { cam: 6, x: 77.2, y: 82.2, width: 13.0, height: 8.0 },
+ { cam: 7, x: 52.0, y: 27.9, width: 12.9, height: 8.0 },
+ { cam: 8, x: 80.2, y: 21.9, width: 12.8, height: 8.0 },
+ { cam: 9, x: 24.4, y: 20.6, width: 12.9, height: 8.0 },
+ { cam: 10, x: 7.9, y: 39.1, width: 12.8, height: 8.0 },
+ { cam: 11, x: 72.9, y: 4.6, width: 13.0, height: 8.0 },
+ ];
+
+ // 为每个摄像头创建可点击热区
+ cameraPositions.forEach(pos => {
+ const hotspot = document.createElement('div');
+ hotspot.className = 'camera-hotspot';
+ hotspot.style.position = 'absolute';
+ hotspot.style.left = pos.x + '%';
+ hotspot.style.top = pos.y + '%';
+ hotspot.style.width = pos.width + '%';
+ hotspot.style.height = pos.height + '%';
+ hotspot.style.cursor = 'pointer';
+ hotspot.style.transition = 'all 0.2s';
+ hotspot.style.display = 'flex';
+ hotspot.style.alignItems = 'center';
+ hotspot.style.justifyContent = 'center';
+ hotspot.style.fontSize = '0.7vw';
+ hotspot.style.fontWeight = 'bold';
+ hotspot.style.color = '#fff';
+ hotspot.style.textShadow = '1px 1px 2px #000';
+ hotspot.style.fontFamily = 'Arial, sans-serif';
+ hotspot.style.whiteSpace = 'nowrap';
+ hotspot.style.borderRadius = '4px';
+ hotspot.style.letterSpacing = '0.5px';
+
+ // 添加CAM文本
+ hotspot.textContent = `CAM ${pos.cam}`;
+
+ // 当前选中的摄像头绿色闪烁
+ if (this.game.state.currentCam === `cam${pos.cam}`) {
+ hotspot.classList.add('camera-selected');
+ hotspot.style.border = 'none';
+ } else {
+ hotspot.style.border = 'none';
+ hotspot.style.background = 'transparent';
+ }
+
+ // 悬浮效果
+ hotspot.addEventListener('mouseenter', () => {
+ if (this.game.state.currentCam !== `cam${pos.cam}`) {
+ hotspot.style.background = 'rgba(255, 255, 255, 0.2)';
+ }
+ });
+
+ hotspot.addEventListener('mouseleave', () => {
+ if (this.game.state.currentCam !== `cam${pos.cam}`) {
+ hotspot.style.background = 'transparent';
+ }
+ });
+
+ // 点击切换摄像头
+ hotspot.addEventListener('click', () => this.switchCamera(pos.cam));
+
+ mapContainer.appendChild(hotspot);
+ });
+
+ grid.appendChild(mapContainer);
+ }
+
+ playAmbientSound() {
+ // 如果在冷却中,不能使用
+ if (this.soundButtonCooldown) {
+ console.log('Sound button on cooldown');
+ return;
+ }
+
+ const currentCam = this.game.state.currentCam;
+
+ // 检查EP是否移动了,如果移动了则重置所有位置的计数
+ const currentEpLocation = this.game.enemyAI.getCurrentLocation();
+ if (this.lastEpLocation !== currentEpLocation) {
+ console.log(`EP moved from ${this.lastEpLocation} to ${currentEpLocation}, resetting all location counts`);
+ this.locationAttractCount = {}; // 重置所有位置计数
+ this.lastEpLocation = currentEpLocation;
+ }
+
+ // 交替播放 1.ogg 和 2.ogg
+ const soundFile = this.currentSoundToggle ? '2.ogg' : '1.ogg';
+ this.currentSoundToggle = !this.currentSoundToggle;
+
+ // 创建并播放音频
+ const audio = new Audio(`assets/sounds/${soundFile}`);
+ audio.play().catch(e => console.log('音频播放失败:', e));
+
+ // 检查当前位置是否已经用完2次
+ let canAttract = true;
+ if (this.locationAttractCount[currentCam] >= this.maxLocationAttractCount) {
+ console.log(`Location ${currentCam} already used ${this.maxLocationAttractCount} times - wasting player's attempt`);
+ canAttract = false;
+ }
+
+ // 尝试吸引EP到当前摄像头位置(如果位置可用)
+ let attracted = false;
+ if (canAttract) {
+ attracted = this.game.enemyAI.attractToSound(currentCam);
+
+ if (attracted) {
+ // 吸引成功,播放过场动画
+ this.playAttractionTransition();
+
+ // 增加该位置的计数
+ this.locationAttractCount[currentCam] = (this.locationAttractCount[currentCam] || 0) + 1;
+ console.log(`Epstein attracted to ${currentCam}! Count: ${this.locationAttractCount[currentCam]}/${this.maxLocationAttractCount}`);
+
+ // 更新EP位置记录
+ this.lastEpLocation = currentCam;
+ } else {
+ // 吸引失败(不邻近或其他原因),不给用户提示
+ console.log('Attraction failed');
+ }
+ } else {
+ // 位置已用完2次,浪费玩家的尝试
+ console.log('Location maxed out - player wasted an attempt');
+ }
+
+ // 增加使用次数(无论是否成功)
+ this.soundButtonUseCount++;
+ console.log(`Sound button used: ${this.soundButtonUseCount}/${this.maxSoundUses}`);
+
+ // 检查是否达到最大使用次数
+ if (this.soundButtonUseCount >= this.maxSoundUses) {
+ console.log('Sound button overused! Camera failure!');
+ this.soundButtonUseCount = 0; // 重置计数
+
+ // 如果正在播放吸引动画,立即停止
+ if (this.cameraPanel.classList.contains('transitioning')) {
+ this.stopStatic();
+ this.cameraPanel.classList.remove('transitioning');
+ }
+
+ // 触发摄像头故障
+ this.game.enemyAI.triggerCameraFailure();
+ }
+
+ // 开始冷却
+ this.soundButtonCooldown = true;
+ this.playSoundBtn.style.opacity = '0.5';
+ this.playSoundBtn.style.cursor = 'not-allowed';
+
+ // 添加加载动画
+ this.startCooldownAnimation();
+
+ // 8秒后解除冷却
+ setTimeout(() => {
+ this.soundButtonCooldown = false;
+ this.playSoundBtn.style.opacity = '1';
+ this.playSoundBtn.style.cursor = 'pointer';
+ this.stopCooldownAnimation();
+ }, this.cooldownTime);
+ }
+
+ // 开始冷却动画
+ startCooldownAnimation() {
+ let dotCount = 0;
+ this.cooldownInterval = setInterval(() => {
+ dotCount = (dotCount + 1) % 4;
+ const dots = '.'.repeat(dotCount);
+ this.playSoundBtn.textContent = `PLAY SOUND${dots}`;
+ }, 500);
+ }
+
+ // 停止冷却动画
+ stopCooldownAnimation() {
+ if (this.cooldownInterval) {
+ clearInterval(this.cooldownInterval);
+ this.cooldownInterval = null;
+ }
+ this.playSoundBtn.textContent = 'PLAY SOUND';
+ }
+
+ // 吸引成功的过场动画
+ playAttractionTransition() {
+ console.log('Playing attraction transition...');
+
+ // 添加过场状态,隐藏背景图片和地图
+ this.cameraPanel.classList.add('transitioning');
+
+ // 隐藏地图
+ const cameraGrid = document.getElementById('camera-grid');
+ if (cameraGrid) {
+ cameraGrid.style.display = 'none';
+ }
+
+ // 隐藏角色
+ const characterOverlay = document.getElementById('character-overlay');
+ if (characterOverlay) {
+ characterOverlay.style.display = 'none';
+ }
+
+ // 暂时降低循环静态音的音量
+ this.game.assets.setSoundVolume('staticLoop', 0.1);
+
+ // 播放正常音量的静态音效
+ this.game.assets.playSound('static', false, 1.0);
+
+ // 1000ms 后停止静态音效
+ setTimeout(() => {
+ this.game.assets.stopSound('static');
+ }, 1000);
+
+ // 显示雪花效果
+ this.startStatic();
+
+ // 500ms 后更新显示
+ setTimeout(() => {
+ // 如果摄像头已经故障,停止动画并显示故障效果
+ if (this.game.state.cameraFailed) {
+ console.log('Camera failed during attraction transition, showing failure effect');
+ this.showCameraFailure();
+ return;
+ }
+
+ this.updateCharacterDisplay();
+
+ // 再过 500ms 淡出雪花,恢复背景
+ setTimeout(() => {
+ // 如果摄像头已经故障,停止动画并显示故障效果
+ if (this.game.state.cameraFailed) {
+ console.log('Camera failed during attraction transition, showing failure effect');
+ this.showCameraFailure();
+ return;
+ }
+
+ this.stopStatic();
+ this.cameraPanel.classList.remove('transitioning');
+
+ // 显示地图
+ if (cameraGrid) {
+ cameraGrid.style.display = 'block';
+ }
+
+ // 显示角色
+ if (characterOverlay) {
+ characterOverlay.style.display = 'block';
+ }
+
+ // 恢复循环静态音的音量
+ this.game.assets.setSoundVolume('staticLoop', 0.3);
+ }, 500);
+ }, 500);
+ }
+
+ // 重置声音按钮计数(摄像头重启后调用)
+ resetSoundButtonCount() {
+ this.soundButtonUseCount = 0;
+ }
+
+ // EP移动时的过场动画
+ playMovementTransition() {
+ console.log('Playing movement transition...');
+
+ // 如果摄像头已经故障,不播放动画
+ if (this.game.state.cameraFailed) {
+ console.log('Camera already failed, skipping movement transition');
+ return;
+ }
+
+ // 添加过场状态
+ this.cameraPanel.classList.add('transitioning');
+
+ // 隐藏地图
+ const cameraGrid = document.getElementById('camera-grid');
+ if (cameraGrid) {
+ cameraGrid.style.display = 'none';
+ }
+
+ // 隐藏角色
+ const characterOverlay = document.getElementById('character-overlay');
+ if (characterOverlay) {
+ characterOverlay.style.display = 'none';
+ }
+
+ // 暂时降低循环静态音的音量
+ this.game.assets.setSoundVolume('staticLoop', 0.1);
+
+ // 播放正常音量的静态音效
+ this.game.assets.playSound('static', false, 1.0);
+
+ // 1000ms 后停止静态音效
+ setTimeout(() => {
+ this.game.assets.stopSound('static');
+ }, 1000);
+
+ // 显示雪花效果
+ this.startStatic();
+
+ // 500ms 后更新显示
+ setTimeout(() => {
+ // 如果摄像头已经故障,停止动画并显示故障效果
+ if (this.game.state.cameraFailed) {
+ console.log('Camera failed during movement transition, showing failure effect');
+ this.showCameraFailure();
+ return;
+ }
+
+ this.updateCharacterDisplay();
+
+ // 再过 500ms 淡出雪花,恢复背景
+ setTimeout(() => {
+ // 如果摄像头已经故障,停止动画并显示故障效果
+ if (this.game.state.cameraFailed) {
+ console.log('Camera failed during movement transition, showing failure effect');
+ this.showCameraFailure();
+ return;
+ }
+
+ this.stopStatic();
+ this.cameraPanel.classList.remove('transitioning');
+
+ // 显示地图
+ if (cameraGrid) {
+ cameraGrid.style.display = 'block';
+ }
+
+ // 显示角色
+ if (characterOverlay) {
+ characterOverlay.style.display = 'block';
+ }
+
+ // 恢复循环静态音的音量
+ this.game.assets.setSoundVolume('staticLoop', 0.3);
+ }, 500);
+ }, 500);
+ }
+
+ // 电击霍金
+ shockHawking() {
+ // 立即播放音效
+ this.game.assets.playSound('hawking_shock', false, 1.0);
+
+ // 显示雪花过场动画
+ this.cameraPanel.classList.add('transitioning');
+
+ // 播放雪花视频
+ if (this.staticVideo) {
+ this.staticVideo.classList.add('active');
+ this.staticVideo.currentTime = 0;
+ this.staticVideo.play().catch(e => console.log('Video playback failed:', e));
+ }
+
+ // 1秒后执行电击并恢复画面
+ setTimeout(() => {
+ if (this.game.enemyAI && this.game.enemyAI.shockHawking()) {
+ console.log('Hawking shocked successfully!');
+ }
+
+ // 停止雪花视频
+ if (this.staticVideo) {
+ this.staticVideo.classList.remove('active');
+ this.staticVideo.pause();
+ }
+
+ // 恢复摄像头画面
+ this.cameraPanel.classList.remove('transitioning');
+ this.updateView();
+ }, 1000);
+ }
+
+ // 更新电击按钮显示(Night 3-5 和 Custom Night 中 Hawking 激活时显示)
+ updateShockButtonVisibility() {
+ if (this.shockHawkingBtn) {
+ const currentCam = this.game.state.currentCam;
+ const night = this.game.state.currentNight;
+
+ // Night 3-5 显示
+ const isNormalNight = night >= 3 && night <= 5;
+
+ // Custom Night 且 Hawking AI > 0 时显示
+ const isCustomNightWithHawking = this.game.state.customNight &&
+ night === 7 &&
+ this.game.state.customAILevels.hawking > 0;
+
+ if ((isNormalNight || isCustomNightWithHawking) && this.game.state.cameraOpen && currentCam === 'cam6') {
+ this.shockHawkingBtn.style.display = 'block';
+ } else {
+ this.shockHawkingBtn.style.display = 'none';
+ }
+ }
+ }
+
+ // 渲染电眼特效(Night 6)- 作为EP容器的子元素
+ renderLightningEyes(epContainer, currentCam) {
+ const eyesConfig = this.game.enemyAI.lightningEyesConfig[currentCam];
+ if (!eyesConfig) return;
+
+ // 创建两只眼睛(相对于EP图片定位)
+ [eyesConfig.eye1, eyesConfig.eye2].forEach((eyeConfig, index) => {
+ // 眼睛容器
+ const eyeContainer = document.createElement('div');
+ eyeContainer.className = 'lightning-eye-container';
+ eyeContainer.style.position = 'absolute';
+ eyeContainer.style.left = eyeConfig.left;
+ eyeContainer.style.top = eyeConfig.top;
+ eyeContainer.style.width = eyeConfig.width;
+ eyeContainer.style.height = eyeConfig.height;
+ eyeContainer.style.transform = 'translate(-50%, -50%)';
+ eyeContainer.style.transformOrigin = 'center center';
+ eyeContainer.style.zIndex = '10';
+ eyeContainer.style.pointerEvents = 'none';
+
+ // 核心发光点
+ const core = document.createElement('div');
+ core.className = 'lightning-eye-core';
+ core.style.position = 'absolute';
+ core.style.top = '50%';
+ core.style.left = '50%';
+ core.style.width = '60%';
+ core.style.height = '60%';
+ core.style.transform = 'translate(-50%, -50%)';
+ core.style.background = 'radial-gradient(circle, rgba(255, 255, 255, 1) 0%, rgba(0, 255, 255, 1) 40%, rgba(0, 200, 255, 0.6) 70%, transparent 100%)';
+ core.style.borderRadius = '50%';
+ core.style.filter = 'brightness(2)';
+ core.style.animation = 'lightning-pulse 0.15s infinite';
+
+ // 外层光晕
+ const glow = document.createElement('div');
+ glow.className = 'lightning-eye-glow';
+ glow.style.position = 'absolute';
+ glow.style.top = '50%';
+ glow.style.left = '50%';
+ glow.style.width = '100%';
+ glow.style.height = '100%';
+ glow.style.transform = 'translate(-50%, -50%)';
+ glow.style.background = 'radial-gradient(ellipse at center, rgba(0, 255, 255, 0.8) 0%, rgba(0, 255, 255, 0.4) 30%, rgba(0, 200, 255, 0.2) 60%, transparent 100%)';
+ glow.style.borderRadius = '50%';
+ glow.style.boxShadow = `
+ 0 0 20px rgba(0, 255, 255, 1),
+ 0 0 40px rgba(0, 255, 255, 0.8),
+ 0 0 60px rgba(0, 255, 255, 0.6)
+ `;
+ glow.style.animation = 'lightning-flicker 0.1s infinite';
+
+ // 雷电效果(多条随机闪电)
+ for (let i = 0; i < 3; i++) {
+ const lightning = document.createElement('div');
+ lightning.className = 'lightning-bolt';
+ lightning.style.position = 'absolute';
+ lightning.style.top = '50%';
+ lightning.style.left = '50%';
+ lightning.style.width = '2px';
+ lightning.style.height = `${30 + Math.random() * 40}%`;
+ lightning.style.background = 'linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(0, 255, 255, 0.8), transparent)';
+ lightning.style.transformOrigin = 'top center';
+ lightning.style.transform = `translate(-50%, -50%) rotate(${Math.random() * 360}deg)`;
+ lightning.style.boxShadow = '0 0 5px rgba(0, 255, 255, 1), 0 0 10px rgba(0, 255, 255, 0.8)';
+ lightning.style.animation = `lightning-bolt ${0.1 + Math.random() * 0.1}s infinite`;
+ lightning.style.animationDelay = `${Math.random() * 0.1}s`;
+ lightning.style.opacity = '0.8';
+ eyeContainer.appendChild(lightning);
+ }
+
+ eyeContainer.appendChild(glow);
+ eyeContainer.appendChild(core);
+ epContainer.appendChild(eyeContainer);
+ });
+
+ console.log(`⚡ Rendered lightning eyes with electric effects at ${currentCam}`);
+ }
+}
diff --git a/games/five-nights-at-epsteins/js/EnemyAI.js b/games/five-nights-at-epsteins/js/EnemyAI.js
new file mode 100644
index 00000000..2e8eefed
--- /dev/null
+++ b/games/five-nights-at-epsteins/js/EnemyAI.js
@@ -0,0 +1,1819 @@
+// 敌人AI系统 - 基于FNAF机制
+class EnemyAI {
+ constructor(game) {
+ this.game = game;
+
+ // ==================== AI配置系统 ====================
+ // Epstein AI配置(按夜数)
+ this.epsteinConfig = {
+ 1: {
+ aiLevel: 12, // AI等级 (0-20),12/24 = 50%移动概率
+ movementInterval: [9000, 10000], // 移动检查间隔(毫秒)[最小值, 最大值]
+ movementDuration: 1000, // 移动动画时长(毫秒)
+ spawnDelay: 120000, // 出场延迟(毫秒)
+ movementProbability: { // 移动方向概率
+ forward: 0.8, // 前进概率 100%
+ lateral: 0.1, // 平移概率 0%(当前不支持)
+ backward: 0.1 // 后退概率 0%
+ },
+ soundLureResistance: 0 // 对sound吸引的抵抗概率(0-1)
+ },
+ 2: {
+ aiLevel: 12,
+ movementInterval: [9000, 10000],
+ movementDuration: 1000,
+ spawnDelay: 0,
+ movementProbability: {
+ forward: 0.8,
+ lateral: 0.1,
+ backward: 0.1
+ },
+ soundLureResistance: 0.1
+ },
+ 3: {
+ aiLevel: 12,
+ movementInterval: [9000, 10000],
+ movementDuration: 1000,
+ spawnDelay: 0,
+ movementProbability: {
+ forward: 0.8,
+ lateral: 0.2,
+ backward: 0
+ },
+ soundLureResistance: 0.1 // Night 3开始:15%概率抵抗sound吸引
+ },
+ 4: {
+ aiLevel: 12,
+ movementInterval: [9000, 10000], // Night 4:9-10秒间隔
+ movementDuration: 1000,
+ spawnDelay: 0,
+ movementProbability: {
+ forward: 0.9,
+ lateral: 0.1,
+ backward: 0
+ },
+ soundLureResistance: 0.15
+ },
+ 5: {
+ aiLevel: 12,
+ movementInterval: [9000, 10000], // Night 5:9-10秒间隔
+ movementDuration: 1000,
+ spawnDelay: 0,
+ movementProbability: {
+ forward: 0.9,
+ lateral: 0.1,
+ backward: 0.0
+ },
+ soundLureResistance: 0.15
+ },
+ 6: {
+ aiLevel: 12,
+ movementInterval: [7500, 8500], // 7.5-8.5秒间隔
+ movementDuration: 1000,
+ spawnDelay: 0, // Night 6立即出场
+ movementProbability: {
+ forward: 0.85, // 85%前进
+ lateral: 0.15, // 15%平移
+ backward: 0.0 // 不后退
+ },
+ soundLureResistance: 0.15
+ }
+ };
+
+ // Trump AI配置(按夜数,Night 2开始)
+ this.trumpConfig = {
+ 2: {
+ aiLevel: 10, // AI等级,10/24 = 42%移动概率
+ movementInterval: [8000, 9000], // 8-9秒随机(比EP快一点)
+ movementDuration: 1000, // 移动动画时长(毫秒)
+ spawnDelay: 0, // 出场延迟(毫秒),开局就出场
+ movementProbability: { // 移动方向概率
+ forward: 0.9, // 90% 前进(更激进)
+ lateral: 0.1, // 10% 平移
+ backward: 0.0 // 0% 后退
+ },
+ ventCrawling: { // 通风管爬行配置
+ cam1Probability: 1.0, // 在cam1时爬行概率 100%
+ cam2Probability: 0.5, // 在cam2时爬行概率 50%
+ soundDelay: 5000, // 开始爬行后多久播放音效(毫秒)
+ soundDuration: 10000, // 爬行音效持续时长(毫秒)
+ totalDuration: 20000, // 爬行总时长(毫秒)
+ retreatDelay: 2000, // 被阻止后多久播放撤退音效(毫秒)
+ retreatSoundDuration: 3000 // 撤退音效持续时长(毫秒)
+ }
+ },
+ 3: {
+ aiLevel: 11, // 降低到46%移动概率(从55%)
+ movementInterval: [9000, 10000], // 改为9-10秒(从8-9秒)
+ movementDuration: 1000,
+ spawnDelay: 0,
+ movementProbability: {
+ forward: 0.75, // 降低到75%前进(从80%)
+ lateral: 0.25, // 增加到25%平移
+ backward: 0.0
+ },
+ ventCrawling: {
+ cam1Probability: 1.0,
+ cam2Probability: 0.4, // 降低到40%(从50%)
+ soundDelay: 5000,
+ soundDuration: 10000,
+ totalDuration: 20000,
+ retreatDelay: 2000,
+ retreatSoundDuration: 3000
+ }
+ },
+ 4: {
+ aiLevel: 13,
+ movementInterval: [8000, 9000], // 8-9秒随机(比EP快)
+ movementDuration: 1000,
+ spawnDelay: 0,
+ movementProbability: {
+ forward: 0.8, // 80% 前进
+ lateral: 0.2, // 20% 平移
+ backward: 0.0
+ },
+ ventCrawling: {
+ cam1Probability: 1.0,
+ cam2Probability: 0.5,
+ soundDelay: 5000,
+ soundDuration: 10000,
+ totalDuration: 20000,
+ retreatDelay: 2000,
+ retreatSoundDuration: 3000
+ }
+ },
+ 5: {
+ aiLevel: 13,
+ movementInterval: [8000, 9000], // 8-9秒随机(比EP快)
+ movementDuration: 1000,
+ spawnDelay: 0,
+ movementProbability: {
+ forward: 0.8, // 80% 前进
+ lateral: 0.2, // 20% 平移
+ backward: 0.0
+ },
+ ventCrawling: {
+ cam1Probability: 1.0,
+ cam2Probability: 0.5,
+ soundDelay: 5000,
+ soundDuration: 10000,
+ totalDuration: 20000,
+ retreatDelay: 2000,
+ retreatSoundDuration: 3000
+ }
+ }
+ };
+
+ // 当前配置(运行时使用)
+ this.currentEpsteinConfig = null;
+ this.currentTrumpConfig = null;
+
+ // 爱泼斯坦的状态
+ this.epstein = {
+ currentLocation: 'cam11', // 起始位置(最远)
+ aiLevel: 0, // AI等级 (0-20, 分母24)
+ movementTimer: null,
+ movementInterval: 12000, // 移动检查间隔
+ hasMovedOnce: false, // 是否已经移动过一次
+ hasSpawned: false, // 是否已经出场
+ };
+
+ // 特朗普的状态
+ this.trump = {
+ currentLocation: 'cam10', // 起始位置
+ aiLevel: 0, // AI等级 (0-20)
+ movementTimer: null,
+ movementInterval: 10000, // 移动检查间隔
+ hasSpawned: false, // 是否已经出场
+ isCrawling: false, // 是否正在爬行
+ crawlingTimer: null, // 爬行计时器
+ crawlingFrom: null, // 从哪个摄像头开始爬行(cam1或cam2)
+ retreatTimer: null, // 撤退计时器
+ };
+
+ // 霍金的状态(第3关开始)
+ this.hawking = {
+ active: false, // 是否激活
+ location: 'cam6', // 固定在cam6
+ timer: null, // 计时器
+ warningLevel: 0, // 0=无警告, 1=黄色警告, 2=红色警告
+ warningTimer: null, // 警告计时器
+ attackTimer: null, // 攻击计时器
+ };
+
+ // 每个摄像头使用的角色图片(根据距离办公室远近)
+ this.characterImages = {
+ 'cam11': '/assets/images/enemyep1.png',
+ 'cam10': '/assets/images/ep1.png',
+ 'cam1': '/assets/images/ep4.png',
+ 'cam9': '/assets/images/enemyep1.png',
+ 'cam8': '/assets/images/enemyep1.png',
+ 'cam7': '/assets/images/enemyep1.png',
+ 'cam6': '/assets/images/enemyep1.png',
+ 'cam5': '/assets/images/enemyep4.png',
+ 'cam4': '/assets/images/ep1.png',
+ 'cam3': '/assets/images/ep4.png',
+ 'cam2': '/assets/images/enemyep1.png',
+ };
+
+ // Night 6 专用图片(带电眼)
+ this.characterImagesNight6 = {
+ 'cam11': '/assets/images/enemyep1_night6.png',
+ 'cam10': '/assets/images/ep1_night6.png',
+ 'cam1': '/assets/images/ep4_night6.png',
+ 'cam9': '/assets/images/enemyep1_night6.png',
+ 'cam8': '/assets/images/enemyep1_night6.png',
+ 'cam7': '/assets/images/enemyep1_night6.png',
+ 'cam6': '/assets/images/enemyep1_night6.png',
+ 'cam5': '/assets/images/enemyep4_night6.png',
+ 'cam4': '/assets/images/ep1_night6.png',
+ 'cam3': '/assets/images/ep4_night6.png',
+ 'cam2': '/assets/images/enemyep1_night6.png',
+ };
+
+ // 特朗普的图片配置(使用绝对路径)
+ this.trumpImages = {
+ 'cam10': '/assets/images/trump3.png',
+ 'cam11': '/assets/images/trump3.png',
+ 'cam9': '/assets/images/trump.png',
+ 'cam8': '/assets/images/trump5.png',
+ 'cam7': '/assets/images/trump3.png',
+ 'cam6': '/assets/images/trump3.png',
+ 'cam5': '/assets/images/trump2.png',
+ 'cam1': '/assets/images/trump4.png',
+ 'cam2': '/assets/images/trump4.png',
+ 'cam3': '/assets/images/trump2.png',
+ 'cam4': '/assets/images/trump3.png',
+ };
+
+ // 定义移动路径图(根据地图连接关系,只能向前移动)
+ // 每个位置的步长(距离办公室的最短路径长度)- 使用BFS计算
+ // Office ← Cam1 ← Cam3 ← ...
+ this.locationDepth = {
+ 'office': 0, // 终点
+ 'cam1': 1, // Cam1 → Office (1步)
+ 'cam2': 2, // Cam2 → Cam1 → Office (2步) ✅ 修正
+ 'cam3': 2, // Cam3 → Cam1 → Office (2步)
+ 'cam6': 3, // Cam6 → Cam3 → Cam1 → Office (3步)
+ 'cam4': 3, // Cam4 → Cam3 → Cam1 → Office (3步)
+ 'cam5': 4, // Cam5 → Cam6 → Cam3 → Cam1 → Office (4步)
+ 'cam7': 4, // Cam7 → Cam4 → Cam3 → Cam1 → Office (4步)
+ 'cam8': 5, // Cam8 → Cam5 → Cam6 → Cam3 → Cam1 → Office (5步)
+ 'cam11': 5, // Cam11 → Cam7 → Cam4 → Cam3 → Cam1 → Office (5步)
+ 'cam9': 5, // Cam9 → Cam7 → Cam4 → Cam3 → Cam1 → Office (5步)
+ 'cam10': 6, // Cam10 → Cam9 → Cam7 → Cam4 → Cam3 → Cam1 → Office (6步)
+ };
+
+ // 特朗普的步长配置(使用和EP相同的步长,一步一步走)
+ this.trumpLocationDepth = {
+ 'office': 0, // 终点
+ 'cam1': 1, // Cam1 → Office (1步)
+ 'cam2': 2, // Cam2 → Cam1 → Office (2步)
+ 'cam3': 2, // Cam3 → Cam1 → Office (2步)
+ 'cam6': 3, // Cam6 → Cam3 → Cam1 → Office (3步)
+ 'cam4': 3, // Cam4 → Cam3 → Cam1 → Office (3步)
+ 'cam5': 4, // Cam5 → Cam6 → Cam3 → Cam1 → Office (4步)
+ 'cam7': 4, // Cam7 → Cam4 → Cam3 → Cam1 → Office (4步)
+ 'cam8': 5, // Cam8 → Cam5 → Cam6 → Cam3 → Cam1 → Office (5步)
+ 'cam11': 5, // Cam11 → Cam7 → Cam4 → Cam3 → Cam1 → Office (5步)
+ 'cam9': 5, // Cam9 → Cam7 → Cam4 → Cam3 → Cam1 → Office (5步)
+ 'cam10': 6, // Cam10 → Cam9 → Cam7 → Cam4 → Cam3 → Cam1 → Office (6步)
+ };
+
+ // 定义移动路径图(只能向办公室方向移动,不能后退)
+ this.movementPaths = {
+ 'cam11': ['cam7', 'cam8'], // Cam11只连接Cam7和Cam8
+ 'cam9': ['cam7', 'cam10'],
+ 'cam10': ['cam9'], // 死胡同,只能去cam9
+ 'cam8': ['cam7', 'cam5'],
+ 'cam7': ['cam4'],
+ 'cam4': ['cam2', 'cam3'], // 修正:添加cam3
+ 'cam5': ['cam4', 'cam6'],
+ 'cam2': ['cam3', 'cam1'], // 修正:添加cam1
+ 'cam3': ['cam1', 'cam6'],
+ 'cam6': ['cam3'],
+ 'cam1': ['office'], // 大门,只能进办公室
+ 'office': [] // 到达办公室,游戏结束
+ };
+
+ // 每个摄像头的邻近房间(用于sound吸引)- 完整的双向连接
+ this.adjacentRooms = {
+ 'cam11': ['cam7', 'cam8'],
+ 'cam9': ['cam7', 'cam10'],
+ 'cam10': ['cam9'],
+ 'cam8': ['cam11', 'cam7', 'cam5'],
+ 'cam7': ['cam11', 'cam8', 'cam9', 'cam4'],
+ 'cam4': ['cam7', 'cam2', 'cam5', 'cam3'], // 修正:添加cam3
+ 'cam5': ['cam8', 'cam4', 'cam6'],
+ 'cam2': ['cam4', 'cam3', 'cam1'], // 修正:添加cam1
+ 'cam3': ['cam2', 'cam1', 'cam6', 'cam4'], // 修正:添加cam4
+ 'cam6': ['cam5', 'cam3'],
+ 'cam1': ['cam3', 'cam2'], // 修正:添加cam2(不包括office,因为那是终点)
+ };
+
+ // 每个摄像头的角色位置配置(CSS定位)
+ this.characterPositions = {
+ 'cam11': { left: '57.1%', bottom: '0%', width: '29%', transform: 'translateX(-50%) rotate(0deg)' },
+ 'cam10': { left: '73.8%', bottom: '1.6%', width: '89.2%', transform: 'translateX(-50%) rotate(0deg)' },
+ 'cam1': { left: '39.9%', bottom: '35.3%', width: '38.8%', transform: 'translateX(-50%) rotate(0deg)' },
+ 'cam9': { left: '18.5%', bottom: '0%', width: '29.6%', transform: 'translateX(-50%) rotate(0deg)' },
+ 'cam8': { left: '96.1%', bottom: '0%', width: '29.6%', transform: 'translateX(-50%) rotate(-23deg)' },
+ 'cam7': { left: '49.7%', bottom: '0%', width: '29.6%', transform: 'translateX(-50%) rotate(-5deg)' },
+ 'cam6': { left: '16.6%', bottom: '0%', width: '29.6%', transform: 'translateX(-50%) rotate(-5deg)' },
+ 'cam5': { left: '71.1%', bottom: '0%', width: '29.6%', transform: 'translateX(-50%) rotate(-5deg)' },
+ 'cam4': { left: '91.4%', bottom: '6.8%', width: '66.9%', transform: 'translateX(-50%) rotate(-5deg)' },
+ 'cam3': { left: '7.4%', bottom: '5%', width: '66.9%', transform: 'translateX(-50%) rotate(-5deg)' },
+ 'cam2': { left: '39.6%', bottom: '27.7%', width: '37.8%', transform: 'translateX(-50%) rotate(-139deg)' },
+ };
+
+ // 角色明暗度配置(百分比)
+ this.characterBrightness = {
+ 'cam11': 100,
+ 'cam10': 100,
+ 'cam1': 22,
+ 'cam9': 8,
+ 'cam8': 9,
+ 'cam7': 9,
+ 'cam6': 9,
+ 'cam5': 7,
+ 'cam4': 65,
+ 'cam3': 30,
+ 'cam2': 8,
+ };
+
+ // 角色旋转配置(度数)
+ this.characterRotation = {
+ 'cam11': 0,
+ 'cam10': 0,
+ 'cam1': 0,
+ 'cam9': 0,
+ 'cam8': -23,
+ 'cam7': -5,
+ 'cam6': -5,
+ 'cam5': -5,
+ 'cam4': -5,
+ 'cam3': -5,
+ 'cam2': -139,
+ };
+
+ // 电眼特效配置(Night 6 特殊夜晚)- 坐标相对于EP图片
+ this.lightningEyesConfig = {
+ 'cam11': {
+ eye1: { left: '46.3%', top: '14.8%', width: '10%', height: '10%' },
+ eye2: { left: '54.2%', top: '13.8%', width: '10%', height: '10%' }
+ },
+ 'cam10': {
+ eye1: { left: '37.0%', top: '41.7%', width: '10%', height: '10%' },
+ eye2: { left: '38.7%', top: '43.5%', width: '10%', height: '10%' }
+ },
+ 'cam1': {
+ eye1: { left: '47.7%', top: '41.6%', width: '10%', height: '10%' },
+ eye2: { left: '49.9%', top: '42.3%', width: '10%', height: '10%' }
+ },
+ 'cam9': {
+ eye1: { left: '46.8%', top: '15.1%', width: '10%', height: '10%' },
+ eye2: { left: '54.7%', top: '14.1%', width: '10%', height: '10%' }
+ },
+ 'cam8': {
+ eye1: { left: '47.1%', top: '15.8%', width: '10%', height: '10%' },
+ eye2: { left: '53.9%', top: '15.3%', width: '10%', height: '10%' }
+ },
+ 'cam7': {
+ eye1: { left: '46.3%', top: '15.6%', width: '10%', height: '10%' },
+ eye2: { left: '54.2%', top: '13.6%', width: '10%', height: '10%' }
+ },
+ 'cam6': {
+ eye1: { left: '46.8%', top: '15.4%', width: '10%', height: '10%' },
+ eye2: { left: '53.7%', top: '14.5%', width: '10%', height: '10%' }
+ },
+ 'cam5': {
+ eye1: { left: '52.2%', top: '21.3%', width: '10%', height: '10%' },
+ eye2: { left: '62.0%', top: '23.1%', width: '10%', height: '10%' }
+ },
+ 'cam4': {
+ eye1: { left: '37.1%', top: '42.4%', width: '10%', height: '10%' },
+ eye2: { left: '38.4%', top: '43.6%', width: '10%', height: '10%' }
+ },
+ 'cam3': {
+ eye1: { left: '47.7%', top: '41.4%', width: '10%', height: '10%' },
+ eye2: { left: '50.0%', top: '42.5%', width: '10%', height: '10%' }
+ },
+ 'cam2': {
+ eye1: { left: '46.1%', top: '15.3%', width: '10%', height: '10%' },
+ eye2: { left: '53.9%', top: '14.3%', width: '10%', height: '10%' }
+ }
+ };
+
+ // 特朗普的位置配置(使用调试工具设置)
+ this.trumpPositions = {
+ 'cam10': { left: '10%', bottom: '0%', width: '40%', transform: 'translateX(-50%) rotate(0deg)' },
+ 'cam11': { left: '38.2%', bottom: '0%', width: '40%', transform: 'translateX(-50%) rotate(0deg)' },
+ 'cam9': { left: '0%', bottom: '34.6%', width: '13.9%', transform: 'translateX(-50%) rotate(44deg)' },
+ 'cam8': { left: '1.5%', bottom: '24.5%', width: '20.1%', transform: 'translateX(-50%) rotate(58deg)' },
+ 'cam7': { left: '7.4%', bottom: '0%', width: '41.4%', transform: 'translateX(-50%) rotate(1deg)' },
+ 'cam6': { left: '86.3%', bottom: '0%', width: '41.4%', transform: 'translateX(-50%) rotate(1deg)' },
+ 'cam5': { left: '0%', bottom: '0%', width: '29.3%', transform: 'translateX(-50%) rotate(1deg)' },
+ 'cam1': { left: '10.8%', bottom: '15%', width: '31.6%', transform: 'translateX(-50%) rotate(1deg)' },
+ 'cam2': { left: '77.2%', bottom: '32.3%', width: '31.6%', transform: 'translateX(-50%) rotate(1deg)' },
+ 'cam3': { left: '100%', bottom: '21.4%', width: '32.9%', transform: 'translateX(-50%) rotate(-62deg)' },
+ 'cam4': { left: '11%', bottom: '0%', width: '31.6%', transform: 'translateX(-50%) rotate(1deg)' },
+ };
+
+ // 特朗普的明暗度配置
+ this.trumpBrightness = {
+ 'cam10': 31,
+ 'cam11': 100,
+ 'cam9': 29,
+ 'cam8': 28,
+ 'cam7': 28,
+ 'cam6': 28,
+ 'cam5': 12,
+ 'cam1': 40,
+ 'cam2': 31,
+ 'cam3': 19,
+ 'cam4': 31,
+ };
+
+ // 特朗普的旋转配置
+ this.trumpRotation = {
+ 'cam10': 0,
+ 'cam11': 0,
+ 'cam9': 44,
+ 'cam8': 58,
+ 'cam7': 1,
+ 'cam6': 1,
+ 'cam5': 1,
+ 'cam1': 1,
+ 'cam2': 1,
+ 'cam3': -62,
+ 'cam4': 1,
+ };
+ }
+
+ // 开始AI循环
+ start() {
+ console.log(`🎮 EnemyAI.start() called for Night ${this.game.state.currentNight}`);
+
+ // 根据夜数加载配置并设置AI等级
+ this.loadAIConfig();
+
+ console.log(`Night ${this.game.state.currentNight} - Epstein AI Config:`, this.currentEpsteinConfig);
+ console.log(`Epstein will spawn in ${this.currentEpsteinConfig.spawnDelay / 1000} seconds...`);
+
+ // 根据配置延迟后EP出场(如果AI等级>0)
+ if (this.epstein.aiLevel > 0) {
+ const spawnTimer = setTimeout(() => {
+ console.log(`⏰ Spawn timer triggered after ${this.currentEpsteinConfig.spawnDelay}ms`);
+ this.spawnEpstein();
+ }, this.currentEpsteinConfig.spawnDelay);
+
+ console.log(`⏰ Spawn timer created:`, spawnTimer);
+ } else {
+ console.log('Epstein AI level is 0, not spawning');
+ }
+
+ // Trump出场逻辑
+ if (this.currentTrumpConfig && this.trump.aiLevel > 0) {
+ console.log(`Night ${this.game.state.currentNight} - Trump AI Config:`, this.currentTrumpConfig);
+ console.log(`Trump will spawn in ${this.currentTrumpConfig.spawnDelay / 1000} seconds...`);
+
+ // 根据配置延迟后Trump出场
+ setTimeout(() => {
+ this.spawnTrump();
+ }, this.currentTrumpConfig.spawnDelay);
+ }
+
+ // Hawking激活逻辑
+ // Custom Night: 根据自定义等级决定是否激活
+ if (this.game.state.customNight && this.game.state.customAILevels.hawking > 0) {
+ console.log('Custom Night: Hawking activated at cam6!');
+ this.startHawking();
+ }
+ // 普通夜晚:Night 3-5 激活
+ else if (!this.game.state.customNight && this.game.state.currentNight >= 3 && this.game.state.currentNight <= 5) {
+ console.log('Hawking activated at cam6!');
+ this.startHawking();
+ }
+ }
+
+ // 加载AI配置
+ loadAIConfig() {
+ const night = this.game.state.currentNight;
+
+ // Custom Night (Night 7) - 使用自定义AI等级
+ if (this.game.state.customNight && night === 7) {
+ const customLevels = this.game.state.customAILevels;
+
+ // Epstein 自定义配置
+ this.currentEpsteinConfig = {
+ aiLevel: customLevels.epstein,
+ movementInterval: [9000, 10000],
+ movementDuration: 1000,
+ spawnDelay: 0,
+ movementProbability: {
+ forward: 0.9,
+ lateral: 0.1,
+ backward: 0.0
+ },
+ soundLureResistance: 0.15
+ };
+ this.epstein.aiLevel = customLevels.epstein;
+ this.epstein.movementInterval = this.getRandomInterval(this.currentEpsteinConfig.movementInterval);
+
+ // Trump 自定义配置
+ if (customLevels.trump > 0) {
+ this.currentTrumpConfig = {
+ aiLevel: customLevels.trump,
+ movementInterval: [8000, 9000],
+ movementDuration: 1000,
+ spawnDelay: 0,
+ movementProbability: {
+ forward: 0.8,
+ lateral: 0.2,
+ backward: 0.0
+ },
+ ventCrawling: {
+ cam1Probability: 1.0,
+ cam2Probability: 0.5,
+ soundDelay: 5000,
+ soundDuration: 10000,
+ totalDuration: 20000,
+ retreatDelay: 2000,
+ retreatSoundDuration: 3000
+ }
+ };
+ this.trump.aiLevel = customLevels.trump;
+ this.trump.movementInterval = this.getRandomInterval(this.currentTrumpConfig.movementInterval);
+ } else {
+ this.currentTrumpConfig = null;
+ }
+
+ // Hawking 自定义配置(通过 customAILevels.hawking 控制)
+ // Hawking 的 AI 等级不影响移动,只影响是否激活
+
+ console.log(`Custom Night AI Config loaded:`);
+ console.log(`- Epstein: Level ${this.epstein.aiLevel}`);
+ console.log(`- Trump: Level ${this.trump.aiLevel || 0}`);
+ console.log(`- Hawking: Level ${customLevels.hawking}`);
+
+ return;
+ }
+
+ // 普通夜晚配置
+ // 加载Epstein配置
+ this.currentEpsteinConfig = this.epsteinConfig[night] || this.epsteinConfig[1];
+ this.epstein.aiLevel = this.currentEpsteinConfig.aiLevel;
+ this.epstein.movementInterval = this.getRandomInterval(this.currentEpsteinConfig.movementInterval);
+
+ // 加载Trump配置(Night 2-5)
+ if (night >= 2 && night <= 5) {
+ this.currentTrumpConfig = this.trumpConfig[night] || this.trumpConfig[2];
+ this.trump.aiLevel = this.currentTrumpConfig.aiLevel;
+ this.trump.movementInterval = this.getRandomInterval(this.currentTrumpConfig.movementInterval);
+ } else {
+ this.currentTrumpConfig = null; // Night 6 没有 Trump
+ }
+
+ console.log(`AI Config loaded for Night ${night}`);
+ console.log(`- Epstein: Level ${this.epstein.aiLevel}, Interval ${this.epstein.movementInterval}ms`);
+ if (this.currentTrumpConfig) {
+ console.log(`- Trump: Level ${this.trump.aiLevel}, Interval ${this.trump.movementInterval}ms`);
+ } else {
+ console.log(`- Trump: Not active this night`);
+ }
+ }
+
+ // 从区间中随机选择一个间隔时间
+ getRandomInterval(intervalConfig) {
+ // 如果是数组,从区间中随机选择
+ if (Array.isArray(intervalConfig)) {
+ const [min, max] = intervalConfig;
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+ // 如果是数字,直接返回
+ return intervalConfig;
+ }
+
+ // EP出场
+ spawnEpstein() {
+ if (this.epstein.hasSpawned) return;
+
+ this.epstein.hasSpawned = true;
+ console.log('✅ Epstein has spawned!');
+
+ // 第一关触发摄像头故障,第二关及以后不触发
+ if (this.game.state.currentNight === 1) {
+ this.triggerCameraFailure();
+ }
+
+ // 开始移动检查循环
+ this.startMovementLoop();
+ }
+
+ // Trump出场
+ spawnTrump() {
+ if (this.trump.hasSpawned) return;
+
+ this.trump.hasSpawned = true;
+ console.log('Trump has spawned at cam10!');
+
+ // 立即更新摄像头显示(如果摄像头打开)
+ if (this.game.state.cameraOpen) {
+ this.updateCameraDisplay();
+ }
+
+ // 开始Trump的移动检查循环
+ this.startTrumpMovementLoop();
+ }
+
+ // 停止AI
+ stop() {
+ if (this.epstein.movementTimer) {
+ clearTimeout(this.epstein.movementTimer); // 改为 clearTimeout
+ this.epstein.movementTimer = null;
+ }
+ if (this.trump.movementTimer) {
+ clearTimeout(this.trump.movementTimer); // 改为 clearTimeout
+ this.trump.movementTimer = null;
+ }
+ if (this.trump.crawlingTimer) {
+ clearTimeout(this.trump.crawlingTimer);
+ this.trump.crawlingTimer = null;
+ }
+ if (this.trump.retreatTimer) {
+ clearTimeout(this.trump.retreatTimer);
+ this.trump.retreatTimer = null;
+ }
+ if (this.hawking.timer) {
+ clearTimeout(this.hawking.timer);
+ this.hawking.timer = null;
+ }
+ if (this.hawking.warningTimer) {
+ clearTimeout(this.hawking.warningTimer);
+ this.hawking.warningTimer = null;
+ }
+ if (this.hawking.attackTimer) {
+ clearTimeout(this.hawking.attackTimer);
+ this.hawking.attackTimer = null;
+ }
+ // 停止爬行音效
+ this.game.assets.stopSound('ventCrawling');
+ // 隐藏霍金警告
+ this.hideHawkingWarning();
+ }
+
+ // 开始移动检查循环
+ startMovementLoop() {
+ // 使用 setTimeout 而不是 setInterval,以支持动态间隔
+ const scheduleNextCheck = () => {
+ // Night 4特殊机制:4AM后EP变得更激进
+ let currentConfig = this.currentEpsteinConfig;
+ if (this.game.state.currentNight === 4 && this.game.state.currentTime >= 4) {
+ // 4AM后使用更激进的配置
+ currentConfig = {
+ ...this.currentEpsteinConfig,
+ movementInterval: [8000, 10000],
+ movementProbability: {
+ forward: 1.0,
+ lateral: 0.0,
+ backward: 0.0
+ }
+ };
+ }
+
+ // 从配置区间中随机选择下一次检查的间隔
+ const nextInterval = this.getRandomInterval(currentConfig.movementInterval);
+
+ this.epstein.movementTimer = setTimeout(() => {
+ this.checkMovement();
+ // 递归调度下一次检查
+ scheduleNextCheck();
+ }, nextInterval);
+ };
+
+ // 开始第一次调度
+ scheduleNextCheck();
+ }
+
+ // 开始Trump的移动检查循环
+ startTrumpMovementLoop() {
+ // 使用 setTimeout 而不是 setInterval,以支持动态间隔
+ const scheduleNextCheck = () => {
+ // Night 5特殊机制:4AM后Trump变得更激进
+ let currentConfig = this.currentTrumpConfig;
+ if (this.game.state.currentNight === 5 && this.game.state.currentTime >= 4) {
+ // 4AM后使用更激进的配置
+ currentConfig = {
+ ...this.currentTrumpConfig,
+ movementInterval: [6000, 7000], // 6-7秒间隔
+ movementProbability: {
+ forward: 1.0,
+ lateral: 0.0,
+ backward: 0.0
+ }
+ };
+ }
+
+ // 从配置区间中随机选择下一次检查的间隔
+ const nextInterval = this.getRandomInterval(currentConfig.movementInterval);
+
+ this.trump.movementTimer = setTimeout(() => {
+ this.checkTrumpMovement();
+ // 递归调度下一次检查
+ scheduleNextCheck();
+ }, nextInterval);
+ };
+
+ // 开始第一次调度
+ scheduleNextCheck();
+ }
+
+ // 检查是否移动(FNAF机制)
+ checkMovement() {
+ // 如果还未出场,不移动
+ if (!this.epstein.hasSpawned) return;
+
+ // 如果AI等级为0,不移动
+ if (this.epstein.aiLevel === 0) return;
+
+ // 如果已经在办公室,不再移动
+ if (this.epstein.currentLocation === 'office') return;
+
+ // Custom Night 使用 1-24,普通夜晚使用 1-20
+ const maxRandom = (this.game.state.customNight && this.game.state.currentNight === 7) ? 24 : 20;
+ const randomNumber = Math.floor(Math.random() * maxRandom) + 1;
+
+ // 如果随机数 <= AI等级,移动成功
+ if (randomNumber <= this.epstein.aiLevel) {
+ this.moveToNextLocation();
+ }
+ }
+
+ // 检查Trump是否移动
+ checkTrumpMovement() {
+ // 如果还未出场,不移动
+ if (!this.trump.hasSpawned) return;
+
+ // 如果AI等级为0,不移动
+ if (this.trump.aiLevel === 0) return;
+
+ // 如果已经在办公室,不再移动
+ if (this.trump.currentLocation === 'office') return;
+
+ // Custom Night 使用 1-24,普通夜晚使用 1-20
+ const maxRandom = (this.game.state.customNight && this.game.state.currentNight === 7) ? 24 : 20;
+ const randomNumber = Math.floor(Math.random() * maxRandom) + 1;
+
+ // 如果随机数 <= AI等级,移动成功
+ if (randomNumber <= this.trump.aiLevel) {
+ this.moveTrumpToNextLocation();
+ }
+ }
+
+ // 移动到下一个位置(支持前进、平移、后退)
+ moveToNextLocation() {
+ const currentLoc = this.epstein.currentLocation;
+ const currentDepth = this.locationDepth[currentLoc];
+
+ // Night 4特殊机制:4AM后EP变得更激进
+ let config = this.currentEpsteinConfig;
+ if (this.game.state.currentNight === 4 && this.game.state.currentTime >= 4) {
+ config = {
+ ...this.currentEpsteinConfig,
+ movementProbability: {
+ forward: 1.0,
+ lateral: 0.0,
+ backward: 0.0
+ }
+ };
+ // 只在第一次触发时显示日志
+ if (!this.epstein.night4AggressiveMode) {
+ console.log('⚡ Night 4: 4AM reached! EP is now in aggressive mode (forward only)');
+ this.epstein.night4AggressiveMode = true;
+ }
+ }
+
+ // 获取所有位置(不限于邻居)
+ const allLocations = Object.keys(this.locationDepth).filter(loc =>
+ loc !== 'office' && loc !== currentLoc
+ );
+
+ // 获取邻近房间
+ const adjacentLocs = this.adjacentRooms[currentLoc] || [];
+
+ // 分类所有可能的移动位置
+ // 前进:所有步长减1的位置(不限于邻近)
+ const forwardLocations = allLocations.filter(loc => this.locationDepth[loc] === currentDepth - 1);
+ // 平移:只能移动到邻近房间且步长相同的位置
+ const lateralLocations = adjacentLocs.filter(loc => this.locationDepth[loc] === currentDepth);
+ // 后退:只能移动到邻近房间且步长加1的位置
+ const backwardLocations = adjacentLocs.filter(loc => this.locationDepth[loc] === currentDepth + 1);
+
+ // 如果当前步长是1且没有前进位置,尝试移动到office
+ if (forwardLocations.length === 0 && currentDepth === 1) {
+ console.log(`Epstein moved: ${currentLoc} -> office`);
+ this.epstein.currentLocation = 'office';
+ this.triggerJumpscare('epstein');
+ return;
+ }
+
+ // 根据配置概率决定移动方向
+ const movementProb = config.movementProbability;
+ const totalProb = movementProb.forward + movementProb.lateral + movementProb.backward;
+
+ // 如果总概率为0或没有任何可移动位置,不移动
+ if (totalProb === 0 || (forwardLocations.length === 0 && lateralLocations.length === 0 && backwardLocations.length === 0)) {
+ console.log(`Epstein has no valid path from ${currentLoc}`);
+ return;
+ }
+
+ // 归一化概率(确保总和为1)
+ const normalizedProb = {
+ forward: movementProb.forward / totalProb,
+ lateral: movementProb.lateral / totalProb,
+ backward: movementProb.backward / totalProb
+ };
+
+ // 根据概率选择移动方向
+ const random = Math.random();
+ let selectedLocations = [];
+ let movementType = '';
+
+ if (random < normalizedProb.forward && forwardLocations.length > 0) {
+ // 前进
+ selectedLocations = forwardLocations;
+ movementType = 'forward';
+ } else if (random < normalizedProb.forward + normalizedProb.lateral && lateralLocations.length > 0) {
+ // 平移
+ selectedLocations = lateralLocations;
+ movementType = 'lateral';
+ } else if (backwardLocations.length > 0) {
+ // 后退
+ selectedLocations = backwardLocations;
+ movementType = 'backward';
+ } else {
+ // 如果选中的方向没有可用位置,回退到前进(默认行为)
+ if (forwardLocations.length > 0) {
+ selectedLocations = forwardLocations;
+ movementType = 'forward (fallback)';
+ } else if (lateralLocations.length > 0) {
+ selectedLocations = lateralLocations;
+ movementType = 'lateral (fallback)';
+ } else if (backwardLocations.length > 0) {
+ selectedLocations = backwardLocations;
+ movementType = 'backward (fallback)';
+ } else {
+ console.log(`Epstein has no valid path from ${currentLoc}`);
+ return;
+ }
+ }
+
+ // 从选中的方向中随机选择一个位置
+ const nextLocation = selectedLocations[Math.floor(Math.random() * selectedLocations.length)];
+
+ console.log(`Epstein moved [${movementType}]: ${currentLoc} (depth ${currentDepth}) -> ${nextLocation} (depth ${this.locationDepth[nextLocation]})`);
+
+ this.epstein.currentLocation = nextLocation;
+
+ // 播放移动音效
+ this.game.assets.playSound('blip', false, 0.5);
+
+ // 如果到达办公室,触发游戏结束
+ if (nextLocation === 'office') {
+ this.triggerJumpscare('epstein');
+ return;
+ }
+
+ // 如果摄像头打开且没有故障,无论查看哪个摄像头都播放动画
+ if (this.game.state.cameraOpen && !this.game.state.cameraFailed) {
+ this.game.camera.playMovementTransition();
+ }
+
+ // 更新摄像头显示
+ this.updateCameraDisplay();
+ }
+
+ // Trump移动到下一个位置(支持前进、平移、后退)
+ moveTrumpToNextLocation() {
+ const currentLoc = this.trump.currentLocation;
+ const currentDepth = this.trumpLocationDepth[currentLoc];
+
+ // Night 5特殊机制:4AM后Trump变得更激进
+ let config = this.currentTrumpConfig;
+ if (this.game.state.currentNight === 5 && this.game.state.currentTime >= 4) {
+ config = {
+ ...this.currentTrumpConfig,
+ movementProbability: {
+ forward: 1.0,
+ lateral: 0.0,
+ backward: 0.0
+ },
+ ventCrawling: {
+ ...this.currentTrumpConfig.ventCrawling,
+ cam1Probability: 1.0, // 4AM后在cam1必定爬行
+ cam2Probability: 0.8 // 4AM后在cam2有80%概率爬行
+ }
+ };
+ // 只在第一次触发时显示日志
+ if (!this.trump.night5AggressiveMode) {
+ console.log('⚡ Night 5: 4AM reached! Trump is now in aggressive mode (faster + more crawling)');
+ this.trump.night5AggressiveMode = true;
+ }
+ }
+
+ // 如果正在爬行,不能移动
+ if (this.trump.isCrawling) {
+ console.log('Trump is crawling, cannot move');
+ return;
+ }
+
+ // 如果在cam1,根据配置概率决定是否爬行
+ if (currentLoc === 'cam1') {
+ const shouldCrawl = Math.random() < config.ventCrawling.cam1Probability;
+ if (shouldCrawl) {
+ console.log(`Trump starting to crawl from ${currentLoc} to office (${config.ventCrawling.cam1Probability * 100}% chance)`);
+ this.startTrumpCrawling(currentLoc);
+ return;
+ }
+ }
+
+ // 如果在cam2,根据配置概率决定是否爬行
+ if (currentLoc === 'cam2') {
+ const shouldCrawl = Math.random() < config.ventCrawling.cam2Probability;
+ if (shouldCrawl) {
+ console.log(`Trump decided to crawl from ${currentLoc} to office (${config.ventCrawling.cam2Probability * 100}% chance)`);
+ this.startTrumpCrawling(currentLoc);
+ return;
+ } else {
+ console.log(`Trump decided to continue moving from ${currentLoc} (${(1 - config.ventCrawling.cam2Probability) * 100}% chance)`);
+ // 继续执行正常移动逻辑
+ }
+ }
+
+ // 获取Trump可以去的位置
+ const allLocations = Object.keys(this.trumpLocationDepth).filter(loc =>
+ loc !== 'office' && loc !== currentLoc
+ );
+
+ // 获取邻近房间
+ const adjacentLocs = this.adjacentRooms[currentLoc] || [];
+
+ // 分类所有可能的移动位置
+ // 前进:所有步长减1的位置(不限于邻近)
+ const forwardLocations = allLocations.filter(loc => this.trumpLocationDepth[loc] === currentDepth - 1);
+ // 平移:只能移动到邻近房间且步长相同的位置
+ const lateralLocations = adjacentLocs.filter(loc => this.trumpLocationDepth[loc] === currentDepth);
+ // 后退:只能移动到邻近房间且步长加1的位置
+ const backwardLocations = adjacentLocs.filter(loc => this.trumpLocationDepth[loc] === currentDepth + 1);
+
+ // 如果没有任何可移动位置,不移动
+ if (forwardLocations.length === 0 && lateralLocations.length === 0 && backwardLocations.length === 0) {
+ console.log(`Trump has no valid path from ${currentLoc}`);
+ return;
+ }
+
+ // 根据配置概率决定移动方向
+ const movementProb = config.movementProbability;
+ const totalProb = movementProb.forward + movementProb.lateral + movementProb.backward;
+
+ // 如果总概率为0,不移动
+ if (totalProb === 0) {
+ console.log(`Trump movement probability is 0`);
+ return;
+ }
+
+ // 归一化概率
+ const normalizedProb = {
+ forward: movementProb.forward / totalProb,
+ lateral: movementProb.lateral / totalProb,
+ backward: movementProb.backward / totalProb
+ };
+
+ // 根据概率选择移动方向
+ const random = Math.random();
+ let selectedLocations = [];
+ let movementType = '';
+
+ if (random < normalizedProb.forward && forwardLocations.length > 0) {
+ selectedLocations = forwardLocations;
+ movementType = 'forward';
+ } else if (random < normalizedProb.forward + normalizedProb.lateral && lateralLocations.length > 0) {
+ selectedLocations = lateralLocations;
+ movementType = 'lateral';
+ } else if (backwardLocations.length > 0) {
+ selectedLocations = backwardLocations;
+ movementType = 'backward';
+ } else {
+ // 回退到可用的方向
+ if (forwardLocations.length > 0) {
+ selectedLocations = forwardLocations;
+ movementType = 'forward (fallback)';
+ } else if (lateralLocations.length > 0) {
+ selectedLocations = lateralLocations;
+ movementType = 'lateral (fallback)';
+ } else if (backwardLocations.length > 0) {
+ selectedLocations = backwardLocations;
+ movementType = 'backward (fallback)';
+ } else {
+ console.log(`Trump has no valid path from ${currentLoc}`);
+ return;
+ }
+ }
+
+ // 从选中的方向中随机选择一个位置
+ const nextLocation = selectedLocations[Math.floor(Math.random() * selectedLocations.length)];
+
+ console.log(`Trump moved [${movementType}]: ${currentLoc} (depth ${currentDepth}) -> ${nextLocation} (depth ${this.trumpLocationDepth[nextLocation]})`);
+
+ this.trump.currentLocation = nextLocation;
+
+ // 播放移动音效
+ this.game.assets.playSound('blip', false, 0.5);
+
+ // 如果摄像头打开且没有故障,播放动画
+ if (this.game.state.cameraOpen && !this.game.state.cameraFailed) {
+ this.game.camera.playMovementTransition();
+ }
+
+ // 更新摄像头显示
+ this.updateCameraDisplay();
+ }
+
+ // Trump开始爬行进入办公室
+ startTrumpCrawling(fromLocation) {
+ const config = this.currentTrumpConfig.ventCrawling;
+
+ // 检查通风管是否已经关闭
+ if (this.game.state.ventsClosed) {
+ console.log('Trump tried to crawl but vents are already closed! Silent retreat.');
+
+ // 静默撤退 - 不播放任何音效
+ // 找出所有步长为3的位置
+ const depth3Locations = Object.keys(this.trumpLocationDepth).filter(loc =>
+ this.trumpLocationDepth[loc] === 3 && loc !== 'office'
+ );
+
+ // 如果没有步长为3的位置,使用EP的步长3位置
+ let retreatLocation;
+ if (depth3Locations.length > 0) {
+ retreatLocation = depth3Locations[Math.floor(Math.random() * depth3Locations.length)];
+ } else {
+ const epDepth3Locations = Object.keys(this.locationDepth).filter(loc =>
+ this.locationDepth[loc] === 3 && loc !== 'office'
+ );
+ retreatLocation = epDepth3Locations[Math.floor(Math.random() * epDepth3Locations.length)];
+ }
+
+ console.log(`Trump silently retreats to ${retreatLocation} (depth 3)`);
+
+ // 直接移动到撤退位置,不播放音效
+ this.trump.currentLocation = retreatLocation;
+ this.trump.isCrawling = false;
+ this.trump.crawlingFrom = null;
+
+ // 更新摄像头显示
+ this.updateCameraDisplay();
+ return;
+ }
+
+ this.trump.isCrawling = true;
+ this.trump.crawlingFrom = fromLocation; // 记录从哪里开始爬行
+ this.trump.currentLocation = 'crawling'; // 标记为爬行状态
+
+ console.log(`Trump is crawling from ${fromLocation}...`);
+ console.log(`Crawling config: soundDelay=${config.soundDelay}ms, soundDuration=${config.soundDuration}ms, totalDuration=${config.totalDuration}ms`);
+
+ // 更新摄像头显示(Trump消失)
+ this.updateCameraDisplay();
+
+ // 根据配置延迟后开始播放爬行音效
+ setTimeout(() => {
+ // 检查Trump是否还在爬行(可能已经被阻止)
+ if (this.trump.isCrawling && this.trump.currentLocation === 'crawling') {
+ console.log('Playing crawling sound...');
+ this.game.assets.playSound('ventCrawling', true, 1.0);
+
+ // 根据配置持续时长后停止爬行音效
+ setTimeout(() => {
+ if (this.trump.isCrawling && this.trump.currentLocation === 'crawling') {
+ console.log('Stopping crawling sound...');
+ this.game.assets.stopSound('ventCrawling');
+ }
+ }, config.soundDuration);
+ }
+ }, config.soundDelay);
+
+ // 根据配置总时长后到达办公室并触发跳杀
+ this.trump.crawlingTimer = setTimeout(() => {
+ console.log('Trump reached the office!');
+ this.trump.currentLocation = 'office';
+ this.trump.isCrawling = false;
+ this.trump.crawlingFrom = null;
+
+ // 停止爬行音效(如果还在播放)
+ this.game.assets.stopSound('ventCrawling');
+
+ // 触发跳杀
+ this.triggerJumpscare('trump');
+ }, config.totalDuration);
+ }
+
+ // 阻止Trump爬行(玩家关闭通风口)
+ stopTrumpCrawling() {
+ if (!this.trump.isCrawling) {
+ return false;
+ }
+
+ const config = this.currentTrumpConfig.ventCrawling;
+
+ console.log('Trump crawling blocked by closed vents!');
+
+ // 清除爬行计时器
+ if (this.trump.crawlingTimer) {
+ clearTimeout(this.trump.crawlingTimer);
+ this.trump.crawlingTimer = null;
+ }
+
+ // 停止爬行音效
+ this.game.assets.stopSound('ventCrawling');
+
+ // Trump被阻止,立即判定离开
+ this.trump.isCrawling = false;
+
+ // 找出所有步长为3的位置
+ const depth3Locations = Object.keys(this.trumpLocationDepth).filter(loc =>
+ this.trumpLocationDepth[loc] === 3 && loc !== 'office'
+ );
+
+ // 如果没有步长为3的位置,使用EP的步长3位置
+ let retreatLocation;
+ if (depth3Locations.length > 0) {
+ // 随机选择一个步长为3的位置
+ retreatLocation = depth3Locations[Math.floor(Math.random() * depth3Locations.length)];
+ } else {
+ // 使用EP的步长配置中的步长3位置
+ const epDepth3Locations = Object.keys(this.locationDepth).filter(loc =>
+ this.locationDepth[loc] === 3 && loc !== 'office'
+ );
+ retreatLocation = epDepth3Locations[Math.floor(Math.random() * epDepth3Locations.length)];
+ }
+
+ console.log(`Trump will retreat to ${retreatLocation} (depth 3)`);
+
+ // 立即移动到撤退位置
+ this.trump.currentLocation = retreatLocation;
+ this.trump.crawlingFrom = null;
+
+ // 更新摄像头显示
+ this.updateCameraDisplay();
+
+ // 根据配置延迟后播放撤退音效
+ setTimeout(() => {
+ console.log('Playing retreat crawling sound...');
+ this.game.assets.playSound('ventCrawling', false, 1.0);
+
+ // 根据配置持续时长后停止音效
+ setTimeout(() => {
+ this.game.assets.stopSound('ventCrawling');
+ }, config.retreatSoundDuration);
+ }, config.retreatDelay);
+
+ return true;
+ }
+
+ // 检查通风口状态变化(从Game.js调用)
+ onVentsChanged(ventsClosed) {
+ // 如果Trump正在爬行且玩家关闭了通风口,阻止Trump
+ if (this.trump.isCrawling && ventsClosed) {
+ this.stopTrumpCrawling();
+ }
+ }
+
+ // 使用sound吸引敌人(从CameraSystem调用)
+ attractToSound(soundLocation) {
+ let epAttracted = false;
+ let trumpAttracted = false;
+
+ // 尝试吸引EP
+ const epCurrentLoc = this.epstein.currentLocation;
+ const adjacentToEp = this.adjacentRooms[epCurrentLoc];
+
+ if (this.epstein.hasSpawned && adjacentToEp && adjacentToEp.includes(soundLocation)) {
+ // 根据配置检查是否抵抗sound吸引
+ const resistance = this.currentEpsteinConfig.soundLureResistance;
+ if (resistance > 0) {
+ const failChance = Math.random();
+ if (failChance < resistance) {
+ console.log(`Epstein resisted the sound lure! (${resistance * 100}% chance on Night ${this.game.state.currentNight})`);
+ // 吸引失败,但仍然播放音效让玩家以为成功了
+ this.game.assets.playSound('blip', false, 0.5);
+ return false; // 返回false表示没有真正吸引到
+ }
+ }
+
+ // 吸引成功,EP移动到sound位置(可以前进或后退)
+ const currentDepth = this.locationDepth[epCurrentLoc];
+ const soundDepth = this.locationDepth[soundLocation];
+ console.log(`Epstein attracted by sound: ${epCurrentLoc} (depth ${currentDepth}) -> ${soundLocation} (depth ${soundDepth})`);
+
+ this.epstein.currentLocation = soundLocation;
+
+ // 播放移动音效
+ this.game.assets.playSound('blip', false, 0.5);
+
+ // 如果到达办公室,触发游戏结束
+ if (soundLocation === 'office') {
+ this.triggerJumpscare('epstein');
+ }
+
+ epAttracted = true;
+ } else {
+ console.log(`Sound at ${soundLocation} is not adjacent to Epstein at ${epCurrentLoc}`);
+ }
+
+ // 尝试吸引Trump(如果已出场且不在爬行状态)
+ const trumpCurrentLoc = this.trump.currentLocation;
+ const adjacentToTrump = this.adjacentRooms[trumpCurrentLoc];
+
+ if (this.trump.hasSpawned && !this.trump.isCrawling && adjacentToTrump && adjacentToTrump.includes(soundLocation)) {
+ // 吸引成功,Trump移动到sound位置(可以前进或后退)
+ const currentDepth = this.trumpLocationDepth[trumpCurrentLoc];
+ const soundDepth = this.trumpLocationDepth[soundLocation];
+ console.log(`Trump attracted by sound: ${trumpCurrentLoc} (depth ${currentDepth}) -> ${soundLocation} (depth ${soundDepth})`);
+
+ this.trump.currentLocation = soundLocation;
+
+ // 播放移动音效(如果EP没有播放)
+ if (!epAttracted) {
+ this.game.assets.playSound('blip', false, 0.5);
+ }
+
+ // 如果到达办公室,触发游戏结束
+ if (soundLocation === 'office') {
+ this.triggerJumpscare('trump');
+ }
+
+ trumpAttracted = true;
+ } else if (this.trump.hasSpawned && !this.trump.isCrawling) {
+ console.log(`Sound at ${soundLocation} is not adjacent to Trump at ${trumpCurrentLoc}`);
+ }
+
+ // 注意:不在这里更新显示,由CameraSystem的动画处理
+
+ return epAttracted || trumpAttracted;
+ }
+
+ // 触发摄像头故障
+ triggerCameraFailure() {
+ console.log('Camera system failure!');
+ this.game.state.cameraFailed = true;
+
+ // 播放静态噪音
+ this.game.assets.playSound('static', true, 1.0);
+
+ // 如果摄像头打开,立即显示故障效果
+ if (this.game.state.cameraOpen) {
+ this.game.camera.showCameraFailure();
+ }
+ // 如果摄像头没打开,下次打开时会自动显示故障效果(在open()方法中)
+ }
+
+ // 更新摄像头显示
+ updateCameraDisplay() {
+ // 直接调用 CameraSystem 的显示方法
+ if (this.game.camera) {
+ this.game.camera.updateCharacterDisplay();
+ }
+ }
+
+ // 触发跳杀
+ triggerJumpscare(enemy = 'epstein') {
+ console.log(`JUMPSCARE by ${enemy}!`);
+ this.stop();
+
+ // 霍金的特殊跳杀动画
+ if (enemy === 'hawking') {
+ this.triggerHawkingMissileJumpscare();
+ return;
+ }
+
+ // 停止所有音效
+ this.game.assets.stopSound('vents');
+ this.game.assets.stopSound('static');
+
+ // 创建跳杀动画容器
+ const jumpscareContainer = document.createElement('div');
+ jumpscareContainer.id = 'jumpscare-container';
+ jumpscareContainer.style.position = 'fixed';
+ jumpscareContainer.style.top = '0';
+ jumpscareContainer.style.left = '0';
+ jumpscareContainer.style.width = '100%';
+ jumpscareContainer.style.height = '100%';
+ jumpscareContainer.style.display = 'flex';
+ jumpscareContainer.style.alignItems = 'center';
+ jumpscareContainer.style.justifyContent = 'center';
+ jumpscareContainer.style.zIndex = '99999';
+ jumpscareContainer.style.overflow = 'hidden';
+
+ // 创建办公室背景
+ const officeBackground = document.createElement('img');
+ officeBackground.src = this.game.assets.images.office.src;
+ officeBackground.style.position = 'absolute';
+ officeBackground.style.top = '0';
+ officeBackground.style.left = '0';
+ officeBackground.style.width = '100%';
+ officeBackground.style.height = '100%';
+ officeBackground.style.objectFit = 'cover';
+ officeBackground.style.zIndex = '1';
+
+ // 创建跳杀图片(居中显示)
+ const jumpscareImg = document.createElement('img');
+ // 根据敌人选择跳杀图片
+ if (enemy === 'trump') {
+ jumpscareImg.src = this.game.assets.images.trumpJumpscare?.src || this.game.assets.images.jumpscare.src;
+ } else if (enemy === 'hawking') {
+ jumpscareImg.src = this.game.assets.images.hawkingJumpscare?.src || this.game.assets.images.jumpscare.src;
+ } else {
+ jumpscareImg.src = this.game.assets.images.jumpscare.src;
+ }
+ jumpscareImg.style.position = 'absolute';
+ jumpscareImg.style.top = '50%';
+ jumpscareImg.style.left = '50%';
+ jumpscareImg.style.transform = 'translate(-50%, -50%)';
+ jumpscareImg.style.width = '25%'; // 初始大小25%
+ jumpscareImg.style.height = 'auto';
+ jumpscareImg.style.zIndex = '2';
+ jumpscareImg.style.transition = 'none';
+
+ jumpscareContainer.appendChild(officeBackground);
+ jumpscareContainer.appendChild(jumpscareImg);
+ document.body.appendChild(jumpscareContainer);
+
+ // 播放跳杀音效
+ if (enemy === 'hawking') {
+ this.game.assets.playSound('hawkingJumpscare', false, 1.0);
+ } else {
+ this.game.assets.playSound('jumpscare', false, 1.0);
+ }
+
+ // 第1帧:25% (立即显示)
+ // 已经设置
+
+ // 第2帧:50% (0.15秒后)
+ setTimeout(() => {
+ jumpscareImg.style.width = '50%';
+ }, 150);
+
+ // 第3帧:100% (0.3秒后)
+ setTimeout(() => {
+ jumpscareImg.style.width = '100%';
+ }, 300);
+
+ // 1.5秒后淡出并显示游戏结束画面
+ setTimeout(() => {
+ jumpscareContainer.style.transition = 'opacity 0.5s';
+ jumpscareContainer.style.opacity = '0';
+
+ setTimeout(() => {
+ document.body.removeChild(jumpscareContainer);
+ this.game.gameOver('GAME OVER');
+ }, 500);
+ }, 1500);
+ }
+
+ // 获取当前位置
+ getCurrentLocation() {
+ return this.epstein.currentLocation;
+ }
+
+ // 获取当前EP的图片(根据夜数选择是否带电眼)
+ getCurrentImage(cam, night) {
+ if (night === 6 && this.characterImagesNight6[cam]) {
+ return this.characterImagesNight6[cam];
+ }
+ return this.characterImages[cam];
+ }
+
+ // 获取Trump当前位置
+ getTrumpCurrentLocation() {
+ return this.trump.currentLocation;
+ }
+
+ // 检查Trump是否正在爬行
+ isTrumpCrawling() {
+ return this.trump.isCrawling;
+ }
+
+ // 重置AI
+ reset() {
+ this.stop();
+
+ // 重置 Epstein
+ this.epstein.currentLocation = 'cam11';
+ this.epstein.aiLevel = 0;
+ this.epstein.hasMovedOnce = false;
+ this.epstein.hasSpawned = false;
+ this.epstein.night4AggressiveMode = false; // 重置Night 4激进模式标志
+ if (this.epstein.timer) {
+ clearTimeout(this.epstein.timer);
+ this.epstein.timer = null;
+ }
+
+ // 重置 Trump
+ this.trump.currentLocation = 'cam10';
+ this.trump.aiLevel = 0;
+ this.trump.hasSpawned = false;
+ this.trump.isCrawling = false;
+ this.trump.crawlingFrom = null;
+ this.trump.night5AggressiveMode = false; // 重置Night 5激进模式标志
+ if (this.trump.timer) {
+ clearTimeout(this.trump.timer);
+ this.trump.timer = null;
+ }
+ if (this.trump.crawlingTimer) {
+ clearTimeout(this.trump.crawlingTimer);
+ this.trump.crawlingTimer = null;
+ }
+ if (this.trump.retreatTimer) {
+ clearTimeout(this.trump.retreatTimer);
+ this.trump.retreatTimer = null;
+ }
+
+ // 重置 Hawking
+ this.hawking.active = false;
+ this.hawking.warningLevel = 0;
+ if (this.hawking.timer) {
+ clearTimeout(this.hawking.timer);
+ this.hawking.timer = null;
+ }
+ if (this.hawking.warningTimer) {
+ clearTimeout(this.hawking.warningTimer);
+ this.hawking.warningTimer = null;
+ }
+ if (this.hawking.attackTimer) {
+ clearTimeout(this.hawking.attackTimer);
+ this.hawking.attackTimer = null;
+ }
+ this.hideHawkingWarning();
+
+ // 清除所有角色显示
+ const characterOverlay = document.getElementById('character-overlay');
+ if (characterOverlay) {
+ characterOverlay.innerHTML = '';
+ }
+
+ console.log('EnemyAI reset complete');
+ }
+
+ // 启动霍金机制
+ startHawking() {
+ this.hawking.active = true;
+ this.hawking.warningLevel = 0;
+ console.log('Hawking started at cam6');
+
+ // 根据AI等级计算警告时间
+ // AI等级越高,警告时间越短(更难)
+ // AI 1-5: 30秒 → 黄色
+ // AI 6-10: 25秒 → 黄色
+ // AI 11-15: 20秒 → 黄色
+ // AI 16-20: 18秒 → 黄色
+ let initialWarningTime = 30000; // 默认30秒
+
+ if (this.game.state.customNight && this.game.state.currentNight === 7) {
+ const hawkingLevel = this.game.state.customAILevels.hawking;
+ if (hawkingLevel >= 16) {
+ initialWarningTime = 18000; // 18秒
+ } else if (hawkingLevel >= 11) {
+ initialWarningTime = 20000; // 20秒
+ } else if (hawkingLevel >= 6) {
+ initialWarningTime = 25000; // 25秒
+ } else {
+ initialWarningTime = 30000; // 30秒
+ }
+ console.log(`Hawking AI Level ${hawkingLevel}: Initial warning in ${initialWarningTime/1000}s`);
+ } else {
+ // 普通夜晚使用默认20秒
+ initialWarningTime = 20000;
+ }
+
+ // X秒后开始黄色警告
+ this.hawking.timer = setTimeout(() => {
+ this.showHawkingWarning('yellow');
+ }, initialWarningTime);
+ }
+
+ // 显示霍金警告
+ showHawkingWarning(level) {
+ if (!this.hawking.active) return;
+
+ console.log(`Hawking warning: ${level}`);
+
+ // 根据AI等级计算警告升级时间
+ // AI等级越高,警告升级越快(更难)
+ let yellowToRedTime = 5000; // 默认5秒
+ let redToBreakTime = 5000; // 默认5秒
+
+ if (this.game.state.customNight && this.game.state.currentNight === 7) {
+ const hawkingLevel = this.game.state.customAILevels.hawking;
+ if (hawkingLevel >= 16) {
+ yellowToRedTime = 3000; // 3秒
+ redToBreakTime = 3000; // 3秒
+ } else if (hawkingLevel >= 11) {
+ yellowToRedTime = 4000; // 4秒
+ redToBreakTime = 4000; // 4秒
+ } else if (hawkingLevel >= 6) {
+ yellowToRedTime = 5000; // 5秒
+ redToBreakTime = 5000; // 5秒
+ } else {
+ yellowToRedTime = 6000; // 6秒
+ redToBreakTime = 6000; // 6秒
+ }
+ }
+
+ if (level === 'yellow') {
+ this.hawking.warningLevel = 1;
+ this.updateHawkingWarningDisplay();
+
+ // X秒后升级为红色警告
+ this.hawking.warningTimer = setTimeout(() => {
+ this.showHawkingWarning('red');
+ }, yellowToRedTime);
+ } else if (level === 'red') {
+ this.hawking.warningLevel = 2;
+ this.updateHawkingWarningDisplay();
+
+ // X秒后摄像头坏掉
+ this.hawking.warningTimer = setTimeout(() => {
+ this.hawkingBreakCamera();
+ }, redToBreakTime);
+ }
+ }
+
+ // 更新霍金警告显示
+ updateHawkingWarningDisplay() {
+ let warningIcon = document.getElementById('hawking-warning-icon');
+
+ if (!warningIcon) {
+ warningIcon = document.createElement('img');
+ warningIcon.id = 'hawking-warning-icon';
+ warningIcon.style.position = 'absolute';
+ warningIcon.style.zIndex = '1000';
+ warningIcon.style.display = 'block';
+ warningIcon.style.animation = 'flash 0.5s infinite';
+
+ // 根据摄像头状态决定添加到哪里
+ if (this.game.state.cameraOpen) {
+ const cameraGrid = document.getElementById('camera-grid');
+ if (cameraGrid) {
+ cameraGrid.appendChild(warningIcon);
+ }
+ } else {
+ document.body.appendChild(warningIcon);
+ }
+ }
+
+ // 根据摄像头状态决定位置和大小
+ if (this.game.state.cameraOpen) {
+ // 摄像头打开:在地图上 cam6 右边,使用相对于地图的百分比定位
+ // cam6 位置: x: 77.2%, y: 82.2%, width: 13.0%, height: 8.0%
+ // 警告图标放在 cam6 右边
+ warningIcon.style.position = 'absolute';
+ warningIcon.style.left = '91%'; // 77.2% + 13.0% + 小间距
+ warningIcon.style.top = '82.2%';
+ warningIcon.style.width = '11.2%'; // 8% * 1.4 = 11.2%
+ warningIcon.style.height = 'auto';
+ warningIcon.style.transform = 'none';
+
+ // 确保在地图容器中
+ const cameraGrid = document.getElementById('camera-grid');
+ if (cameraGrid && warningIcon.parentElement !== cameraGrid) {
+ cameraGrid.appendChild(warningIcon);
+ }
+ } else {
+ // 摄像头关闭:在风扇标志(氧气显示)左边,使用 fixed 定位
+ warningIcon.style.position = 'fixed';
+ warningIcon.style.left = 'auto';
+ warningIcon.style.right = 'calc(2vw + 15vw)'; // 氧气显示右边距 + 氧气显示宽度 + 间距
+ warningIcon.style.top = 'auto';
+ warningIcon.style.bottom = '2vh';
+ warningIcon.style.width = '3vw';
+ warningIcon.style.height = 'auto';
+ warningIcon.style.transform = 'none';
+
+ // 确保在 body 中
+ if (warningIcon.parentElement !== document.body) {
+ document.body.appendChild(warningIcon);
+ }
+ }
+
+ // 设置警告图片
+ if (this.hawking.warningLevel === 1) {
+ warningIcon.src = '/assets/images/Warninglight.png';
+ } else if (this.hawking.warningLevel === 2) {
+ warningIcon.src = '/assets/images/Warningheavy.png';
+ }
+ }
+
+ // 隐藏霍金警告
+ hideHawkingWarning() {
+ const warningIcon = document.getElementById('hawking-warning-icon');
+ if (warningIcon) {
+ warningIcon.remove();
+ }
+ }
+
+ // 霍金破坏摄像头
+ hawkingBreakCamera() {
+ console.log('Hawking broke the camera!');
+
+ // 隐藏警告
+ this.hideHawkingWarning();
+
+ // 摄像头坏掉
+ this.triggerCameraFailure();
+
+ // 霍金从cam6消失
+ this.hawking.active = false;
+ this.updateCameraDisplay();
+
+ // 4秒后跳杀
+ this.hawking.attackTimer = setTimeout(() => {
+ this.triggerJumpscare('hawking');
+ }, 4000);
+ }
+
+ // 霍金的导弹跳杀动画
+ triggerHawkingMissileJumpscare() {
+ console.log('Hawking missile jumpscare!');
+
+ // 停止所有音效
+ this.game.assets.stopSound('vents');
+ this.game.assets.stopSound('static');
+
+ // 创建跳杀动画容器
+ const jumpscareContainer = document.createElement('div');
+ jumpscareContainer.id = 'jumpscare-container';
+ jumpscareContainer.style.position = 'fixed';
+ jumpscareContainer.style.top = '0';
+ jumpscareContainer.style.left = '0';
+ jumpscareContainer.style.width = '100%';
+ jumpscareContainer.style.height = '100%';
+ jumpscareContainer.style.zIndex = '99999';
+ jumpscareContainer.style.overflow = 'hidden';
+ jumpscareContainer.style.backgroundColor = '#000';
+
+ // 创建办公室背景
+ const officeBackground = document.createElement('img');
+ officeBackground.src = this.game.assets.images.office.src;
+ officeBackground.style.position = 'absolute';
+ officeBackground.style.top = '0';
+ officeBackground.style.left = '0';
+ officeBackground.style.width = '100%';
+ officeBackground.style.height = '100%';
+ officeBackground.style.objectFit = 'cover';
+ officeBackground.style.zIndex = '1';
+
+ // 创建霍金图片(在房间里)
+ const hawkingImg = document.createElement('img');
+ hawkingImg.src = '/assets/images/mrstephen.png';
+ hawkingImg.style.position = 'absolute';
+ hawkingImg.style.left = '43.6%';
+ hawkingImg.style.bottom = '27.4%';
+ hawkingImg.style.width = '30%';
+ hawkingImg.style.height = 'auto';
+ hawkingImg.style.zIndex = '2';
+ hawkingImg.style.filter = 'brightness(0.68) contrast(1) saturate(1)';
+
+ // 创建导弹图片(从霍金位置飞向玩家)
+ const missileImg = document.createElement('img');
+ missileImg.src = '/assets/images/front.png';
+ missileImg.style.position = 'absolute';
+ missileImg.style.left = '25%';
+ missileImg.style.top = '40%';
+ missileImg.style.width = '5%';
+ missileImg.style.height = 'auto';
+ missileImg.style.zIndex = '3';
+ missileImg.style.transition = 'all 1s ease-out';
+
+ // 创建爆炸帧图容器(初始隐藏)
+ const explosionImg = document.createElement('div');
+ explosionImg.style.position = 'absolute';
+ explosionImg.style.top = '50%';
+ explosionImg.style.left = '50%';
+ explosionImg.style.transform = 'translate(-50%, -50%)';
+ explosionImg.style.width = '50vw'; // 容器宽度
+ explosionImg.style.height = '50vh'; // 容器高度
+ explosionImg.style.zIndex = '4';
+ explosionImg.style.backgroundImage = 'url(/assets/images/exp2.png)';
+ explosionImg.style.backgroundSize = '400% auto'; // 4列,高度自适应
+ explosionImg.style.backgroundRepeat = 'no-repeat';
+ explosionImg.style.backgroundPosition = '0% 0%';
+ explosionImg.style.display = 'none';
+
+ jumpscareContainer.appendChild(officeBackground);
+ jumpscareContainer.appendChild(hawkingImg);
+ jumpscareContainer.appendChild(missileImg);
+ jumpscareContainer.appendChild(explosionImg);
+ document.body.appendChild(jumpscareContainer);
+
+ // 播放霍金跳杀音效
+ this.game.assets.playSound('hawkingJumpscare', false, 1.0);
+
+ // 导弹飞向玩家(放大并移动到中心)
+ setTimeout(() => {
+ missileImg.style.left = '50%';
+ missileImg.style.top = '50%';
+ missileImg.style.transform = 'translate(-50%, -50%)';
+ missileImg.style.width = '80%';
+ }, 50);
+
+ // 1秒后导弹到达,开始爆炸动画
+ setTimeout(() => {
+ missileImg.style.display = 'none';
+ explosionImg.style.display = 'block';
+
+ // 播放爆炸帧动画(使用第一列的4帧,原图是4列3行)
+ let frame = 0;
+ const totalFrames = 3; // 0-3共4帧
+ const frameInterval = 80; // 每帧80ms
+
+ // 第一列的4帧对应的Y位置(手动调整后的精确位置)
+ const yPositions = [8.00, 36.50, 65.00, 93.00];
+
+ const animateExplosion = setInterval(() => {
+ // X位置固定在0%(第一列),Y位置使用预设数组
+ explosionImg.style.backgroundPosition = `0% ${yPositions[frame]}%`;
+
+ frame++;
+ if (frame > totalFrames) {
+ clearInterval(animateExplosion);
+
+ // 爆炸动画结束后淡出
+ setTimeout(() => {
+ jumpscareContainer.style.transition = 'opacity 0.5s';
+ jumpscareContainer.style.opacity = '0';
+
+ setTimeout(() => {
+ document.body.removeChild(jumpscareContainer);
+ this.game.gameOver('GAME OVER');
+ }, 500);
+ }, 200);
+ }
+ }, frameInterval);
+ }, 1000);
+ }
+
+ // 电击霍金(玩家点击按钮)
+ shockHawking() {
+ if (!this.hawking.active) {
+ console.log('Hawking is not active');
+ return false;
+ }
+
+ console.log('Hawking shocked! Resetting timer...');
+
+ // 清除所有计时器
+ if (this.hawking.timer) {
+ clearTimeout(this.hawking.timer);
+ this.hawking.timer = null;
+ }
+ if (this.hawking.warningTimer) {
+ clearTimeout(this.hawking.warningTimer);
+ this.hawking.warningTimer = null;
+ }
+ if (this.hawking.attackTimer) {
+ clearTimeout(this.hawking.attackTimer);
+ this.hawking.attackTimer = null;
+ }
+
+ // 重置警告等级
+ this.hawking.warningLevel = 0;
+ this.hideHawkingWarning();
+
+ // 播放电击音效
+ this.game.assets.playSound('hawking_shock', false, 0.8);
+
+ // 根据AI等级计算重置后的警告时间
+ let resetWarningTime = 20000; // 默认20秒
+
+ if (this.game.state.customNight && this.game.state.currentNight === 7) {
+ const hawkingLevel = this.game.state.customAILevels.hawking;
+ if (hawkingLevel >= 16) {
+ resetWarningTime = 18000; // 18秒
+ } else if (hawkingLevel >= 11) {
+ resetWarningTime = 20000; // 20秒
+ } else if (hawkingLevel >= 6) {
+ resetWarningTime = 25000; // 25秒
+ } else {
+ resetWarningTime = 30000; // 30秒
+ }
+ }
+
+ // X秒后重新开始警告
+ this.hawking.timer = setTimeout(() => {
+ this.showHawkingWarning('yellow');
+ }, resetWarningTime);
+
+ return true;
+ }
+}
diff --git a/games/five-nights-at-epsteins/js/Game.js b/games/five-nights-at-epsteins/js/Game.js
new file mode 100644
index 00000000..9bbf52bb
--- /dev/null
+++ b/games/five-nights-at-epsteins/js/Game.js
@@ -0,0 +1,1432 @@
+// 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 = `
+ DEFEND YOURSELF AGAINST TRUMP
+
+ 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.
+
+ GOT IT
+ `;
+ // 重新绑定按钮事件
+ const gotItBtn = document.getElementById('tutorial-got-it');
+ if (gotItBtn) {
+ gotItBtn.addEventListener('click', () => this.closeTutorial());
+ }
+ } else if (type === 'night3') {
+ // Night 3 教程:霍金
+ tutorialContent.innerHTML = `
+ DEFEND YOURSELF AGAINST STEPHEN HAWKING
+
+ 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.
+
+ GOT IT
+ `;
+ // 重新绑定按钮事件
+ const gotItBtn = document.getElementById('tutorial-got-it');
+ if (gotItBtn) {
+ gotItBtn.addEventListener('click', () => this.closeTutorial());
+ }
+ } else {
+ // Night 1 教程:EP
+ tutorialContent.innerHTML = `
+ DEFEND YOURSELF AGAINST EPSTEIN
+
+ 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.
+
+ GOT IT
+ `;
+ // 重新绑定按钮事件
+ 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 6(Special 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...Web version port in progress ';
+ 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));
+ }
+ }
+}
diff --git a/games/five-nights-at-epsteins/js/GameState.js b/games/five-nights-at-epsteins/js/GameState.js
new file mode 100644
index 00000000..84ce2ffa
--- /dev/null
+++ b/games/five-nights-at-epsteins/js/GameState.js
@@ -0,0 +1,43 @@
+// 游戏状态管理
+class GameState {
+ constructor() {
+ this.currentNight = 1;
+ this.maxNights = 5; // 当前版本有5晚(Night 1-5),Night 6 是特殊关卡
+ this.currentTime = 0; // 0-6 (12AM-6AM)
+ this.oxygen = 100; // 氧气替代电量
+ this.isGameRunning = false;
+ this.tutorialActive = false; // 教程是否激活
+ this.currentScene = 'office';
+ this.cameraOpen = false;
+ this.ventsClosed = false; // 通风口状态
+ this.ventsToggling = false; // 通风口是否正在切换
+ this.currentCam = 'cam11'; // 当前摄像头
+ this.cameraFailed = false; // 摄像头是否故障
+ this.cameraRestarting = false; // 摄像头是否正在重启
+ this.controlPanelBusy = false; // 控制面板是否正在处理操作
+
+ // Custom Night 状态
+ this.customNight = false; // 是否为自定义夜晚
+ this.customAILevels = {
+ epstein: 0,
+ trump: 0,
+ hawking: 0
+ };
+ }
+
+ reset() {
+ this.currentTime = 0;
+ this.oxygen = 100;
+ this.isGameRunning = true;
+ this.tutorialActive = false;
+ this.currentScene = 'office';
+ this.cameraOpen = false;
+ this.ventsClosed = false;
+ this.ventsToggling = false;
+ this.currentCam = 'cam11';
+ this.cameraFailed = false;
+ this.cameraRestarting = false;
+ this.controlPanelBusy = false;
+ // 注意:不重置 customNight 和 customAILevels,因为它们在 initGame 之前设置
+ }
+}
diff --git a/games/five-nights-at-epsteins/js/InputHandler.js b/games/five-nights-at-epsteins/js/InputHandler.js
new file mode 100644
index 00000000..e09dc32e
--- /dev/null
+++ b/games/five-nights-at-epsteins/js/InputHandler.js
@@ -0,0 +1,264 @@
+// Input handler
+class InputHandler {
+ constructor(game) {
+ this.game = game;
+ this.touchStartX = 0;
+ this.touchStartY = 0;
+ this.isTouching = false;
+ this.bindEvents();
+ }
+
+ bindEvents() {
+ // Keyboard controls
+ document.addEventListener('keydown', (e) => this.handleKeyPress(e));
+
+ // Mouse movement view control - edge trigger
+ const gameScreen = document.getElementById('game-screen');
+ gameScreen.addEventListener('mousemove', (e) => this.handleMouseMove(e));
+
+ // Touch controls for mobile
+ gameScreen.addEventListener('touchstart', (e) => this.handleTouchStart(e), { passive: false });
+ gameScreen.addEventListener('touchmove', (e) => this.handleTouchMove(e), { passive: false });
+ gameScreen.addEventListener('touchend', (e) => this.handleTouchEnd(e), { passive: false });
+ }
+
+ handleKeyPress(e) {
+ // ==================== 作弊键(生产环境请注释掉) ====================
+
+ /* // F6 作弊键:立即触发特朗普进入管道(测试音效用)
+ if (e.key === 'F6') {
+ e.preventDefault();
+ if (this.game.state.isGameRunning && this.game.enemyAI.trump.hasSpawned) {
+ console.log('🎮 CHEAT: Forcing Trump to crawl into vents...');
+ this.showCheatNotification('Trump entering vents NOW!');
+
+ // 强制特朗普从 cam1 开始爬行
+ this.game.enemyAI.trump.currentLocation = 'cam1';
+
+ // 立即播放音效(不等待延迟)- 音量改为1.0(最大值)
+ console.log('Playing crawling sound immediately...');
+ this.game.assets.playSound('ventCrawling', true, 1.0);
+
+ // 10秒后停止音效
+ setTimeout(() => {
+ console.log('Stopping crawling sound...');
+ this.game.assets.stopSound('ventCrawling');
+ }, 10000);
+ } else if (this.game.state.isGameRunning) {
+ this.showCheatNotification('Trump not spawned yet!');
+ }
+ return;
+ }
+
+ // F9 作弊键:跳过当前夜晚(调试用)
+ if (e.key === 'F9') {
+ e.preventDefault();
+ if (this.game.state.isGameRunning) {
+ console.log('🎮 CHEAT: Skipping current night...');
+
+ // 显示作弊提示
+ this.showCheatNotification('Skipping Night ' + this.game.state.currentNight);
+
+ // 延迟执行,让玩家看到提示
+ setTimeout(() => {
+ this.game.winNight();
+ }, 500);
+ }
+ return;
+ }
+
+ // F10 作弊键:解锁特殊夜晚(调试用)
+ if (e.key === 'F10') {
+ e.preventDefault();
+ console.log('🎮 CHEAT: Unlocking Special Night...');
+ localStorage.setItem('night6Unlocked', 'true');
+ this.showCheatNotification('Special Night Unlocked!');
+
+ // 如果在主菜单,立即更新按钮显示
+ if (this.game.mainMenu && !this.game.mainMenu.classList.contains('hidden')) {
+ this.game.updateContinueButton();
+ }
+ return;
+ }
+
+ // F8 作弊键:解锁Custom Night(调试用)
+ if (e.key === 'F8') {
+ e.preventDefault();
+ console.log('🎮 CHEAT: Unlocking Custom Night...');
+ localStorage.setItem('night6Completed', 'true');
+ this.showCheatNotification('Custom Night Unlocked!');
+
+ // 如果在主菜单,立即更新按钮显示
+ if (this.game.mainMenu && !this.game.mainMenu.classList.contains('hidden')) {
+ this.game.updateContinueButton();
+ }
+ return;
+ }
+
+ // F7 作弊键:时间加速(测试用)
+ if (e.key === 'F7') {
+ e.preventDefault();
+ if (this.game.state.isGameRunning) {
+ this.game.state.currentTime += 1;
+ this.game.ui.update();
+ this.showCheatNotification(`Time: ${this.game.state.currentTime} AM`);
+
+ if (this.game.state.currentTime >= 6) {
+ this.game.winNight();
+ }
+ }
+ return;
+ }
+
+ // 数字键1-6:快速跳到对应关卡(测试用,仅在主菜单有效)
+ if (e.key >= '1' && e.key <= '6') {
+ if (this.game.mainMenu && !this.game.mainMenu.classList.contains('hidden')) {
+ e.preventDefault();
+ const night = parseInt(e.key);
+ console.log(`🎮 CHEAT: Jumping to Night ${night}...`);
+ this.game.state.currentNight = night;
+ this.showCheatNotification(`Starting Night ${night}`);
+
+ // 如果是Night 6,需要先解锁
+ if (night === 6) {
+ localStorage.setItem('night6Unlocked', 'true');
+ setTimeout(() => this.game.startSpecialNight(), 500);
+ } else {
+ setTimeout(() => this.game.initGame(), 500);
+ }
+
+ this.game.mainMenu.classList.add('hidden');
+ const menuMusic = document.getElementById('menu-music');
+ if (menuMusic) {
+ menuMusic.pause();
+ menuMusic.currentTime = 0;
+ }
+ }
+ return;
+ } */
+
+ // ==================== 作弊键结束 ====================
+
+ if (!this.game.state.isGameRunning) return;
+
+ switch(e.key.toLowerCase()) {
+ case 'v':
+ this.game.toggleVents();
+ break;
+ case ' ':
+ e.preventDefault();
+ this.game.toggleCamera();
+ break;
+ }
+ }
+
+ // 作弊通知
+ showCheatNotification(message) {
+ // 创建通知元素
+ const notification = document.createElement('div');
+ notification.style.position = 'fixed';
+ notification.style.top = '10px';
+ notification.style.left = '50%';
+ notification.style.transform = 'translateX(-50%)';
+ notification.style.background = 'rgba(255, 215, 0, 0.9)';
+ notification.style.color = '#000';
+ notification.style.padding = '10px 20px';
+ notification.style.fontSize = '20px';
+ notification.style.fontWeight = 'bold';
+ notification.style.fontFamily = 'Arial, sans-serif';
+ notification.style.borderRadius = '5px';
+ notification.style.zIndex = '99999';
+ notification.style.boxShadow = '0 0 20px rgba(255, 215, 0, 0.8)';
+ notification.textContent = '🎮 CHEAT: ' + message;
+
+ document.body.appendChild(notification);
+
+ // 1秒后移除
+ setTimeout(() => {
+ notification.remove();
+ }, 1000);
+ }
+
+ handleMouseMove(e) {
+ if (!this.game.state.isGameRunning || this.game.state.cameraOpen) return;
+
+ const edgeThreshold = 100;
+ const mouseX = e.clientX;
+ const screenWidth = window.innerWidth;
+
+ // Check if at left edge
+ if (mouseX < edgeThreshold) {
+ this.game.isRotatingLeft = true;
+ this.game.isRotatingRight = false;
+ }
+ // Check if at right edge
+ else if (mouseX > screenWidth - edgeThreshold) {
+ this.game.isRotatingRight = true;
+ this.game.isRotatingLeft = false;
+ }
+ // In middle area, stop rotation
+ else {
+ this.game.isRotatingLeft = false;
+ this.game.isRotatingRight = false;
+ }
+ }
+
+ handleTouchStart(e) {
+ if (!this.game.state.isGameRunning || this.game.state.cameraOpen) return;
+
+ // Don't prevent default if touching UI elements
+ const target = e.target;
+ if (target.closest('.hotspot') || target.closest('.control-panel-button') ||
+ target.closest('.camera-button') || target.closest('#control-panel-popup')) {
+ return;
+ }
+
+ e.preventDefault();
+
+ const touch = e.touches[0];
+ this.touchStartX = touch.clientX;
+ this.touchStartY = touch.clientY;
+ this.isTouching = true;
+ }
+
+ handleTouchMove(e) {
+ if (!this.game.state.isGameRunning || this.game.state.cameraOpen || !this.isTouching) return;
+
+ // Don't prevent default if touching UI elements
+ const target = e.target;
+ if (target.closest('.hotspot') || target.closest('.control-panel-button') ||
+ target.closest('.camera-button') || target.closest('#control-panel-popup')) {
+ return;
+ }
+
+ e.preventDefault();
+
+ const touch = e.touches[0];
+ const deltaX = touch.clientX - this.touchStartX;
+ const deltaY = Math.abs(touch.clientY - this.touchStartY);
+
+ // Only rotate if horizontal swipe (not vertical)
+ if (deltaY < 50) {
+ const sensitivity = 0.002;
+ // Reverse the direction: swipe right = view right, swipe left = view left
+ const movement = -deltaX * sensitivity;
+
+ // Update view position directly
+ this.game.viewPosition += movement;
+ this.game.viewPosition = Math.max(0, Math.min(1, this.game.viewPosition));
+ this.game.ui.updateViewPosition(this.game.viewPosition);
+
+ // Update touch start position for smooth continuous movement
+ this.touchStartX = touch.clientX;
+ this.touchStartY = touch.clientY;
+ }
+ }
+
+ handleTouchEnd(e) {
+ if (!this.game.state.isGameRunning) return;
+
+ this.isTouching = false;
+ this.game.isRotatingLeft = false;
+ this.game.isRotatingRight = false;
+ }
+}
diff --git a/games/five-nights-at-epsteins/js/ScaryFaceFlicker.js b/games/five-nights-at-epsteins/js/ScaryFaceFlicker.js
new file mode 100644
index 00000000..3db28a74
--- /dev/null
+++ b/games/five-nights-at-epsteins/js/ScaryFaceFlicker.js
@@ -0,0 +1,59 @@
+// 恐怖脸闪烁效果
+const basePath = './';
+const normalBackground = `${basePath}assets/images/menubackground.png`;
+const scaryBackgrounds = [
+ `${basePath}assets/images/scaryhawk.png`,
+ `${basePath}assets/images/scaryep.png`,
+ `${basePath}assets/images/scarytrump.png`
+];
+
+let scaryFaceInterval = null;
+const preloadedImages = {};
+
+// 预加载所有背景图片
+function preloadBackgrounds() {
+ const normalImg = new Image();
+ normalImg.src = normalBackground;
+ preloadedImages['normal'] = normalImg;
+
+ scaryBackgrounds.forEach((bg, index) => {
+ const img = new Image();
+ img.src = bg;
+ preloadedImages[`scary-${index}`] = img;
+ });
+}
+
+function startScaryFaceFlicker() {
+ if (scaryFaceInterval) {
+ stopScaryFaceFlicker();
+ }
+
+ const mainMenu = document.getElementById('main-menu');
+ if (!mainMenu) return;
+
+ scaryFaceInterval = setInterval(() => {
+ if (Math.random() < 0.1) {
+ const bgIndex = Math.floor(Math.random() * 3);
+ const scaryBg = scaryBackgrounds[bgIndex];
+
+ mainMenu.style.backgroundImage = `url('${scaryBg}')`;
+
+ const hideDelay = 50 + Math.random() * 150;
+ setTimeout(() => {
+ mainMenu.style.backgroundImage = `url('${normalBackground}')`;
+ }, hideDelay);
+ }
+ }, 100);
+}
+
+function stopScaryFaceFlicker() {
+ if (scaryFaceInterval) {
+ clearInterval(scaryFaceInterval);
+ scaryFaceInterval = null;
+
+ const mainMenu = document.getElementById('main-menu');
+ if (mainMenu) {
+ mainMenu.style.backgroundImage = `url('${normalBackground}')`;
+ }
+ }
+}
diff --git a/games/five-nights-at-epsteins/js/StaticNoise.js b/games/five-nights-at-epsteins/js/StaticNoise.js
new file mode 100644
index 00000000..3fc91726
--- /dev/null
+++ b/games/five-nights-at-epsteins/js/StaticNoise.js
@@ -0,0 +1,58 @@
+// 静态噪点效果
+class StaticNoise {
+ constructor() {
+ this.canvas = document.getElementById('static-canvas');
+ if (!this.canvas) {
+ console.error('Static canvas not found');
+ return;
+ }
+
+ this.ctx = this.canvas.getContext('2d');
+ this.isRunning = false;
+ this.animationId = null;
+ this.resize();
+ window.addEventListener('resize', () => this.resize());
+ }
+
+ resize() {
+ this.canvas.width = window.innerWidth;
+ this.canvas.height = window.innerHeight;
+ }
+
+ start() {
+ if (this.isRunning) return;
+ this.isRunning = true;
+ this.canvas.style.display = 'block';
+ this.animate();
+ }
+
+ stop() {
+ this.isRunning = false;
+ this.canvas.style.display = 'none';
+ if (this.animationId) {
+ cancelAnimationFrame(this.animationId);
+ this.animationId = null;
+ }
+ }
+
+ animate() {
+ if (!this.ctx || !this.isRunning) return;
+
+ const width = this.canvas.width;
+ const height = this.canvas.height;
+ const imageData = this.ctx.createImageData(width, height);
+ const data = imageData.data;
+
+ // 生成更强烈的随机噪点(电视雪花效果)
+ for (let i = 0; i < data.length; i += 4) {
+ const value = Math.random() > 0.5 ? 255 : 0;
+ data[i] = value;
+ data[i + 1] = value;
+ data[i + 2] = value;
+ data[i + 3] = Math.random() * 255;
+ }
+
+ this.ctx.putImageData(imageData, 0, 0);
+ this.animationId = requestAnimationFrame(() => this.animate());
+ }
+}
diff --git a/games/five-nights-at-epsteins/js/UIManager.js b/games/five-nights-at-epsteins/js/UIManager.js
new file mode 100644
index 00000000..812b0f1a
--- /dev/null
+++ b/games/five-nights-at-epsteins/js/UIManager.js
@@ -0,0 +1,609 @@
+// UI Manager
+class UIManager {
+ constructor(game) {
+ this.game = game;
+ this.initElements();
+ }
+
+ initElements() {
+ this.powerValue = document.getElementById('power-value');
+ this.timeValue = document.getElementById('time-value');
+ this.nightValue = document.getElementById('night-value');
+ this.currentSceneImg = document.getElementById('current-scene');
+ }
+
+ update() {
+ this.powerValue.textContent = Math.floor(this.game.state.oxygen);
+ this.timeValue.textContent = `${this.game.state.currentTime === 0 ? 12 : this.game.state.currentTime} AM`;
+
+ // Custom Night 显示为 "7" 或 "CUSTOM"
+ if (this.game.state.customNight && this.game.state.currentNight === 7) {
+ this.nightValue.textContent = '7';
+ } else {
+ this.nightValue.textContent = this.game.state.currentNight;
+ }
+
+ // Only update scene image when camera is not open
+ if (!this.game.state.cameraOpen) {
+ const sceneKey = 'office';
+ if (this.game.assets.images[sceneKey]) {
+ this.currentSceneImg.src = this.game.assets.images[sceneKey].src;
+ this.currentSceneImg.style.display = 'block';
+ }
+ }
+
+ // Flash warning when oxygen below 40%
+ if (this.game.state.oxygen <= 40 && this.game.state.ventsClosed) {
+ this.powerValue.classList.add('flicker');
+ } else {
+ this.powerValue.classList.remove('flicker');
+ }
+
+ // Update camera status
+ this.updateCameraStatus();
+ }
+
+ createHotspots() {
+ const hotspotsContainer = document.getElementById('hotspots');
+ hotspotsContainer.innerHTML = '';
+
+ // Create control panel button (special style)
+ this.createControlPanelButton();
+
+ // Create camera button (special style)
+ this.createCameraButton();
+
+ // Bind close camera button event
+ this.bindCloseCameraButton();
+ }
+
+ createControlPanelButton() {
+ const hotspotsContainer = document.getElementById('hotspots');
+
+ const controlBtn = document.createElement('div');
+ controlBtn.id = 'vents-btn';
+ controlBtn.className = 'control-panel-button';
+ controlBtn.style.position = 'absolute';
+ controlBtn.style.left = '0';
+ controlBtn.style.bottom = '0';
+ controlBtn.style.width = '25vw';
+ controlBtn.style.height = '10vh';
+ controlBtn.style.background = 'rgba(0, 0, 0, 0.7)';
+ controlBtn.style.border = '2px solid rgba(255, 255, 255, 0.3)';
+ controlBtn.style.borderLeft = 'none';
+ controlBtn.style.borderBottom = 'none';
+ controlBtn.style.display = 'flex';
+ controlBtn.style.flexDirection = 'row';
+ controlBtn.style.alignItems = 'center';
+ controlBtn.style.justifyContent = 'space-between';
+ controlBtn.style.cursor = 'pointer';
+ controlBtn.style.opacity = '0';
+ controlBtn.style.transition = 'opacity 0.3s, background 0.3s';
+ controlBtn.style.padding = '0 1.5vw';
+ controlBtn.style.pointerEvents = 'none';
+
+ // Left arrows container
+ const leftArrows = document.createElement('div');
+ leftArrows.style.display = 'flex';
+ leftArrows.style.gap = '0.8vw';
+ leftArrows.className = 'control-arrows';
+ leftArrows.style.flexShrink = '0';
+
+ const arrowLeft1 = document.createElement('div');
+ arrowLeft1.innerHTML = '▲';
+ arrowLeft1.style.color = '#fff';
+ arrowLeft1.style.fontSize = '2vw';
+ arrowLeft1.style.lineHeight = '1';
+ leftArrows.appendChild(arrowLeft1);
+
+ const arrowLeft2 = document.createElement('div');
+ arrowLeft2.innerHTML = '▲';
+ arrowLeft2.style.color = '#fff';
+ arrowLeft2.style.fontSize = '2vw';
+ arrowLeft2.style.lineHeight = '1';
+ leftArrows.appendChild(arrowLeft2);
+
+ controlBtn.appendChild(leftArrows);
+
+ // CONTROL PANEL text
+ const text = document.createElement('div');
+ text.textContent = 'CONTROL PANEL';
+ text.style.color = '#fff';
+ text.style.fontSize = '1.8vw';
+ text.style.fontWeight = 'bold';
+ text.style.fontFamily = 'Arial, sans-serif';
+ text.style.letterSpacing = '0.15vw';
+ text.style.whiteSpace = 'nowrap';
+ text.style.flex = '1';
+ text.style.textAlign = 'center';
+ controlBtn.appendChild(text);
+
+ // Right arrows container
+ const rightArrows = document.createElement('div');
+ rightArrows.style.display = 'flex';
+ rightArrows.style.gap = '0.8vw';
+ rightArrows.className = 'control-arrows';
+ rightArrows.style.flexShrink = '0';
+
+ const arrowRight1 = document.createElement('div');
+ arrowRight1.innerHTML = '▲';
+ arrowRight1.style.color = '#fff';
+ arrowRight1.style.fontSize = '2vw';
+ arrowRight1.style.lineHeight = '1';
+ rightArrows.appendChild(arrowRight1);
+
+ const arrowRight2 = document.createElement('div');
+ arrowRight2.innerHTML = '▲';
+ arrowRight2.style.color = '#fff';
+ arrowRight2.style.fontSize = '2vw';
+ arrowRight2.style.lineHeight = '1';
+ rightArrows.appendChild(arrowRight2);
+
+ controlBtn.appendChild(rightArrows);
+
+ // Hover effect
+ controlBtn.addEventListener('mouseenter', () => {
+ controlBtn.style.background = 'rgba(0, 0, 0, 0.9)';
+ });
+
+ controlBtn.addEventListener('mouseleave', () => {
+ controlBtn.style.background = 'rgba(0, 0, 0, 0.7)';
+ });
+
+ // Click event - open control panel popup
+ controlBtn.addEventListener('click', () => {
+ this.toggleControlPanel();
+ setTimeout(() => this.updateControlPanelArrows(), 50);
+ });
+
+ hotspotsContainer.appendChild(controlBtn);
+ }
+
+ updateControlPanelArrows() {
+ const controlBtn = document.getElementById('vents-btn');
+ if (!controlBtn) return;
+
+ const arrows = controlBtn.querySelectorAll('.control-arrows div');
+ const isOpen = document.getElementById('control-panel-popup') &&
+ !document.getElementById('control-panel-popup').classList.contains('hidden');
+
+ // Before panel opens: arrows point up ▲
+ // Before panel closes: arrows point down ▼
+ arrows.forEach((arrow) => {
+ arrow.innerHTML = isOpen ? '▼' : '▲';
+ });
+ }
+
+ toggleControlPanel() {
+ const panel = document.getElementById('control-panel-popup');
+ if (panel) {
+ const wasHidden = panel.classList.contains('hidden');
+
+ // 如果要关闭面板,检查是否有操作正在进行
+ if (!wasHidden && this.game.state.controlPanelBusy) {
+ // console.log('Cannot close control panel: operation in progress');
+ return; // 阻止关闭,不显示任何消息
+ }
+
+ panel.classList.toggle('hidden');
+
+ // Control view rotation
+ if (wasHidden) {
+ // Open panel, stop rotation
+ this.game.isRotatingLeft = false;
+ this.game.isRotatingRight = false;
+ this.game.state.controlPanelOpen = true;
+ } else {
+ // Close panel
+ this.game.state.controlPanelOpen = false;
+ }
+ } else {
+ this.createControlPanelPopup();
+ this.game.isRotatingLeft = false;
+ this.game.isRotatingRight = false;
+ this.game.state.controlPanelOpen = true;
+ }
+ }
+
+ createControlPanelPopup() {
+ const popup = document.createElement('div');
+ popup.id = 'control-panel-popup';
+ popup.style.position = 'fixed';
+ popup.style.top = '10vh';
+ popup.style.left = '10vw';
+ popup.style.width = '70vw';
+ popup.style.minHeight = '60vh';
+ popup.style.background = '#000';
+ popup.style.border = '4px solid #0f0';
+ popup.style.padding = '4vh 4vw';
+ popup.style.zIndex = '100';
+ popup.style.fontFamily = "'Courier New', monospace";
+ popup.style.color = '#0f0';
+
+ // Title
+ const title = document.createElement('div');
+ title.textContent = '/// Control Panel';
+ title.style.fontSize = '2.5vw';
+ title.style.fontWeight = 'bold';
+ title.style.marginBottom = '5vh';
+ popup.appendChild(title);
+
+ // Options container
+ const optionsContainer = document.createElement('div');
+ optionsContainer.id = 'control-options';
+
+ // Option 1: Air Vents
+ const option1 = document.createElement('div');
+ option1.id = 'option-vents';
+ option1.style.fontSize = '2.5vw';
+ option1.style.marginBottom = '4vh';
+ option1.style.cursor = 'pointer';
+ option1.style.padding = '1.5vh 0';
+ option1.style.display = 'flex';
+ option1.style.alignItems = 'center';
+ option1.style.direction = 'ltr'; // 强制从左到右
+ option1.innerHTML = this.game.state.ventsClosed ?
+ '> Open Air Vents ' :
+ '> Close Air Vents ';
+ option1.addEventListener('click', () => {
+ this.game.toggleVents();
+ // 不在这里立即更新,等toggleVents完成后会自动调用updateControlPanelOptions
+ });
+ optionsContainer.appendChild(option1);
+
+ // Option 2: Restart Cameras
+ const option2 = document.createElement('div');
+ option2.id = 'option-cameras';
+ option2.style.fontSize = '2.5vw';
+ option2.style.cursor = 'pointer';
+ option2.style.padding = '1.5vh 0';
+ option2.style.display = 'flex';
+ option2.style.alignItems = 'center';
+ option2.style.direction = 'ltr'; // 强制从左到右
+ option2.innerHTML = '> Restart Cameras ';
+ option2.addEventListener('click', () => {
+ this.selectControlOption('cameras');
+ this.handleRestartCamera();
+ });
+ optionsContainer.appendChild(option2);
+
+ popup.appendChild(optionsContainer);
+
+ // Click outside to close (only if no operation in progress)
+ document.addEventListener('click', (e) => {
+ if (!popup.contains(e.target) && e.target.id !== 'vents-btn' && !e.target.closest('#vents-btn')) {
+ // 检查是否有操作正在进行
+ if (this.game.state.controlPanelBusy) {
+ // console.log('Cannot close control panel: operation in progress');
+ return; // 阻止关闭,不显示任何消息
+ }
+
+ popup.classList.add('hidden');
+ this.game.state.controlPanelOpen = false;
+ this.updateControlPanelArrows();
+ }
+ });
+
+ document.body.appendChild(popup);
+ }
+
+ selectControlOption(option) {
+ const option1 = document.getElementById('option-vents');
+ const option2 = document.getElementById('option-cameras');
+
+ if (option === 'vents') {
+ const arrow1 = option1.querySelector('.option-arrow');
+ const arrow2 = option2.querySelector('.option-arrow');
+ if (arrow1) arrow1.style.color = '#0f0';
+ if (arrow2) arrow2.style.color = 'transparent';
+
+ // 更新通风口文本(不包括dots span)
+ const text1 = option1.querySelector('span:nth-child(2)');
+ if (text1) {
+ text1.textContent = this.game.state.ventsClosed ? 'Open Air Vents' : 'Close Air Vents';
+ }
+ } else {
+ const arrow1 = option1.querySelector('.option-arrow');
+ const arrow2 = option2.querySelector('.option-arrow');
+ if (arrow1) arrow1.style.color = 'transparent';
+ if (arrow2) arrow2.style.color = '#0f0';
+ }
+ }
+
+ updateControlPanelOptions() {
+ this.selectControlOption('vents');
+ this.updateCameraStatus();
+ this.updateVentsStatus(); // 添加通风口状态更新
+ }
+
+ // Update vents status display (dots animation)
+ updateVentsStatus() {
+ const dotsSpan = document.getElementById('vents-dots');
+ if (!dotsSpan) return;
+
+ if (this.game.state.ventsToggling) {
+ // 正在切换,显示点动画
+ dotsSpan.style.color = '#0f0'; // Green dots
+ if (!dotsSpan.dataset.animating) {
+ dotsSpan.dataset.animating = 'true';
+ this.animateLoadingDots(dotsSpan);
+ }
+ } else {
+ // 不在切换中,清空点
+ dotsSpan.textContent = '';
+ delete dotsSpan.dataset.animating;
+ }
+ }
+
+ // Update camera status display
+ updateCameraStatus() {
+ const statusSpan = document.getElementById('camera-status');
+ const dotsSpan = document.getElementById('camera-dots');
+ if (!statusSpan) return;
+
+ if (this.game.state.cameraRestarting) {
+ // Restarting, show dots after button
+ if (dotsSpan) {
+ dotsSpan.style.color = '#0f0'; // Green dots
+ if (!dotsSpan.dataset.animating) {
+ dotsSpan.dataset.animating = 'true';
+ this.animateLoadingDots(dotsSpan);
+ }
+ }
+ // 只有在摄像头确实故障时才显示ERR
+ if (this.game.state.cameraFailed) {
+ statusSpan.style.color = '#f00';
+ statusSpan.textContent = 'ERR';
+ } else {
+ // 没有故障时,重启期间不显示ERR
+ statusSpan.textContent = '';
+ }
+ } else if (this.game.state.cameraFailed) {
+ // Failed, show ERR on right, no dots
+ if (dotsSpan) {
+ dotsSpan.textContent = '';
+ delete dotsSpan.dataset.animating;
+ }
+ statusSpan.style.color = '#f00';
+ statusSpan.textContent = 'ERR';
+ } else {
+ // Normal, don't show anything
+ if (dotsSpan) {
+ dotsSpan.textContent = '';
+ delete dotsSpan.dataset.animating;
+ }
+ statusSpan.textContent = '';
+ }
+ }
+
+ // Animate loading dots (green dots after button)
+ animateLoadingDots(element) {
+ const states = ['.', '..', '...'];
+ let index = 0;
+
+ const animate = () => {
+ if (!element.dataset.animating) return;
+
+ element.textContent = states[index];
+ // console.log('Dots animation:', states[index]); // 调试输出
+ index = (index + 1) % states.length;
+
+ setTimeout(animate, 500); // Switch every 0.5s
+ };
+
+ animate();
+ }
+
+ // Animate display (dots after button, ERR on right) - 不再使用
+ animateLoadingDotsWithERR(element) {
+ // 已废弃,使用 animateLoadingDots 代替
+ }
+
+ // Handle restart camera
+ handleRestartCamera() {
+ // 允许在摄像头没有故障时也能重启(作为策略使用)
+ if (!this.game.state.cameraRestarting && !this.game.state.controlPanelBusy) {
+ // console.log('Restarting cameras...');
+ this.game.camera.restartCamera();
+
+ // Immediately update status display (show loading animation, but ERR doesn't disappear)
+ this.updateCameraStatus();
+
+ // Update status display every 100ms
+ const updateInterval = setInterval(() => {
+ this.updateCameraStatus();
+ if (!this.game.state.cameraRestarting) {
+ clearInterval(updateInterval);
+ }
+ }, 100);
+ }
+ }
+
+ createCameraButton() {
+ const hotspotsContainer = document.getElementById('hotspots');
+
+ const cameraBtn = document.createElement('div');
+ cameraBtn.id = 'camera-btn';
+ cameraBtn.className = 'camera-button';
+ cameraBtn.style.position = 'absolute';
+ cameraBtn.style.right = '0';
+ cameraBtn.style.top = '25%';
+ cameraBtn.style.width = '6vw';
+ cameraBtn.style.height = '45vh';
+ cameraBtn.style.background = 'rgba(0, 0, 0, 0.7)';
+ cameraBtn.style.border = '2px solid rgba(255, 255, 255, 0.3)';
+ cameraBtn.style.borderRight = 'none';
+ cameraBtn.style.display = 'flex';
+ cameraBtn.style.flexDirection = 'column';
+ cameraBtn.style.alignItems = 'center';
+ cameraBtn.style.justifyContent = 'space-between';
+ cameraBtn.style.cursor = 'pointer';
+ cameraBtn.style.opacity = '0';
+ cameraBtn.style.transition = 'opacity 0.3s, background 0.3s';
+ cameraBtn.style.padding = '2vh 0';
+ cameraBtn.style.pointerEvents = 'none';
+
+ // Top arrows container
+ const topArrows = document.createElement('div');
+ topArrows.style.display = 'flex';
+ topArrows.style.flexDirection = 'column';
+ topArrows.style.gap = '0.5vh';
+
+ // Top arrow (points left when closed)
+ const arrowTop = document.createElement('div');
+ arrowTop.innerHTML = '◄';
+ arrowTop.className = 'camera-arrow';
+ arrowTop.style.color = '#fff';
+ arrowTop.style.fontSize = '1.8vw';
+ arrowTop.style.transform = 'rotate(0deg)';
+ arrowTop.style.lineHeight = '1';
+ topArrows.appendChild(arrowTop);
+
+ // Second arrow
+ const arrowTop2 = document.createElement('div');
+ arrowTop2.innerHTML = '◄';
+ arrowTop2.className = 'camera-arrow';
+ arrowTop2.style.color = '#fff';
+ arrowTop2.style.fontSize = '1.8vw';
+ arrowTop2.style.transform = 'rotate(0deg)';
+ arrowTop2.style.lineHeight = '1';
+ topArrows.appendChild(arrowTop2);
+
+ cameraBtn.appendChild(topArrows);
+
+ // CAMERA text (horizontal text rotated 90 degrees counterclockwise)
+ const text = document.createElement('div');
+ text.textContent = 'CAMERA';
+ text.style.color = '#fff';
+ text.style.fontSize = '1.3vw';
+ text.style.fontWeight = 'bold';
+ text.style.fontFamily = 'Arial, sans-serif';
+ text.style.transform = 'rotate(-90deg)';
+ text.style.letterSpacing = '0.2vw';
+ text.style.whiteSpace = 'nowrap';
+ cameraBtn.appendChild(text);
+
+ // Bottom arrows container
+ const bottomArrows = document.createElement('div');
+ bottomArrows.style.display = 'flex';
+ bottomArrows.style.flexDirection = 'column';
+ bottomArrows.style.gap = '0.5vh';
+
+ // Bottom arrow
+ const arrowBottom = document.createElement('div');
+ arrowBottom.innerHTML = '◄';
+ arrowBottom.className = 'camera-arrow';
+ arrowBottom.style.color = '#fff';
+ arrowBottom.style.fontSize = '1.8vw';
+ arrowBottom.style.transform = 'rotate(0deg)';
+ arrowBottom.style.lineHeight = '1';
+ bottomArrows.appendChild(arrowBottom);
+
+ // Second bottom arrow
+ const arrowBottom2 = document.createElement('div');
+ arrowBottom2.innerHTML = '◄';
+ arrowBottom2.className = 'camera-arrow';
+ arrowBottom2.style.color = '#fff';
+ arrowBottom2.style.fontSize = '1.8vw';
+ arrowBottom2.style.transform = 'rotate(0deg)';
+ arrowBottom2.style.lineHeight = '1';
+ bottomArrows.appendChild(arrowBottom2);
+
+ cameraBtn.appendChild(bottomArrows);
+
+ // Hover effect
+ cameraBtn.addEventListener('mouseenter', () => {
+ cameraBtn.style.background = 'rgba(0, 0, 0, 0.9)';
+ });
+
+ cameraBtn.addEventListener('mouseleave', () => {
+ cameraBtn.style.background = 'rgba(0, 0, 0, 0.7)';
+ });
+
+ // Click event
+ cameraBtn.addEventListener('click', () => {
+ // console.log('📷 Camera button clicked!');
+ this.game.toggleCamera();
+ // Delay arrow update, wait for state change
+ setTimeout(() => this.updateCameraButtonArrows(), 50);
+ });
+
+ hotspotsContainer.appendChild(cameraBtn);
+ }
+
+ bindCloseCameraButton() {
+ // Close button removed - camera button is now always accessible
+ }
+
+ updateCameraButtonArrows() {
+ const cameraBtn = document.getElementById('camera-btn');
+ if (!cameraBtn) return;
+
+ const arrows = cameraBtn.querySelectorAll('.camera-arrow');
+ const isOpen = this.game.state.cameraOpen;
+
+ // Update arrow direction
+ // Before opening (closed state): arrows point left (0deg)
+ // Before closing (open state): arrows point right (180deg)
+ arrows.forEach((arrow) => {
+ arrow.style.transform = isOpen ? 'rotate(180deg)' : 'rotate(0deg)';
+ });
+ }
+
+ updateHotspotVisibility(viewPosition) {
+ const ventsBtn = document.getElementById('vents-btn');
+ const cameraBtn = document.getElementById('camera-btn');
+
+ // console.log('🔍 updateHotspotVisibility - viewPosition:', viewPosition);
+
+ // Show control panel when view is at far left (viewPosition = 0)
+ if (ventsBtn) {
+ ventsBtn.style.opacity = viewPosition < 0.15 ? '1' : '0';
+ ventsBtn.style.pointerEvents = viewPosition < 0.15 ? 'auto' : 'none';
+ }
+
+ // Show camera button when view is at far right (viewPosition = 1)
+ if (cameraBtn) {
+ const isVisible = viewPosition > 0.85;
+ cameraBtn.style.opacity = isVisible ? '1' : '0';
+ cameraBtn.style.pointerEvents = isVisible ? 'auto' : 'none';
+ // console.log('📷 Camera button - opacity:', cameraBtn.style.opacity, 'pointerEvents:', cameraBtn.style.pointerEvents);
+ }
+ }
+
+ showTooltip(event, text) {
+ let tooltip = document.getElementById('game-tooltip');
+ if (!tooltip) {
+ tooltip = document.createElement('div');
+ tooltip.id = 'game-tooltip';
+ tooltip.style.position = 'fixed';
+ tooltip.style.background = 'rgba(0, 0, 0, 0.8)';
+ tooltip.style.color = 'white';
+ tooltip.style.padding = '8px 12px';
+ tooltip.style.borderRadius = '4px';
+ tooltip.style.fontSize = '14px';
+ tooltip.style.pointerEvents = 'none';
+ tooltip.style.zIndex = '10000';
+ tooltip.style.whiteSpace = 'nowrap';
+ document.body.appendChild(tooltip);
+ }
+
+ tooltip.textContent = text;
+ tooltip.style.display = 'block';
+ tooltip.style.left = event.clientX + 10 + 'px';
+ tooltip.style.top = event.clientY + 10 + 'px';
+ }
+
+ hideTooltip() {
+ const tooltip = document.getElementById('game-tooltip');
+ if (tooltip) {
+ tooltip.style.display = 'none';
+ }
+ }
+
+ updateViewPosition(viewPosition) {
+ const offset = -viewPosition * 50;
+ this.currentSceneImg.style.left = `${offset}%`;
+ this.updateHotspotVisibility(viewPosition);
+ }
+}
diff --git a/games/five-nights-at-epsteins/js/main.js b/games/five-nights-at-epsteins/js/main.js
new file mode 100644
index 00000000..5c813cc8
--- /dev/null
+++ b/games/five-nights-at-epsteins/js/main.js
@@ -0,0 +1,325 @@
+// 游戏入口 - 初始化所有模块
+let game;
+let staticNoise;
+
+// 预加载进度跟踪
+let loadedAssets = 0;
+let totalAssets = 0;
+
+// 禁用浏览器默认行为,提升游戏体验
+function disableBrowserDefaults() {
+ // 禁用右键菜单
+ document.addEventListener('contextmenu', (e) => {
+ e.preventDefault();
+ return false;
+ }, { capture: true });
+
+ // 禁用拖拽
+ document.addEventListener('dragstart', (e) => {
+ e.preventDefault();
+ return false;
+ }, { capture: true });
+
+ // 禁用选择文本(双击、长按等)
+ document.addEventListener('selectstart', (e) => {
+ e.preventDefault();
+ return false;
+ }, { capture: true });
+
+ // 禁用复制
+ document.addEventListener('copy', (e) => {
+ e.preventDefault();
+ return false;
+ }, { capture: true });
+
+ // 禁用剪切
+ document.addEventListener('cut', (e) => {
+ e.preventDefault();
+ return false;
+ }, { capture: true });
+
+ // 禁用某些快捷键
+ document.addEventListener('keydown', (e) => {
+ // 禁用 Ctrl+A (全选)
+ if (e.ctrlKey && e.key === 'a') {
+ e.preventDefault();
+ return false;
+ }
+ // 禁用 Ctrl+C (复制)
+ if (e.ctrlKey && e.key === 'c') {
+ e.preventDefault();
+ return false;
+ }
+ // 禁用 Ctrl+X (剪切)
+ if (e.ctrlKey && e.key === 'x') {
+ e.preventDefault();
+ return false;
+ }
+ // 禁用 Ctrl+S (保存)
+ if (e.ctrlKey && e.key === 's') {
+ e.preventDefault();
+ return false;
+ }
+ // 禁用 Ctrl+P (打印)
+ if (e.ctrlKey && e.key === 'p') {
+ e.preventDefault();
+ return false;
+ }
+ // 禁用 Ctrl+U (查看源代码)
+ if (e.ctrlKey && e.key === 'u') {
+ e.preventDefault();
+ return false;
+ }
+ }, { capture: true });
+
+ // 禁用触摸设备的长按菜单
+ document.addEventListener('touchstart', (e) => {
+ if (e.touches.length > 1) {
+ e.preventDefault();
+ }
+ }, { passive: false, capture: true });
+
+ // 禁用双指缩放
+ document.addEventListener('touchmove', (e) => {
+ if (e.touches.length > 1) {
+ e.preventDefault();
+ }
+ }, { passive: false, capture: true });
+
+ // 阻止鼠标选择文本
+ document.addEventListener('mousedown', (e) => {
+ // 允许按钮点击
+ if (e.target.tagName === 'BUTTON' || e.target.closest('button')) {
+ return true;
+ }
+ // 阻止其他元素的鼠标按下(防止拖拽选择)
+ if (e.detail > 1) { // 双击或多击
+ e.preventDefault();
+ return false;
+ }
+ }, { capture: true });
+
+ // console.log('Browser defaults disabled for better game experience');
+}
+
+// 更新预加载进度
+function updatePreloadProgress(progress) {
+ const progressBar = document.getElementById('progress-bar');
+ const percentage = document.getElementById('preloader-percentage');
+
+ if (progressBar && percentage) {
+ progressBar.style.width = progress + '%';
+ percentage.textContent = Math.round(progress) + '%';
+ }
+}
+
+// 预加载所有游戏资源
+async function preloadGameAssets() {
+ const basePath = './';
+
+ // 定义所有需要预加载的资源
+ const imagePaths = [
+ 'assets/images/original.png',
+ 'assets/images/Cam1.png',
+ 'assets/images/Cam2.png',
+ 'assets/images/Cam3.png',
+ 'assets/images/Cam4.png',
+ 'assets/images/Cam5.png',
+ 'assets/images/Cam6.png',
+ 'assets/images/Cam7.png',
+ 'assets/images/Cam8.png',
+ 'assets/images/Cam9.png',
+ 'assets/images/Cam10.png',
+ 'assets/images/Cam11.png',
+ 'assets/images/jump.png',
+ 'assets/images/menubackground.png',
+ 'assets/images/cutscene.png',
+ 'assets/images/fa3.png',
+ 'assets/images/FNAE-Map-layout.png',
+ 'assets/images/enemyep1.png',
+ 'assets/images/ep1.png',
+ 'assets/images/ep4.png',
+ 'assets/images/enemyep4.png',
+ 'assets/images/scaryhawk.png',
+ 'assets/images/scaryep.png',
+ 'assets/images/scarytrump.png',
+ 'assets/images/winscreen.png', // Night 5 胜利画面
+ 'assets/images/goldenstephen.png' // Golden 霍金
+ ];
+
+ const soundPaths = [
+ 'assets/sounds/music.ogg',
+ 'assets/sounds/music3.ogg',
+ 'assets/sounds/Static_sound.ogg',
+ 'assets/sounds/vents.ogg',
+ 'assets/sounds/jumpcare.ogg',
+ 'assets/sounds/Blip.ogg',
+ 'assets/sounds/winmusic.ogg',
+ 'assets/sounds/chimes.ogg',
+ 'assets/sounds/Crank1.ogg',
+ 'assets/sounds/Crank2.ogg',
+ 'assets/sounds/goldenstephenscare.ogg' // Golden 霍金音效
+ ];
+
+ totalAssets = imagePaths.length + soundPaths.length;
+ loadedAssets = 0;
+
+ // 预加载图片
+ const imagePromises = imagePaths.map(path => {
+ return new Promise((resolve) => {
+ const img = new Image();
+ img.onload = () => {
+ loadedAssets++;
+ updatePreloadProgress((loadedAssets / totalAssets) * 100);
+ resolve();
+ };
+ img.onerror = () => {
+ console.warn(`Failed to load image: ${path}`);
+ loadedAssets++;
+ updatePreloadProgress((loadedAssets / totalAssets) * 100);
+ resolve();
+ };
+ img.src = basePath + path;
+ });
+ });
+
+ // 预加载音频(不阻塞,快速加载)
+ const audioPromises = soundPaths.map(path => {
+ return new Promise((resolve) => {
+ const audio = new Audio();
+ audio.addEventListener('canplaythrough', () => {
+ loadedAssets++;
+ updatePreloadProgress((loadedAssets / totalAssets) * 100);
+ resolve();
+ }, { once: true });
+ audio.addEventListener('error', () => {
+ console.warn(`Failed to load audio: ${path}`);
+ loadedAssets++;
+ updatePreloadProgress((loadedAssets / totalAssets) * 100);
+ resolve();
+ }, { once: true });
+ audio.src = basePath + path;
+ audio.load();
+ });
+ });
+
+ // 等待所有资源加载完成
+ await Promise.all([...imagePromises, ...audioPromises]);
+
+ // 确保进度条显示100%
+ updatePreloadProgress(100);
+
+ // 等待一小段时间让玩家看到100%
+ await new Promise(resolve => setTimeout(resolve, 500));
+}
+
+// 隐藏预加载动画
+function hidePreloader() {
+ const preloader = document.getElementById('preloader');
+ if (preloader) {
+ preloader.classList.add('fade-out');
+ setTimeout(() => {
+ preloader.style.display = 'none';
+ }, 500);
+ }
+}
+
+// 页面加载完成后启动
+window.addEventListener('DOMContentLoaded', async () => {
+ // 禁用浏览器默认行为
+ disableBrowserDefaults();
+
+ // 先预加载所有资源
+ await preloadGameAssets();
+
+ // 预加载背景图片(用于恐怖脸效果)
+ preloadBackgrounds();
+
+ // 隐藏预加载动画
+ hidePreloader();
+
+ // 初始化游戏
+ game = new Game();
+ staticNoise = new StaticNoise();
+
+ // 更新Continue按钮显示
+ game.updateContinueButton();
+
+ const mainMenu = document.getElementById('main-menu');
+
+ // 检查是否从外部页面启动(带autostart参数)
+ const urlParams = new URLSearchParams(window.location.search);
+ const autostart = urlParams.get('autostart');
+
+ // 启动菜单音乐
+ const menuMusic = document.getElementById('menu-music');
+ if (menuMusic) {
+ menuMusic.volume = 0.5;
+
+ // 如果是autostart,立即尝试播放
+ if (autostart === '1') {
+ // console.log('检测到autostart参数,尝试自动播放音乐...');
+ menuMusic.play().then(() => {
+ // console.log('✅ 音乐自动播放成功!');
+ }).catch(e => {
+ // console.log('❌ 自动播放失败,等待用户交互:', e);
+ // 失败则等待用户点击
+ setupManualPlayback();
+ });
+ } else {
+ // 正常流程:等待用户点击
+ setupManualPlayback();
+ }
+
+ function setupManualPlayback() {
+ const playMusic = () => {
+ if (mainMenu && !mainMenu.classList.contains('hidden')) {
+ menuMusic.play().catch(e => {/* console.log('音乐播放需要用户交互') */});
+ }
+ document.removeEventListener('click', playMusic);
+ document.removeEventListener('keydown', playMusic);
+ };
+
+ document.addEventListener('click', playMusic);
+ document.addEventListener('keydown', playMusic);
+ }
+ }
+
+ // 监听主菜单显示/隐藏,控制雪花和鬼脸效果
+ const observer = new MutationObserver(() => {
+ if (mainMenu && !mainMenu.classList.contains('hidden')) {
+ startScaryFaceFlicker();
+ staticNoise.start();
+ } else {
+ stopScaryFaceFlicker();
+ staticNoise.stop();
+ }
+ });
+
+ if (mainMenu) {
+ observer.observe(mainMenu, { attributes: true, attributeFilter: ['class'] });
+
+ if (!mainMenu.classList.contains('hidden')) {
+ startScaryFaceFlicker();
+ staticNoise.start();
+ }
+ }
+});
+
+// 监听来自父页面的消息(iframe 通信)
+window.addEventListener('message', (event) => {
+ if (event.data.type === 'USER_CLICKED_PLAY') {
+ // console.log('收到父页面的用户点击事件');
+ const menuMusic = document.getElementById('menu-music');
+ if (menuMusic) {
+ // 立即尝试播放音乐
+ menuMusic.volume = 0.5;
+ menuMusic.play().then(() => {
+ // console.log('✅ 音乐自动播放成功!');
+ }).catch(e => {
+ // console.log('❌ 音乐播放失败:', e);
+ // 如果失败,等待用户在游戏内点击
+ });
+ }
+ }
+});
diff --git a/games/five-nights-at-epsteins/preview.png b/games/five-nights-at-epsteins/preview.png
new file mode 100644
index 00000000..5570284e
Binary files /dev/null and b/games/five-nights-at-epsteins/preview.png differ
diff --git a/games/five-nights-at-epsteins/style.css b/games/five-nights-at-epsteins/style.css
new file mode 100644
index 00000000..9f25bb7b
--- /dev/null
+++ b/games/five-nights-at-epsteins/style.css
@@ -0,0 +1,2140 @@
+html {
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ font-family: 'Arial', sans-serif;
+ background: #000;
+ overflow: hidden;
+ color: #fff;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+ height: 100%;
+ /* 禁用文本选择 */
+ user-select: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ /* 禁用触摸时的高亮 */
+ -webkit-tap-highlight-color: transparent;
+ /* 禁用拖拽 */
+ -webkit-user-drag: none;
+ -khtml-user-drag: none;
+ -moz-user-drag: none;
+ -o-user-drag: none;
+ user-drag: none;
+}
+
+/* 禁用所有元素的选择和拖拽 - 使用!important确保优先级 */
+* {
+ user-select: none !important;
+ -webkit-user-select: none !important;
+ -moz-user-select: none !important;
+ -ms-user-select: none !important;
+ -webkit-user-drag: none !important;
+ -khtml-user-drag: none !important;
+ -moz-user-drag: none !important;
+ -o-user-drag: none !important;
+ user-drag: none !important;
+ -webkit-touch-callout: none !important;
+}
+
+/* 特别针对文本元素 */
+div,
+span,
+p,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+button,
+a,
+label {
+ user-select: none !important;
+ -webkit-user-select: none !important;
+ -moz-user-select: none !important;
+ -ms-user-select: none !important;
+}
+
+/* 禁用图片拖拽 */
+img {
+ -webkit-user-drag: none !important;
+ -khtml-user-drag: none !important;
+ -moz-user-drag: none !important;
+ -o-user-drag: none !important;
+ user-drag: none !important;
+ pointer-events: none;
+}
+
+/* 确保按钮可以点击但不能选择文本 */
+button {
+ pointer-events: auto;
+ cursor: pointer;
+}
+
+/* 预加载动画 */
+#preloader {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 99999;
+ transition: opacity 0.5s ease-out;
+}
+
+#preloader.fade-out {
+ opacity: 0;
+ pointer-events: none;
+}
+
+.preloader-content {
+ text-align: center;
+ max-width: 500px;
+ padding: 20px;
+}
+
+.preloader-logo {
+ font-size: 3vw;
+ font-weight: bold;
+ color: #ff0000;
+ text-shadow:
+ 0 0 10px #ff0000,
+ 0 0 20px #ff0000,
+ 0 0 30px #ff0000,
+ 2px 2px 4px #000;
+ margin-bottom: 40px;
+ font-family: 'Courier New', monospace;
+ letter-spacing: 0.2em;
+ line-height: 1.3;
+ animation: glowPulse 2s ease-in-out infinite;
+}
+
+@keyframes glowPulse {
+
+ 0%,
+ 100% {
+ text-shadow:
+ 0 0 10px #ff0000,
+ 0 0 20px #ff0000,
+ 0 0 30px #ff0000,
+ 2px 2px 4px #000;
+ }
+
+ 50% {
+ text-shadow:
+ 0 0 20px #ff0000,
+ 0 0 30px #ff0000,
+ 0 0 40px #ff0000,
+ 0 0 50px #ff0000,
+ 2px 2px 4px #000;
+ }
+}
+
+.preloader-spinner {
+ position: relative;
+ width: 80px;
+ height: 80px;
+ margin: 0 auto 30px;
+}
+
+.spinner-ring {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ border: 3px solid transparent;
+ border-top-color: #ff0000;
+ border-radius: 50%;
+ animation: spin 1.5s linear infinite;
+}
+
+.spinner-ring:nth-child(2) {
+ width: 70%;
+ height: 70%;
+ top: 15%;
+ left: 15%;
+ border-top-color: #cc0000;
+ animation-duration: 1.2s;
+ animation-direction: reverse;
+}
+
+.spinner-ring:nth-child(3) {
+ width: 40%;
+ height: 40%;
+ top: 30%;
+ left: 30%;
+ border-top-color: #990000;
+ animation-duration: 0.9s;
+}
+
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.preloader-text {
+ font-size: 1.5vw;
+ color: #fff;
+ font-family: 'Courier New', monospace;
+ margin-bottom: 20px;
+ letter-spacing: 0.3em;
+}
+
+.loading-dots::after {
+ content: '...';
+ animation: loadingDots 1.5s steps(4, end) infinite;
+}
+
+@keyframes loadingDots {
+
+ 0%,
+ 20% {
+ content: '.';
+ }
+
+ 40% {
+ content: '..';
+ }
+
+ 60%,
+ 100% {
+ content: '...';
+ }
+}
+
+.preloader-progress {
+ width: 100%;
+ height: 4px;
+ background: #333;
+ border-radius: 2px;
+ overflow: hidden;
+ margin-bottom: 10px;
+}
+
+.progress-bar {
+ height: 100%;
+ background: linear-gradient(90deg, #ff0000, #cc0000);
+ width: 0%;
+ transition: width 0.3s ease;
+ box-shadow: 0 0 10px #ff0000;
+}
+
+.preloader-percentage {
+ font-size: 1.2vw;
+ color: #888;
+ font-family: 'Courier New', monospace;
+}
+
+/* 通用隐藏类 */
+.hidden {
+ display: none !important;
+}
+
+/* 加载动画 */
+.loading {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ font-size: 24px;
+ color: #fff;
+ font-family: 'Courier New', monospace;
+ z-index: 1000;
+ animation: pulse 1.5s infinite;
+}
+
+@keyframes pulse {
+
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.3;
+ }
+}
+
+#game-container {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ overflow: hidden;
+}
+
+/* 游戏画面 */
+#game-screen {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ display: none;
+ overflow: hidden;
+ background: #000;
+}
+
+#game-screen.active {
+ display: block;
+}
+
+#current-scene {
+ position: absolute;
+ height: 100%;
+ width: 150%;
+ object-fit: cover;
+ left: 0;
+ top: 0;
+ transition: left 0.05s linear;
+ display: block;
+}
+
+/* 热区 */
+#hotspots {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+}
+
+.hotspot {
+ position: absolute;
+ cursor: pointer;
+ pointer-events: auto;
+ transition: opacity 0.2s;
+}
+
+.hotspot:hover {
+ opacity: 0.8;
+}
+
+/* UI 覆盖层 */
+#ui-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 10;
+ font-family: 'Courier New', monospace;
+ font-weight: bold;
+}
+
+/* 左上角 UI */
+#top-left-ui {
+ position: absolute;
+ top: 2vh;
+ left: 2vw;
+ color: white;
+ text-shadow: 2px 2px 4px #000;
+}
+
+#time-display {
+ font-size: 2.5vw;
+ margin-bottom: 0.5vh;
+}
+
+#night-display {
+ font-size: 1.5vw;
+ color: #ccc;
+}
+
+/* 右下角 UI */
+#bottom-right-ui {
+ position: absolute;
+ bottom: 2vh;
+ right: 2vw;
+ color: white;
+ text-shadow: 2px 2px 4px #000;
+}
+
+#oxygen-display {
+ display: flex;
+ align-items: center;
+ gap: 0;
+ font-size: 2vw;
+ font-weight: bold;
+}
+
+#oxygen-display .vent-icon {
+ margin-right: 1vw;
+}
+
+#power-value {
+ margin: 0;
+}
+
+.percent-sign {
+ margin-left: 0.1vw;
+ margin-right: 0;
+}
+
+.oxygen-unit {
+ margin-left: 1.2vw;
+ display: inline-flex;
+ align-items: baseline;
+}
+
+#oxygen-display sub {
+ font-size: 1vw;
+ vertical-align: sub;
+ line-height: 0;
+}
+
+.vent-icon {
+ width: 3vw;
+ height: 3vw;
+ object-fit: contain;
+ transition: none;
+ /* 禁用默认过渡,使用animation */
+ animation: spin-fast 0.333s linear infinite;
+ /* 默认快速旋转(通风口开启)- 1秒3圈 */
+}
+
+/* 风扇旋转动画 - 快速(通风口开启) */
+@keyframes spin-fast {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* 风扇旋转动画 - 慢速(减速中) */
+@keyframes spin-slow {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* 风扇停止状态 */
+.vent-icon.stopped {
+ animation: none;
+}
+
+/* 风扇减速状态 */
+.vent-icon.slowing {
+ animation: spin-slow 3s linear infinite;
+}
+
+/* 风扇加速状态 */
+.vent-icon.speeding-up {
+ animation: spin-slow 3s linear infinite;
+}
+
+animation: spin 0.8s linear infinite;
+}
+
+@keyframes spin {
+ from {
+ transform: rotate(0deg);
+ }
+
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+/* 游戏按钮 */
+.game-button {
+ position: absolute;
+ width: 8vw;
+ height: 8vh;
+ background: rgba(255, 0, 0, 0.1);
+ border: 2px solid rgba(255, 0, 0, 0.3);
+ cursor: pointer;
+ transition: all 0.3s;
+ pointer-events: auto;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.game-button:hover {
+ background: rgba(255, 0, 0, 0.3);
+ border-color: rgba(255, 0, 0, 0.8);
+ box-shadow: 0 0 20px rgba(255, 0, 0, 0.5);
+}
+
+.game-button .button-hint {
+ opacity: 0;
+ color: white;
+ font-size: 1.2vw;
+ font-weight: bold;
+ text-shadow: 2px 2px 4px #000;
+ transition: opacity 0.3s;
+ pointer-events: none;
+}
+
+.game-button:hover .button-hint {
+ opacity: 1;
+}
+
+/* 左下角:控制面板按钮 */
+#control-panel-btn {
+ bottom: 2vh;
+ left: 2vw;
+}
+
+/* 右侧中间:摄像头按钮 */
+#camera-btn {
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ height: 15vh;
+ width: 5vw;
+}
+
+/* 摄像头面板 - 从右往左滑入 + 缩放效果 */
+#camera-panel {
+ position: absolute;
+ top: 5vh;
+ right: 7vw;
+ width: 70vw;
+ height: calc(70vw * 0.75);
+ /* 保持4:3宽高比 */
+ max-height: 80vh;
+ /* 但不超过80vh */
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-color: #000;
+ z-index: 20;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: flex-end;
+ padding: 0;
+ transform: translateX(calc(100% + 7vw)) scale(0.5);
+ transform-origin: right center;
+ transition: transform 0.4s ease-in-out;
+ border: 3px solid #444;
+ box-shadow: -5px 0 20px rgba(0, 0, 0, 0.7);
+ opacity: 0;
+ overflow: hidden;
+ /* 防止EP图片溢出到摄像头面板外 */
+}
+
+#camera-panel.show {
+ transform: translateX(0) scale(1);
+ opacity: 1;
+}
+
+#camera-panel.closing {
+ transform: translateX(calc(100% + 7vw)) scale(0.5);
+ opacity: 0;
+}
+
+#camera-panel.transitioning {
+ background-image: none !important;
+}
+
+/* 当前摄像头标签 */
+#current-cam-label {
+ position: absolute;
+ top: 15px;
+ left: 15px;
+ font-size: 24px;
+ font-weight: bold;
+ color: #fff;
+ font-family: Arial, sans-serif;
+ text-shadow: 2px 2px 4px #000;
+ z-index: 1;
+}
+
+/* 角色图层容器 */
+#character-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 1;
+}
+
+#character-overlay img {
+ position: absolute;
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+ opacity: 0;
+ transition: opacity 0.3s;
+}
+
+#character-overlay img.visible {
+ opacity: 1;
+}
+
+/* 摄像头切换静态噪点 */
+#camera-static-video {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ pointer-events: none;
+ z-index: 2;
+ opacity: 0;
+ transition: opacity 0.2s;
+ display: none;
+}
+
+#camera-static-video.active {
+ display: block;
+ opacity: 0.8;
+}
+
+/* 摄像头故障标识 */
+#camera-error-label {
+ position: absolute;
+ top: 15px;
+ right: 25px;
+ font-size: 48px;
+ font-weight: bold;
+ color: #f00;
+ font-family: Arial, sans-serif;
+ text-shadow: 2px 2px 4px #000;
+ z-index: 3;
+ display: none;
+}
+
+#camera-error-label.active {
+ display: block;
+ /* 移除闪烁动画 */
+}
+
+/* 地图容器 - 移到左下角 */
+#camera-grid {
+ position: absolute;
+ bottom: 15px;
+ left: 15px;
+ width: 28vw;
+ max-width: 380px;
+ z-index: 1;
+}
+
+.camera-hotspot {
+ position: absolute;
+ cursor: pointer;
+ border-radius: 4px;
+ transition: all 0.2s;
+}
+
+.camera-hotspot:hover {
+ transform: scale(1.05);
+}
+
+/* 选中的摄像头绿色闪烁动画 */
+.camera-selected {
+ animation: greenPulse 1s ease-in-out infinite;
+}
+
+@keyframes greenPulse {
+
+ 0%,
+ 100% {
+ background: rgba(0, 255, 0, 0.6);
+ }
+
+ 50% {
+ background: rgba(0, 255, 0, 0.2);
+ }
+}
+
+/* ELECTROCUTE 按钮(霍金电击) - 只在cam6显示 */
+#shock-hawking-btn {
+ position: absolute;
+ bottom: 100px;
+ right: 25px;
+ width: 280px;
+ padding: 20px 0;
+ font-size: 24px;
+ background: rgba(0, 0, 0, 0.7);
+ border: 3px solid #fff;
+ color: #fff;
+ cursor: pointer;
+ transition: all 0.3s;
+ font-family: Arial, sans-serif;
+ font-weight: bold;
+ letter-spacing: 4px;
+ z-index: 1;
+ text-align: center;
+ display: none;
+ /* 默认隐藏 */
+ box-sizing: border-box;
+}
+
+#shock-hawking-btn:hover {
+ background: rgba(255, 255, 255, 0.2);
+ transform: scale(1.05);
+}
+
+#shock-hawking-btn:active {
+ transform: scale(0.95);
+}
+
+/* PLAY SOUND 按钮 */
+#play-sound-btn {
+ position: absolute;
+ bottom: 25px;
+ right: 25px;
+ width: 280px;
+ padding: 20px 0;
+ font-size: 24px;
+ background: rgba(0, 0, 0, 0.7);
+ border: 3px solid #fff;
+ color: #fff;
+ cursor: pointer;
+ transition: all 0.3s;
+ font-family: Arial, sans-serif;
+ font-weight: bold;
+ letter-spacing: 4px;
+ z-index: 1;
+ text-align: center;
+ box-sizing: border-box;
+}
+
+#play-sound-btn:hover {
+ background: rgba(255, 255, 255, 0.2);
+ transform: scale(1.05);
+}
+
+/* 摄像头关闭按钮 - 手机端显示 */
+#close-camera {
+ display: none;
+}
+
+/* 主菜单 */
+#main-menu {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: url('/assets/images/menubackground.png') center/cover;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+ padding-left: 5%;
+ z-index: 100;
+}
+
+#main-menu>h1 {
+ margin-top: -10vh;
+ /* 整体上移 */
+}
+
+/* 星星图标 - 放在中间偏上位置 */
+#star-icon {
+ position: absolute;
+ top: 15vh;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 5vw;
+ max-width: 80px;
+ height: auto;
+ z-index: 103;
+ animation: star-glow 2s ease-in-out infinite;
+}
+
+/* 第二颗星星 - 在第一颗下面 */
+#star-icon-2 {
+ position: absolute;
+ top: 22vh;
+ /* 比第一颗低7vh */
+ left: 50%;
+ transform: translateX(-50%);
+ width: 5vw;
+ max-width: 80px;
+ height: auto;
+ z-index: 103;
+ animation: star-glow 2s ease-in-out infinite 0.5s;
+ /* 延迟0.5s,产生交替闪烁效果 */
+}
+
+/* 第三颗星星 - 在第二颗下面 (20/20/20通关) */
+#star-icon-3 {
+ position: absolute;
+ top: 29vh;
+ /* 比第二颗低7vh */
+ left: 50%;
+ transform: translateX(-50%);
+ width: 5vw;
+ max-width: 80px;
+ height: auto;
+ z-index: 103;
+ animation: star-glow 2s ease-in-out infinite 1s;
+ /* 延迟1s,产生交替闪烁效果 */
+}
+
+@keyframes star-glow {
+
+ 0%,
+ 100% {
+ filter: drop-shadow(0 0 10px rgba(255, 215, 0, 0.8));
+ transform: translateX(-50%) scale(1);
+ }
+
+ 50% {
+ filter: drop-shadow(0 0 20px rgba(255, 215, 0, 1));
+ transform: translateX(-50%) scale(1.05);
+ }
+}
+
+/* Golden 霍金闪烁动画 */
+@keyframes golden-flicker {
+ 0% {
+ opacity: 0;
+ }
+
+ 10% {
+ opacity: 1;
+ }
+
+ 20% {
+ opacity: 0;
+ }
+
+ 30% {
+ opacity: 1;
+ }
+
+ 40% {
+ opacity: 0.5;
+ }
+
+ 50% {
+ opacity: 1;
+ }
+
+ 60% {
+ opacity: 0.8;
+ }
+
+ 70% {
+ opacity: 1;
+ }
+
+ 80% {
+ opacity: 0.6;
+ }
+
+ 90% {
+ opacity: 1;
+ }
+
+ 100% {
+ opacity: 0;
+ }
+}
+
+/* 静态雪花效果 - 使用 canvas */
+#static-canvas {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 101;
+ opacity: 0.5;
+ /* 增强透明度,从 0.25 改成 0.5 */
+ mix-blend-mode: screen;
+ /* 改用 screen 模式,更明显 */
+}
+
+#main-menu>h1,
+#main-menu>button,
+#main-menu>img {
+ position: relative;
+ z-index: 102;
+}
+
+#main-menu h1 {
+ font-size: 5vw;
+ margin-bottom: 5vh;
+ text-shadow: 4px 4px 8px rgba(0, 0, 0, 0.8);
+ color: #fff;
+ font-family: 'Courier New', monospace;
+ font-weight: bold;
+ letter-spacing: 2px;
+ line-height: 1.2;
+}
+
+/* 音量按钮 */
+#volume-btn {
+ position: fixed;
+ top: 2vh;
+ right: 2vw;
+ padding: 15px 25px;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ z-index: 102;
+ /* 在白噪声(101)上面,但低于其他UI(103) */
+ transition: all 0.2s;
+ font-family: 'Courier New', monospace;
+ font-size: 18px;
+ font-weight: bold;
+ color: #fff;
+ letter-spacing: 3px;
+ text-shadow: 4px 4px 8px rgba(0, 0, 0, 0.8);
+}
+
+#volume-btn:hover {
+ transform: scale(1.05);
+ text-shadow: 4px 4px 12px rgba(0, 0, 0, 1);
+}
+
+/* 音量设置面板 */
+#volume-panel {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: rgba(0, 0, 0, 0.98);
+ border: 2px solid #666;
+ border-radius: 0;
+ padding: 20px;
+ z-index: 105;
+ /* 面板要在最上面 */
+ min-width: 400px;
+ max-width: 90vw;
+ max-height: 90vh;
+ overflow-y: auto;
+ box-shadow: 0 0 30px rgba(0, 0, 0, 0.9), inset 0 0 20px rgba(0, 0, 0, 0.5);
+}
+
+#volume-panel h3 {
+ color: #ff0000;
+ font-family: 'Courier New', monospace;
+ font-size: 18px;
+ margin-bottom: 15px;
+ margin-top: 0;
+ text-align: center;
+ letter-spacing: 3px;
+ text-shadow:
+ 0 0 10px #ff0000,
+ 0 0 20px #ff0000,
+ 2px 2px 4px #000;
+}
+
+.volume-item {
+ margin-bottom: 15px;
+}
+
+.volume-item label {
+ color: #ff0000;
+ font-family: 'Courier New', monospace;
+ font-size: 12px;
+ display: block;
+ margin-bottom: 6px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ text-shadow:
+ 0 0 5px #ff0000,
+ 0 0 10px #ff0000;
+}
+
+.volume-slider-container {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.volume-slider-container input[type="range"] {
+ flex: 1;
+ height: 3px;
+ background: #333;
+ outline: none;
+ border-radius: 0;
+ cursor: pointer;
+ border: 1px solid #555;
+}
+
+.volume-slider-container input[type="range"]::-webkit-slider-thumb {
+ appearance: none;
+ width: 12px;
+ height: 12px;
+ background: #666;
+ border-radius: 0;
+ cursor: pointer;
+ border: 1px solid #888;
+}
+
+.volume-slider-container input[type="range"]::-moz-range-thumb {
+ width: 12px;
+ height: 12px;
+ background: #666;
+ border-radius: 0;
+ cursor: pointer;
+ border: 1px solid #888;
+}
+
+.volume-percent {
+ color: #777;
+ font-family: 'Courier New', monospace;
+ font-size: 13px;
+ min-width: 45px;
+ text-align: right;
+}
+
+#close-volume-panel {
+ width: 100%;
+ padding: 8px;
+ margin-top: 10px;
+ background: #222;
+ color: #888;
+ border: 1px solid #555;
+ border-radius: 0;
+ font-family: 'Courier New', monospace;
+ font-size: 13px;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.2s;
+ letter-spacing: 2px;
+}
+
+#close-volume-panel:hover {
+ background: #333;
+ color: #aaa;
+ border-color: #777;
+}
+
+#main-menu button {
+ width: 20vw;
+ min-width: 200px;
+ padding: 1.5vh 2vw;
+ margin: 1.5vh 0;
+ font-size: 1.8vw;
+ background: transparent;
+ border: none;
+ color: #fff;
+ cursor: pointer;
+ transition: all 0.2s;
+ text-align: left;
+ font-family: 'Courier New', monospace;
+ font-weight: bold;
+ letter-spacing: 3px;
+ white-space: nowrap;
+}
+
+#main-menu button::before {
+ content: '> ';
+ opacity: 0;
+ transition: opacity 0.2s;
+}
+
+#main-menu button:hover::before,
+#main-menu button:focus::before {
+ opacity: 1;
+}
+
+#main-menu button:hover {
+ transform: translateX(10px);
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
+}
+
+/* 特殊夜晚按钮 */
+#special-night-btn {
+ color: #ffd700;
+ text-shadow: 0 0 10px rgba(255, 215, 0, 0.8);
+}
+
+/* 恐怖脸闪烁效果 - 不再需要,改用背景图切换 */
+.copyright {
+ position: absolute;
+ bottom: 2vh;
+ left: 2vw;
+ font-size: 1.2vw;
+ color: #888;
+ font-family: 'Courier New', monospace;
+ z-index: 102;
+}
+
+.reset-hint {
+ position: absolute;
+ bottom: 2vh;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 0.8vw;
+ color: #888;
+ font-family: 'Courier New', monospace;
+ z-index: 102;
+}
+
+.version {
+ position: absolute;
+ bottom: 2vh;
+ right: 8vw;
+ font-size: 1.8vw;
+ color: #ccc;
+ font-family: 'Courier New', monospace;
+ z-index: 102;
+ font-weight: bold;
+}
+
+/* 游戏结束 */
+#game-over {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ z-index: 100000;
+ overflow: hidden;
+}
+
+/* 雪花视频背景 */
+#game-over-static {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ z-index: 1;
+ opacity: 0.8;
+}
+
+/* 游戏结束内容层 */
+#game-over-content {
+ position: relative;
+ z-index: 2;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+#game-over h2 {
+ font-size: 8vw;
+ margin-bottom: 2vh;
+ color: #ff0000;
+ text-shadow:
+ 0 0 10px #ff0000,
+ 0 0 20px #ff0000,
+ 0 0 30px #ff0000,
+ 3px 3px 6px #000;
+ font-weight: bold;
+ letter-spacing: 0.1em;
+}
+
+#game-over-subtitle {
+ font-size: 1.8vw;
+ margin-bottom: 4vh;
+ color: #ff0000;
+ font-style: italic;
+ text-align: center;
+ padding: 0 2vw;
+ text-shadow:
+ 0 0 5px #ff0000,
+ 0 0 10px #ff0000,
+ 2px 2px 4px #000;
+ font-weight: bold;
+}
+
+#game-over button {
+ width: 20vw;
+ min-width: 200px;
+ padding: 1.5vh 2vw;
+ margin: 1vh 0;
+ font-size: 1.5vw;
+ background: #333;
+ border: 2px solid #666;
+ color: #fff;
+ cursor: pointer;
+ transition: all 0.3s;
+}
+
+#game-over button:hover {
+ background: #555;
+ border-color: #fff;
+}
+
+/* 游戏教程提示 */
+#tutorial-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.85);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10000;
+}
+
+#tutorial-content {
+ background: rgba(30, 30, 30, 0.95);
+ border: 3px solid #666;
+ padding: 4vh 5vw;
+ max-width: 60vw;
+ text-align: center;
+ box-shadow: 0 0 30px rgba(0, 0, 0, 0.8);
+}
+
+#tutorial-content h2 {
+ font-size: 3vw;
+ margin-bottom: 3vh;
+ color: #fff;
+ text-shadow: 2px 2px 4px #000;
+ letter-spacing: 0.1em;
+}
+
+#tutorial-content p {
+ font-size: 1.4vw;
+ line-height: 1.8;
+ color: #ddd;
+ margin-bottom: 3vh;
+ text-align: left;
+}
+
+#tutorial-got-it {
+ width: 15vw;
+ min-width: 150px;
+ padding: 1.5vh 3vw;
+ font-size: 1.8vw;
+ background: #555;
+ border: 2px solid #888;
+ color: #fff;
+ cursor: pointer;
+ transition: all 0.3s;
+ font-weight: bold;
+ letter-spacing: 0.2em;
+}
+
+#tutorial-got-it:hover {
+ background: #777;
+ border-color: #fff;
+ transform: scale(1.05);
+}
+
+/* 隐藏类 */
+.hidden {
+ display: none !important;
+}
+
+
+/* 闪烁效果 */
+@keyframes flicker {
+
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.3;
+ }
+}
+
+.flicker {
+ animation: flicker 0.1s infinite;
+}
+
+/* 警告闪烁动画 */
+@keyframes flash {
+
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.3;
+ }
+}
+
+/* 加载点动画 */
+@keyframes loadingDots {
+
+ 0%,
+ 20% {
+ content: '.';
+ }
+
+ 40% {
+ content: '..';
+ }
+
+ 60%,
+ 100% {
+ content: '...';
+ }
+}
+
+.loading-dots {
+ animation: loadingDots 1s infinite;
+}
+
+/* 过场动画 */
+#cutscene {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 200;
+ cursor: pointer;
+ opacity: 0;
+ transition: opacity 3s ease-in-out;
+}
+
+#cutscene.fade-in {
+ opacity: 1;
+}
+
+#cutscene.fade-out {
+ opacity: 0;
+}
+
+#cutscene img {
+ max-width: 100%;
+ max-height: 100%;
+ object-fit: contain;
+}
+
+.cutscene-hint {
+ position: absolute;
+ bottom: 5vh;
+ left: 50%;
+ transform: translateX(-50%);
+ color: #fff;
+ font-size: 1.5vw;
+ font-family: 'Courier New', monospace;
+ animation: pulse 1.5s infinite;
+}
+
+/* 每晚开始场景 */
+#night-intro {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #000;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 200;
+ opacity: 0;
+ transition: opacity 1.5s ease-in-out;
+}
+
+#night-intro.fade-in {
+ opacity: 1;
+}
+
+#night-intro.fade-out {
+ opacity: 0;
+}
+
+#night-intro-text {
+ font-size: 8vw;
+ color: #fff;
+ font-family: 'Courier New', monospace;
+ font-weight: bold;
+ letter-spacing: 0.5vw;
+ text-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
+ z-index: 1;
+}
+
+/* 电眼闪烁动画 (Night 6) */
+@keyframes lightning-flicker {
+
+ 0%,
+ 100% {
+ opacity: 1;
+ }
+
+ 50% {
+ opacity: 0.85;
+ }
+}
+
+@keyframes lightning-pulse {
+
+ 0%,
+ 100% {
+ transform: translate(-50%, -50%) scale(1);
+ opacity: 1;
+ }
+
+ 50% {
+ transform: translate(-50%, -50%) scale(1.1);
+ opacity: 0.9;
+ }
+}
+
+@keyframes lightning-bolt {
+
+ 0%,
+ 100% {
+ opacity: 0;
+ transform: translate(-50%, -50%) rotate(var(--rotation)) scaleY(0.5);
+ }
+
+ 10%,
+ 20% {
+ opacity: 1;
+ transform: translate(-50%, -50%) rotate(var(--rotation)) scaleY(1);
+ }
+
+ 30% {
+ opacity: 0;
+ transform: translate(-50%, -50%) rotate(var(--rotation)) scaleY(0.8);
+ }
+}
+
+.lightning-eye-effect {
+ animation: lightning-flicker 0.1s infinite;
+}
+
+/* Mobile Portrait */
+@media (max-width: 768px) and (orientation: portrait) {
+
+ /* Preloader mobile styles */
+ .preloader-logo {
+ font-size: 6vw;
+ }
+
+ .preloader-text {
+ font-size: 3.5vw;
+ }
+
+ .preloader-percentage {
+ font-size: 3vw;
+ }
+
+ /* Increase touch target sizes */
+ .control-panel-button,
+ .camera-button {
+ min-width: 60px !important;
+ min-height: 60px !important;
+ }
+
+ /* Adjust UI text sizes for mobile */
+ #top-left-ui {
+ top: 1vh;
+ left: 2vw;
+ }
+
+ #time-display {
+ font-size: 4vw;
+ }
+
+ #night-display {
+ font-size: 3vw;
+ }
+
+ #bottom-right-ui {
+ bottom: 1vh;
+ right: 2vw;
+ }
+
+ #oxygen-display {
+ font-size: 4vw;
+ }
+
+ .vent-icon {
+ width: 5vw;
+ height: 5vw;
+ }
+
+ /* Camera panel adjustments */
+ #camera-panel {
+ width: 90vw;
+ height: 70vh;
+ right: 5vw;
+ top: 10vh;
+ }
+
+ #current-cam-label {
+ font-size: 18px;
+ top: 10px;
+ left: 10px;
+ }
+
+ #camera-error-label {
+ font-size: 36px;
+ top: 10px;
+ right: 15px;
+ }
+
+ #camera-grid {
+ width: 40vw;
+ max-width: none;
+ bottom: 10px;
+ left: 10px;
+ }
+
+ #play-sound-btn {
+ width: 180px;
+ padding: 10px 0;
+ font-size: 14px;
+ bottom: 15px;
+ right: 15px;
+ letter-spacing: 2px;
+ }
+
+ #shock-hawking-btn {
+ width: 180px;
+ padding: 10px 0;
+ font-size: 14px;
+ bottom: 70px;
+ right: 15px;
+ letter-spacing: 2px;
+ }
+
+ /* Control panel popup */
+ #control-panel-popup {
+ width: 85vw !important;
+ left: 7.5vw !important;
+ top: 15vh !important;
+ min-height: 50vh !important;
+ font-size: 3.5vw !important;
+ }
+
+ #control-panel-popup h3 {
+ font-size: 4vw !important;
+ }
+
+ .control-row input {
+ font-size: 3vw !important;
+ }
+
+ /* Main menu - 修复移动端按钮点击问题 */
+ #main-menu {
+ padding-left: 8%;
+ padding-right: 8%;
+ align-items: center;
+ }
+
+ #star-icon {
+ width: 12vw;
+ max-width: 60px;
+ top: 10vh;
+ }
+
+ #star-icon-2 {
+ width: 12vw;
+ max-width: 60px;
+ top: 18vh;
+ /* 比第一颗低8vh */
+ }
+
+ #star-icon-3 {
+ width: 12vw;
+ max-width: 60px;
+ top: 26vh;
+ /* 比第二颗低8vh */
+ }
+
+ #main-menu h1 {
+ font-size: 10vw;
+ margin-bottom: 8vh;
+ text-align: center;
+ width: 100%;
+ }
+
+ #main-menu button {
+ font-size: 5vw;
+ width: 70vw;
+ max-width: 400px;
+ padding: 3vh 6vw;
+ margin: 2vh 0;
+ text-align: center;
+ min-height: 60px;
+ touch-action: manipulation;
+ -webkit-tap-highlight-color: rgba(255, 255, 255, 0.2);
+ position: relative;
+ z-index: 103;
+ }
+
+ #main-menu button::before {
+ content: '';
+ opacity: 0;
+ }
+
+ #main-menu button:hover::before,
+ #main-menu button:focus::before {
+ opacity: 0;
+ }
+
+ #main-menu button:active {
+ background: rgba(255, 255, 255, 0.1);
+ transform: scale(0.98);
+ }
+
+ .copyright {
+ font-size: 2.5vw;
+ left: 50%;
+ transform: translateX(-50%);
+ text-align: center;
+ width: 90%;
+ }
+
+ .reset-hint {
+ font-size: 2.5vw;
+ }
+
+ .version {
+ font-size: 2.5vw;
+ }
+
+ /* Game over screen */
+ #game-over h2 {
+ font-size: 12vw;
+ }
+
+ #game-over-subtitle {
+ font-size: 3.5vw;
+ }
+
+ #game-over button {
+ font-size: 4vw;
+ width: 60vw;
+ padding: 2vh 4vw;
+ }
+
+ /* Tutorial */
+ #tutorial-content {
+ max-width: 85vw;
+ padding: 3vh 4vw;
+ }
+
+ #tutorial-content h2 {
+ font-size: 5vw;
+ }
+
+ #tutorial-content p {
+ font-size: 3.5vw;
+ }
+
+ #tutorial-got-it {
+ font-size: 4vw;
+ width: 40vw;
+ padding: 2vh 4vw;
+ }
+
+ /* Night intro */
+ #night-intro-text {
+ font-size: 12vw;
+ }
+
+ .cutscene-hint {
+ font-size: 3.5vw;
+ }
+}
+
+/* Mobile Landscape - 横屏优化 */
+@media (max-width: 926px) and (orientation: landscape) {
+
+ /* Preloader mobile styles */
+ .preloader-logo {
+ font-size: 4vh;
+ }
+
+ .preloader-text {
+ font-size: 2vh;
+ }
+
+ .preloader-percentage {
+ font-size: 2vh;
+ }
+
+ /* 横屏时增大摄像头按钮并确保可见 */
+ #camera-btn {
+ width: 70px !important;
+ height: 60vh !important;
+ min-width: 70px !important;
+ min-height: 200px !important;
+ background: rgba(0, 0, 0, 0.95) !important;
+ border: 3px solid rgba(255, 255, 255, 0.7) !important;
+ border-right: none !important;
+ z-index: 25 !important;
+ top: 30vh !important;
+ transform: translateY(0) !important;
+ }
+
+ #camera-btn .camera-arrow {
+ font-size: 20px !important;
+ }
+
+ #camera-btn div[style*="CAMERA"] {
+ font-size: 14px !important;
+ }
+
+ /* UI adjustments for landscape */
+ #top-left-ui {
+ top: 1vh;
+ left: 1vw;
+ }
+
+ #time-display {
+ font-size: 3vh;
+ }
+
+ #night-display {
+ font-size: 2vh;
+ }
+
+ #bottom-right-ui {
+ bottom: 1vh;
+ right: 1vw;
+ }
+
+ #oxygen-display {
+ font-size: 3vh;
+ }
+
+ .vent-icon {
+ width: 4vh;
+ height: 4vh;
+ }
+
+ /* Camera panel - 横屏时不占满屏幕,留出右侧空间 */
+ #camera-panel {
+ width: calc(100vw - 70px);
+ height: 100vh;
+ right: 70px;
+ top: 0;
+ border: none;
+ border-right: 3px solid #444;
+ }
+
+ #current-cam-label {
+ font-size: 2.5vh;
+ top: 1vh;
+ left: 1vw;
+ }
+
+ #camera-error-label {
+ font-size: 5vh;
+ top: 1vh;
+ right: 2vw;
+ }
+
+ /* Camera grid - 横屏时缩小并移到左下角 */
+ #camera-grid {
+ width: 25vw;
+ max-width: 280px;
+ bottom: 1vh;
+ left: 1vw;
+ }
+
+ /* 横屏时调整按钮布局 */
+ #play-sound-btn {
+ width: 180px;
+ padding: 2vh 0;
+ font-size: 2.5vh;
+ bottom: 2vh;
+ right: 2vw;
+ letter-spacing: 2px;
+ }
+
+ #shock-hawking-btn {
+ width: 180px;
+ padding: 2vh 0;
+ font-size: 2.5vh;
+ bottom: 10vh;
+ right: 2vw;
+ letter-spacing: 2px;
+ }
+
+ /* Main menu landscape */
+ #main-menu {
+ padding-left: 5%;
+ align-items: flex-start;
+ }
+
+ #main-menu h1 {
+ font-size: 6vh;
+ margin-bottom: 3vh;
+ text-align: left;
+ }
+
+ #main-menu button {
+ font-size: 2.5vh;
+ width: 30vw;
+ min-width: 200px;
+ padding: 1.5vh 3vw;
+ margin: 1vh 0;
+ min-height: 50px;
+ }
+
+ .copyright,
+ .reset-hint,
+ .version {
+ font-size: 1.5vh;
+ }
+
+ /* Game over screen landscape */
+ #game-over h2 {
+ font-size: 8vh;
+ }
+
+ #game-over-subtitle {
+ font-size: 2vh;
+ }
+
+ #game-over button {
+ font-size: 2.5vh;
+ width: 30vw;
+ padding: 1.5vh 3vw;
+ min-height: 50px;
+ }
+
+ /* Tutorial - 横屏时大幅缩小 */
+ #tutorial-content {
+ max-width: 70vw;
+ max-height: 85vh;
+ padding: 2vh 3vw;
+ overflow-y: auto;
+ }
+
+ #tutorial-content h2 {
+ font-size: 2.5vh;
+ margin-bottom: 1.5vh;
+ }
+
+ #tutorial-content p {
+ font-size: 1.6vh;
+ line-height: 1.5;
+ margin-bottom: 2vh;
+ }
+
+ #tutorial-got-it {
+ font-size: 2vh;
+ width: 20vw;
+ min-width: 120px;
+ padding: 1.5vh 3vw;
+ min-height: 45px;
+ }
+
+ /* Night intro landscape */
+ #night-intro-text {
+ font-size: 8vh;
+ }
+
+ .cutscene-hint {
+ font-size: 2vh;
+ }
+
+ /* Volume button landscape */
+ #volume-btn {
+ font-size: 2vh;
+ padding: 1vh 2vw;
+ top: 1vh;
+ right: 1vw;
+ }
+
+ /* Volume panel landscape - 优化高度防止溢出 */
+ #volume-panel {
+ padding: 2vh 3vw;
+ min-width: 300px;
+ max-width: 70vw;
+ max-height: 85vh;
+ }
+
+ #volume-panel h3 {
+ font-size: 2vh;
+ margin-bottom: 1.5vh;
+ }
+
+ .volume-item {
+ margin-bottom: 1.5vh;
+ }
+
+ .volume-item label {
+ font-size: 1.5vh;
+ margin-bottom: 0.5vh;
+ }
+
+ .volume-percent {
+ font-size: 1.5vh;
+ }
+
+ #close-volume-panel {
+ font-size: 1.8vh;
+ padding: 1vh;
+ margin-top: 1.5vh;
+ }
+}
+
+/* Prevent text selection on touch devices */
+@media (hover: none) and (pointer: coarse) {
+ * {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-tap-highlight-color: transparent;
+ }
+
+ input,
+ textarea {
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ -ms-user-select: text;
+ user-select: text;
+ }
+}
+
+/* Touch feedback */
+@media (hover: none) and (pointer: coarse) {
+
+ .hotspot:active,
+ .control-panel-button:active,
+ .camera-button:active,
+ button:active {
+ opacity: 0.7;
+ transform: scale(0.95);
+ }
+}
+
+
+/* ==================== Custom Night Menu ==================== */
+#custom-night-menu {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: #000000;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ z-index: 100;
+ padding: 2vh;
+}
+
+#custom-night-menu h1 {
+ font-size: 5vw;
+ color: #fff;
+ margin-bottom: 4vh;
+ text-shadow: 0 0 20px rgba(255, 0, 0, 0.5);
+}
+
+.custom-night-controls {
+ display: flex;
+ flex-direction: column;
+ gap: 3vh;
+ width: 60%;
+ max-width: 800px;
+ margin-bottom: 4vh;
+}
+
+.ai-control {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: rgba(20, 20, 20, 0.8);
+ padding: 2vh 3vw;
+ border-radius: 10px;
+ border: 2px solid rgba(100, 100, 100, 0.3);
+ transition: all 0.3s;
+}
+
+.ai-control:hover {
+ background: rgba(40, 40, 40, 0.9);
+ border-color: rgba(150, 150, 150, 0.5);
+}
+
+.ai-name {
+ font-size: 2vw;
+ font-weight: bold;
+ color: #fff;
+ min-width: 150px;
+}
+
+.ai-slider-container {
+ display: flex;
+ align-items: center;
+ gap: 1vw;
+ flex: 1;
+ margin: 0 2vw;
+}
+
+.ai-btn-minus,
+.ai-btn-plus {
+ width: 40px;
+ height: 40px;
+ font-size: 24px;
+ font-weight: bold;
+ background: rgba(50, 50, 50, 0.8);
+ border: 2px solid rgba(100, 100, 100, 0.5);
+ color: #fff;
+ cursor: pointer;
+ border-radius: 5px;
+ transition: all 0.2s;
+}
+
+.ai-btn-minus:hover,
+.ai-btn-plus:hover {
+ background: rgba(80, 80, 80, 0.9);
+ border-color: rgba(150, 150, 150, 0.7);
+ transform: scale(1.1);
+}
+
+.ai-btn-minus:active,
+.ai-btn-plus:active {
+ transform: scale(0.95);
+}
+
+.ai-slider {
+ flex: 1;
+ height: 8px;
+ -webkit-appearance: none;
+ appearance: none;
+ background: rgba(80, 80, 80, 0.6);
+ border-radius: 5px;
+ outline: none;
+}
+
+.ai-slider::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 24px;
+ height: 24px;
+ background: #ddd;
+ border-radius: 50%;
+ cursor: pointer;
+ box-shadow: 0 0 10px rgba(200, 200, 200, 0.5);
+ transition: all 0.2s;
+}
+
+.ai-slider::-webkit-slider-thumb:hover {
+ transform: scale(1.2);
+ box-shadow: 0 0 15px rgba(220, 220, 220, 0.8);
+}
+
+.ai-slider::-moz-range-thumb {
+ width: 24px;
+ height: 24px;
+ background: #ddd;
+ border-radius: 50%;
+ cursor: pointer;
+ border: none;
+ box-shadow: 0 0 10px rgba(200, 200, 200, 0.5);
+ transition: all 0.2s;
+}
+
+.ai-slider::-moz-range-thumb:hover {
+ transform: scale(1.2);
+ box-shadow: 0 0 15px rgba(220, 220, 220, 0.8);
+}
+
+.ai-value {
+ font-size: 2.5vw;
+ font-weight: bold;
+ color: #fff;
+ min-width: 60px;
+ text-align: center;
+ text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
+}
+
+.custom-night-buttons {
+ display: flex;
+ gap: 2vw;
+}
+
+.custom-night-buttons button {
+ padding: 1.5vh 3vw;
+ font-size: 1.5vw;
+ font-weight: bold;
+ background: rgba(50, 50, 50, 0.8);
+ border: 2px solid rgba(100, 100, 100, 0.5);
+ color: #fff;
+ cursor: pointer;
+ border-radius: 5px;
+ transition: all 0.3s;
+ min-width: 150px;
+}
+
+.custom-night-buttons button:hover {
+ background: rgba(80, 80, 80, 0.9);
+ border-color: rgba(150, 150, 150, 0.7);
+ transform: translateY(-2px);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+}
+
+.custom-night-buttons button:active {
+ transform: translateY(0);
+}
+
+/* Mobile 适配 */
+@media (max-width: 768px) {
+ #custom-night-menu h1 {
+ font-size: 8vw;
+ }
+
+ .custom-night-controls {
+ width: 90%;
+ }
+
+ .ai-control {
+ flex-direction: column;
+ gap: 1vh;
+ padding: 2vh;
+ }
+
+ .ai-name {
+ font-size: 5vw;
+ min-width: auto;
+ }
+
+ .ai-slider-container {
+ width: 100%;
+ margin: 0;
+ }
+
+ .ai-value {
+ font-size: 6vw;
+ }
+
+ .custom-night-buttons {
+ flex-direction: column;
+ width: 80%;
+ }
+
+ .custom-night-buttons button {
+ font-size: 4vw;
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/js/config.js b/js/config.js
index 099dfc5b..5fceb1ac 100644
--- a/js/config.js
+++ b/js/config.js
@@ -1 +1 @@
-var json={"games":{"2048":{"path":"2048","aliases":[],"categories":[]},"1v1.LOL":{"path":"1v1-lol","aliases":[],"categories":["online","battle"]},"10 minutes till dawn":{"path":"10-minutes-till-dawn","aliases":[],"categories":[]},"Achievement Unlocked":{"path":"flash/?game=achievement-unlocked","aliases":[],"categories":[]},"Achievement Unlocked 2":{"path":"flash/?game=achievement-unlocked-2","aliases":[],"categories":[]},"Achievement Unlocked 3":{"path":"flash/?game=achievement-unlocked-3","aliases":[],"categories":[]},"A Dark Room":{"path":"a-dark-room","aliases":[],"categories":[]},"Abandoned":{"path":"abandoned","aliases":[],"categories":["escape"]},"Abandoned 2":{"path":"abandoned-2","aliases":[],"categories":[]},"Ages of Conflict":{"path":"ages-of-conflict","aliases":[],"categories":["escape"]},"Age of War":{"path":"flash/?game=age-of-war","aliases":[],"categories":[]},"Age of War Hacked":{"path":"flash/?game=age-of-war-hacked","aliases":[],"categories":[]},"Aground":{"path":"aground","aliases":[],"categories":[]},"Amidst The Sky":{"path":"amidst-the-sky","aliases":[],"categories":[]},"Another Gentlemans Adventure":{"path":"another-gentlemans-adventure","aliases":[],"categories":[]},"Awesome Tanks":{"path":"awesome-tanks","aliases":[],"categories":[]},"Awesome Tanks 2":{"path":"awesome-tanks-2","aliases":[],"categories":[]},"Babel Tower":{"path":"babel-tower","aliases":[],"categories":[]},"Basket Random":{"path":"basket-random","aliases":[],"categories":[]},"Basketball Stars":{"path":"basketball-stars","aliases":[],"categories":[]},"Bit Life":{"path":"bit-life","aliases":[],"categories":[]},"Boxing Random":{"path":"boxing-random","aliases":[],"categories":[]},"Bloons":{"path":"flash/?game=bloons","aliases":[],"categories":[]},"Bloons TD 1":{"path":"flash/?game=bloons-td-1","aliases":[],"categories":[]},"Bloons TD 2":{"path":"flash/?game=bloons-td-2","aliases":[],"categories":[]},"Bloons TD 3":{"path":"flash/?game=bloons-td-3","aliases":[],"categories":[]},"Bloons TD 4":{"path":"flash/?game=bloons-td-4","aliases":[],"categories":[]},"Bloons TD 5":{"path":"flash/?game=bloons-td-5","aliases":[],"categories":[]},"Bloxorz":{"path":"flash/?game=bloxorz","aliases":[],"categories":[]},"Brawl Stars Project Laser":{"path":"brawl-stars-project-laser","aliases":[],"categories":[]},"Breaklock":{"path":"breaklock","aliases":[],"categories":[]},"Cat Ninja":{"path":"flash/?game=cat-ninja","aliases":[],"categories":[]},"Curveball":{"path":"flash/?game=curveball","aliases":[],"categories":[]},"Chrome Dino":{"path":"chrome-dino","aliases":[],"categories":[]},"Clicker Heroes":{"path":"clicker-heroes","aliases":[],"categories":[]},"Clicker Heroes Updated":{"path":"clicker-heroes-updated","aliases":[],"categories":[]},"Cookie Clicker":{"path":"cookie-clicker","aliases":[],"categories":[]},"Core Ball":{"path":"core-ball","aliases":[],"categories":[]},"Crossy Road":{"path":"crossy-road","aliases":[],"categories":[]},"Conway's Game of Life":{"path":"conways-game-of-life","aliases":[],"categories":[]},"Creative Kill Chamber":{"path":"flash/?game=creative-kill-chamber","aliases":[],"categories":[]},"Cut The Rope":{"path":"cut-the-rope","aliases":[],"categories":[]},"Doge Miner":{"path":"doge-miner","aliases":[],"categories":[]},"Don't Escape":{"path":"flash/?game=dont-escape","aliases":[],"categories":[]},"Don't Escape 2":{"path":"flash/?game=dont-escape-2","aliases":[],"categories":[]},"Don't Escape 3":{"path":"flash/?game=dont-escape-3","aliases":[],"categories":[]},"Doodle Jump":{"path":"doodle-jump","aliases":[],"categories":[]},"Drift Boss":{"path":"drift-boss","aliases":[],"categories":[]},"Drift Hunters":{"path":"drift-hunters","aliases":[],"categories":[]},"Drive Mad":{"path":"drive-mad","aliases":[],"categories":[]},"Drive Mad S":{"path":"drive-mad-s","aliases":[],"categories":[]},"Duck Life":{"path":"flash/?game=duck-life","aliases":[],"categories":[]},"Duck Life 2":{"path":"flash/?game=duck-life-2","aliases":[],"categories":[]},"Duck Life 3":{"path":"flash/?game=duck-life-3","aliases":[],"categories":[]},"Duck Life 4":{"path":"duck-life-4","aliases":[],"categories":[]},"Duck Life Treasure Hunt":{"path":"flash/?game=duck-life-treasure-hunt","aliases":[],"categories":[]},"Dune!":{"path":"dune","aliases":[],"categories":[]},"Eaglercraft Ampler Launcher":{"path":"ampler-launcher","aliases":[],"categories":[]},"Eggy Car":{"path":"eggy-car","aliases":[],"categories":[]},"EvoWars":{"path":"evowars","aliases":[],"categories":[]},"Escape The Car":{"path":"flash/?game=escape-the-car","aliases":[],"categories":[]},"Escape The Closet":{"path":"flash/?game=escape-the-closet","aliases":[],"categories":[]},"Escape The Phonebooth":{"path":"flash/?game=escape-the-phonebooth","aliases":[],"categories":[]},"Escape The Bathroom":{"path":"flash/?game=escape-the-bathroom","aliases":[],"categories":[]},"Escape The Freezer":{"path":"flash/?game=escape-the-freezer","aliases":[],"categories":[]},"Escape The Shack":{"path":"flash/?game=escape-the-shack","aliases":[],"categories":[]},"Family Feud":{"path":"family-feud","aliases":[],"categories":[]},"Fireboy and Watergirl in the Forest Temple":{"path":"fireboy-and-watergirl","aliases":["Fireboy and Watergirl","Fireboy and Watergirl 1"],"categories":[]},"Fireboy and Watergirl in the Light Temple":{"path":"fireboy-and-watergirl-2","aliases":["Fireboy and Watergirl 2"],"categories":[]},"Fireboy and Watergirl in the Ice Temple":{"path":"fireboy-and-watergirl-3","aliases":["Fireboy and Watergirl 3"],"categories":[]},"Fireboy and Watergirl in the Crystal Temple":{"path":"fireboy-and-watergirl-4","aliases":["Fireboy and Watergirl 4"],"categories":[]},"Flash Chess":{"path":"flash/?game=flash-chess","aliases":[],"categories":[]},"Friday Night Funkin":{"path":"friday-night-funkin","aliases":[],"categories":[]},"Factory Balls":{"path":"flash/?game=factory-balls","aliases":[],"categories":[]},"Flappy Bird":{"path":"flappy-bird","aliases":[],"categories":[]},"Geometry Dash Lite":{"path":"geometry-dash-lite","aliases":[],"categories":[]},"Geometry Dash Remastered":{"path":"geometry-dash-remastered","aliases":[],"categories":[]},"Geometry Vibes":{"path":"geometry-vibes","aliases":[],"categories":[]},"Geometry Vibes Monster":{"path":"geometry-vibes-monster","aliases":[],"categories":[]},"Geometry Vibes X-Ball":{"path":"geometry-vibes-x-ball","aliases":[],"categories":[]},"Getaway Shootout":{"path":"getaway-shootout","aliases":[],"categories":[]},"Gons io":{"path":"gons-io","aliases":[],"categories":[]},"Gun Mayhem":{"path":"flash/?game=gun-mayhem","aliases":[],"categories":[]},"Gun Mayhem 2":{"path":"flash/?game=gun-mayhem-2","aliases":[],"categories":[]},"Gun Spin":{"path":"gunspin","aliases":[],"categories":[]},"Henry Stickmin 0: Breaking The Bank":{"path":"flash/?game=breaking-the-bank","aliases":[],"categories":[]},"Henry Stickmin 1: Escaping The Prison":{"path":"flash/?game=escaping-the-prison","aliases":[],"categories":[]},"Henry Stickmin 2: Stealing The Diamond":{"path":"flash/?game=stealing-the-diamond","aliases":[],"categories":[]},"Henry Stickmin 3: Infiltrating The Airship":{"path":"flash/?game=infiltrating-the-airship","aliases":[],"categories":[]},"Henry Stickmin 4: Fleeing the Complex":{"path":"flash/?game=fleeing-the-complex","aliases":[],"categories":[]},"Hextris":{"path":"hextris","aliases":[],"categories":[]},"Incremancer":{"path":"incremancer","aliases":[],"categories":[]},"Learn To Fly":{"path":"flash/?game=learn-to-fly","aliases":[],"categories":[]},"Learn To Fly 2":{"path":"flash/?game=learn-to-fly-2","aliases":[],"categories":[]},"Learn To Fly 3":{"path":"flash/?game=learn-to-fly-3","aliases":[],"categories":[]},"Learn To Fly Idle":{"path":"flash/?game=learn-to-fly-idle","aliases":[],"categories":[]},"Lion Solider's Vengeance":{"path":"lion-soldiers-vengeance","aliases":[],"cateogires":[]},"Idle Breakout":{"path":"idle-breakout","aliases":[],"categories":[]},"Maptroid":{"path":"maptroid","aliases":[],"categories":[]},"Mario":{"path":"mario-game","aliases":[],"categories":[]},"Monkey Mart":{"path":"monkey-mart","aliases":[],"categories":[]},"MotoX3M":{"path":"motox3m","aliases":[],"categories":[]},"N-gon":{"path":"n-gon","aliases":[],"categories":[]},"N Step Steve Part 1":{"path":"n-step-steve-part-1","aliases":[],"categories":[]},"N Step Steve Part 2":{"path":"n-step-steve-part-2","aliases":[],"categories":[]},"OvO":{"path":"ovo/1.4.4","aliases":[],"categories":[]},"OvO 2":{"path":"ovo/2.0.2alpha","aliases":[],"categories":[]},"Papas Pizzeria":{"path":"flash/?game=papas-pizzaria","aliases":[],"categories":[]},"Papas Freezeria":{"path":"flash/?game=papas-freezeria","aliases":[],"categories":[]},"Particle Clicker":{"path":"particle-clicker","aliases":[],"categories":[]},"Planet Life":{"path":"planet-life","aliases":[],"categories":[]},"P.craft":{"path":"pcraft","aliases":[],"categories":[]},"Progress Knight Quest":{"path":"progress-knight-quest","aliases":[],"categories":[]},"Progress Knight Reborn":{"path":"progress-knight-reborn","aliases":[],"categories":[]},"Pull Of War":{"path":"pull-of-war","aliases":[],"categories":[]},"Reach The Core":{"path":"reach-the-core","aliases":[],"categories":[]},"Restless Wing Syndrome":{"path":"restless-wing-syndrome","aliases":[],"categories":[]},"Raft Wars":{"path":"flash/?game=raft-wars","aliases":[],"categories":[]},"Raft Wars 2":{"path":"flash/?game=raft-wars-2","aliases":[],"categories":[]},"Retro Bowl":{"path":"retro-bowl","aliases":[],"categories":[]},"Retro Bowl Old":{"path":"retro-bowl-old","aliases":[],"categories":[]},"Rift Shift":{"path":"rift-shift","aliases":[],"categories":[]},"Rocket League 2D":{"path":"rocket-league-2d","aliases":[],"categories":[]},"Rogue Soul":{"path":"flash/?game=rogue-soul","aliases":[],"categories":[]},"Rogue Soul 2":{"path":"flash/?game=rogue-soul-2","aliases":[],"categories":[]},"Riddle School":{"path":"flash/?game=riddle-school","aliases":[],"categories":["escape"]},"Riddle School 2":{"path":"flash/?game=riddle-school-2","aliases":[],"categories":["escape"]},"Riddle School 3":{"path":"flash/?game=riddle-school-3","aliases":[],"categories":["escape"]},"Riddle School 4":{"path":"flash/?game=riddle-school-4","aliases":[],"categories":["escape"]},"Riddle School 5":{"path":"flash/?game=riddle-school-5","aliases":[],"categories":["escape"]},"Riddle Transfer":{"path":"flash/?game=riddle-transfer","aliases":[],"categories":["escape"]},"Riddle Transfer 2":{"path":"flash/?game=riddle-transfer-2","aliases":[],"categories":["escape"]},"Rooftop Snipers":{"path":"rooftop-snipers","aliases":[],"categories":[]},"Rookie Bowman":{"path":"rookie-bowman","aliases":[],"categories":[]},"Run 3":{"path":"run-3","aliases":[],"categories":[]},"Run 3 Beta (may not work)":{"path":"run-3-beta","aliases":[],"categories":[]},"Sabercut":{"path":"sabercut","aliases":[],"categories":[]},"Sandspiel":{"path":"sandspiel","aliases":[],"categories":[]},"Scuba Bear":{"path":"scuba-bear","aliases":[],"categories":[]},"Shadow Fight":{"path":"shadow-fight","aliases":[],"categories":[]},"Slice Master":{"path":"slice-master","aliases":[],"categories":[]},"Slope":{"path":"slope","aliases":[],"categories":[]},"Smash Karts":{"path":"smash-karts","aliases":[],"categories":[]},"Soccer Random":{"path":"soccer-random","aliases":[],"categories":[]},"Station Saturn":{"path":"station-saturn","aliases":[],"categories":[]},"Stickman Hook":{"path":"stickman-hook","aliases":[],"categories":[]},"Subway Surfers":{"path":"subway-surfers","aliases":[],"categories":[]},"Subway Surfers New York":{"path":"subway-surfers-ny","aliases":[],"categories":[]},"Stick RPG Complete":{"path":"flash/?game=stick-rpg-complete","aliases":[],"categories":[]},"Stick War":{"path":"flash/?game=stick-war","aliases":[],"categories":[]},"Submachine":{"path":"flash/?game=submachine","aliases":[],"categories":["escape"]},"Submachine 2":{"path":"flash/?game=submachine-2","aliases":[],"categories":["escape"]},"Submachine 3":{"path":"flash/?game=submachine-3","aliases":[],"categories":["escape"]},"Submachine 4":{"path":"flash/?game=submachine-4","aliases":[],"categories":["escape"]},"Submachine 5":{"path":"flash/?game=submachine-5","aliases":[],"categories":["escape"]},"Submachine 6":{"path":"flash/?game=submachine-6","aliases":[],"categories":["escape"]},"Submachine 7":{"path":"flash/?game=submachine-7","aliases":[],"categories":["escape"]},"Submachine 8":{"path":"flash/?game=submachine-8","aliases":[],"categories":["escape"]},"Submachine 9":{"path":"flash/?game=submachine-9","aliases":[],"categories":["escape"]},"Submachine 10":{"path":"flash/?game=submachine-10","aliases":[],"categories":["escape"]},"Submachine 0":{"path":"flash/?game=submachine-0","aliases":[],"categories":["escape"]},"Submachine 32 Chambers":{"path":"flash/?game=submachine-32-chambers","aliases":[],"categories":["escape"]},"Submachine FLF":{"path":"flash/?game=submachine-flf","aliases":[],"categories":["escape"]},"Sugar Sugar":{"path":"flash/?game=sugar-sugar","aliases":[],"categories":[]},"Swords and Souls":{"path":"flash/?game=swords-and-souls","aliases":[],"categories":[]},"The Final Earth":{"path":"the-final-earth","aliases":[],"categories":[]},"The Treasure":{"path":"the-treasure","aliases":[],"categories":["escape"]},"There Is No Game":{"path":"there-is-no-game","aliases":[],"categories":[]},"Tic Tac What?":{"path":"tic-tac-what","aliases":[],"categories":[]},"Time Shooter":{"path":"time-shooter","aliases":[],"categories":[]},"Time Shooter 3":{"path":"time-shooter-3","aliases":[],"categories":[]},"Tiny Fishing":{"path":"tiny-fishing","aliases":[],"categories":[]},"Trace":{"path":"trace","aliases":[],"categories":["escape"]},"Tanuki Sunset":{"path":"tanuki-sunset","aliases":[],"categories":[]},"Temple Run 2":{"path":"temple-run-2","aliases":[],"categories":[]},"The Impossible Quiz":{"path":"flash/?game=the-impossible-quiz","aliases":[],"categories":[]},"This Is The Only Level":{"path":"flash/?game=this-is-the-only-level","aliases":[],"categories":[]},"This Is The Only Level 2":{"path":"flash/?game=this-is-the-only-level-2","aliases":[],"categories":[]},"Tunnel Rush":{"path":"tunnel-rush","aliases":[],"categories":[]},"Two Ball 3d":{"path":"two-ball-3d","aliases":[],"categories":[]},"Vex 3":{"path":"vex-3","aliases":[],"categories":[]},"Vex 4":{"path":"vex-4","aliases":[],"categories":[]},"Vex 5":{"path":"vex-5","aliases":[],"categories":[]},"Vex 6":{"path":"vex-6","aliases":[],"categories":[]},"Vex 7":{"path":"vex-7","aliases":[],"categories":[]},"Volley Random":{"path":"volley-random","aliases":[],"categories":[]},"Web OSU":{"path":"web-osu","aliases":[],"categories":[]},"Ultimate Chess":{"path":"flash/?game=ultimate-chess","aliases":[],"categories":[]},"X Trench Run":{"path":"x-trench-run","aliases":[],"categories":[]},"Yohoho":{"path":"yohoho","aliases":[],"categories":[]}},"themes":{},"config":{"proxy":true,"proxyPath":"https://monkey.nordparrot.ro"}}
\ No newline at end of file
+var json={"games":{"2048":{"path":"2048","aliases":[],"categories":[]},"1v1.LOL":{"path":"1v1-lol","aliases":[],"categories":["online","battle"]},"10 minutes till dawn":{"path":"10-minutes-till-dawn","aliases":[],"categories":[]},"Achievement Unlocked":{"path":"flash/?game=achievement-unlocked","aliases":[],"categories":[]},"Achievement Unlocked 2":{"path":"flash/?game=achievement-unlocked-2","aliases":[],"categories":[]},"Achievement Unlocked 3":{"path":"flash/?game=achievement-unlocked-3","aliases":[],"categories":[]},"A Dark Room":{"path":"a-dark-room","aliases":[],"categories":[]},"Abandoned":{"path":"abandoned","aliases":[],"categories":["escape"]},"Abandoned 2":{"path":"abandoned-2","aliases":[],"categories":[]},"Ages of Conflict":{"path":"ages-of-conflict","aliases":[],"categories":["escape"]},"Age of War":{"path":"flash/?game=age-of-war","aliases":[],"categories":[]},"Age of War Hacked":{"path":"flash/?game=age-of-war-hacked","aliases":[],"categories":[]},"Aground":{"path":"aground","aliases":[],"categories":[]},"Amidst The Sky":{"path":"amidst-the-sky","aliases":[],"categories":[]},"Another Gentlemans Adventure":{"path":"another-gentlemans-adventure","aliases":[],"categories":[]},"Awesome Tanks":{"path":"awesome-tanks","aliases":[],"categories":[]},"Awesome Tanks 2":{"path":"awesome-tanks-2","aliases":[],"categories":[]},"Babel Tower":{"path":"babel-tower","aliases":[],"categories":[]},"Basket Random":{"path":"basket-random","aliases":[],"categories":[]},"Basketball Stars":{"path":"basketball-stars","aliases":[],"categories":[]},"Bit Life":{"path":"bit-life","aliases":[],"categories":[]},"Boxing Random":{"path":"boxing-random","aliases":[],"categories":[]},"Bloons":{"path":"flash/?game=bloons","aliases":[],"categories":[]},"Bloons TD 1":{"path":"flash/?game=bloons-td-1","aliases":[],"categories":[]},"Bloons TD 2":{"path":"flash/?game=bloons-td-2","aliases":[],"categories":[]},"Bloons TD 3":{"path":"flash/?game=bloons-td-3","aliases":[],"categories":[]},"Bloons TD 4":{"path":"flash/?game=bloons-td-4","aliases":[],"categories":[]},"Bloons TD 5":{"path":"flash/?game=bloons-td-5","aliases":[],"categories":[]},"Bloxorz":{"path":"flash/?game=bloxorz","aliases":[],"categories":[]},"Brawl Stars Project Laser":{"path":"brawl-stars-project-laser","aliases":[],"categories":[]},"Breaklock":{"path":"breaklock","aliases":[],"categories":[]},"Cat Ninja":{"path":"flash/?game=cat-ninja","aliases":[],"categories":[]},"Curveball":{"path":"flash/?game=curveball","aliases":[],"categories":[]},"Chrome Dino":{"path":"chrome-dino","aliases":[],"categories":[]},"Clicker Heroes":{"path":"clicker-heroes","aliases":[],"categories":[]},"Clicker Heroes Updated":{"path":"clicker-heroes-updated","aliases":[],"categories":[]},"Cookie Clicker":{"path":"cookie-clicker","aliases":[],"categories":[]},"Core Ball":{"path":"core-ball","aliases":[],"categories":[]},"Crossy Road":{"path":"crossy-road","aliases":[],"categories":[]},"Conway's Game of Life":{"path":"conways-game-of-life","aliases":[],"categories":[]},"Creative Kill Chamber":{"path":"flash/?game=creative-kill-chamber","aliases":[],"categories":[]},"Cut The Rope":{"path":"cut-the-rope","aliases":[],"categories":[]},"Doge Miner":{"path":"doge-miner","aliases":[],"categories":[]},"Don't Escape":{"path":"flash/?game=dont-escape","aliases":[],"categories":[]},"Don't Escape 2":{"path":"flash/?game=dont-escape-2","aliases":[],"categories":[]},"Don't Escape 3":{"path":"flash/?game=dont-escape-3","aliases":[],"categories":[]},"Doodle Jump":{"path":"doodle-jump","aliases":[],"categories":[]},"Drift Boss":{"path":"drift-boss","aliases":[],"categories":[]},"Drift Hunters":{"path":"drift-hunters","aliases":[],"categories":[]},"Drive Mad":{"path":"drive-mad","aliases":[],"categories":[]},"Drive Mad S":{"path":"drive-mad-s","aliases":[],"categories":[]},"Duck Life":{"path":"flash/?game=duck-life","aliases":[],"categories":[]},"Duck Life 2":{"path":"flash/?game=duck-life-2","aliases":[],"categories":[]},"Duck Life 3":{"path":"flash/?game=duck-life-3","aliases":[],"categories":[]},"Duck Life 4":{"path":"duck-life-4","aliases":[],"categories":[]},"Duck Life Treasure Hunt":{"path":"flash/?game=duck-life-treasure-hunt","aliases":[],"categories":[]},"Dune!":{"path":"dune","aliases":[],"categories":[]},"Eaglercraft Ampler Launcher":{"path":"ampler-launcher","aliases":[],"categories":[]},"Eggy Car":{"path":"eggy-car","aliases":[],"categories":[]},"EvoWars":{"path":"evowars","aliases":[],"categories":[]},"Escape The Car":{"path":"flash/?game=escape-the-car","aliases":[],"categories":[]},"Escape The Closet":{"path":"flash/?game=escape-the-closet","aliases":[],"categories":[]},"Escape The Phonebooth":{"path":"flash/?game=escape-the-phonebooth","aliases":[],"categories":[]},"Escape The Bathroom":{"path":"flash/?game=escape-the-bathroom","aliases":[],"categories":[]},"Escape The Freezer":{"path":"flash/?game=escape-the-freezer","aliases":[],"categories":[]},"Escape The Shack":{"path":"flash/?game=escape-the-shack","aliases":[],"categories":[]},"Family Feud":{"path":"family-feud","aliases":[],"categories":[]},"Fireboy and Watergirl in the Forest Temple":{"path":"fireboy-and-watergirl","aliases":["Fireboy and Watergirl","Fireboy and Watergirl 1"],"categories":[]},"Fireboy and Watergirl in the Light Temple":{"path":"fireboy-and-watergirl-2","aliases":["Fireboy and Watergirl 2"],"categories":[]},"Fireboy and Watergirl in the Ice Temple":{"path":"fireboy-and-watergirl-3","aliases":["Fireboy and Watergirl 3"],"categories":[]},"Fireboy and Watergirl in the Crystal Temple":{"path":"fireboy-and-watergirl-4","aliases":["Fireboy and Watergirl 4"],"categories":[]},"Five Nights at Epstein's":{"path":"five-nights-at-epsteins","aliases":[],"categories":[]},"Flash Chess":{"path":"flash/?game=flash-chess","aliases":[],"categories":[]},"Friday Night Funkin":{"path":"friday-night-funkin","aliases":[],"categories":[]},"Factory Balls":{"path":"flash/?game=factory-balls","aliases":[],"categories":[]},"Flappy Bird":{"path":"flappy-bird","aliases":[],"categories":[]},"Geometry Dash Lite":{"path":"geometry-dash-lite","aliases":[],"categories":[]},"Geometry Dash Remastered":{"path":"geometry-dash-remastered","aliases":[],"categories":[]},"Geometry Vibes":{"path":"geometry-vibes","aliases":[],"categories":[]},"Geometry Vibes Monster":{"path":"geometry-vibes-monster","aliases":[],"categories":[]},"Geometry Vibes X-Ball":{"path":"geometry-vibes-x-ball","aliases":[],"categories":[]},"Getaway Shootout":{"path":"getaway-shootout","aliases":[],"categories":[]},"Gons io":{"path":"gons-io","aliases":[],"categories":[]},"Gun Mayhem":{"path":"flash/?game=gun-mayhem","aliases":[],"categories":[]},"Gun Mayhem 2":{"path":"flash/?game=gun-mayhem-2","aliases":[],"categories":[]},"Gun Spin":{"path":"gunspin","aliases":[],"categories":[]},"Henry Stickmin 0: Breaking The Bank":{"path":"flash/?game=breaking-the-bank","aliases":[],"categories":[]},"Henry Stickmin 1: Escaping The Prison":{"path":"flash/?game=escaping-the-prison","aliases":[],"categories":[]},"Henry Stickmin 2: Stealing The Diamond":{"path":"flash/?game=stealing-the-diamond","aliases":[],"categories":[]},"Henry Stickmin 3: Infiltrating The Airship":{"path":"flash/?game=infiltrating-the-airship","aliases":[],"categories":[]},"Henry Stickmin 4: Fleeing the Complex":{"path":"flash/?game=fleeing-the-complex","aliases":[],"categories":[]},"Hextris":{"path":"hextris","aliases":[],"categories":[]},"Incremancer":{"path":"incremancer","aliases":[],"categories":[]},"Learn To Fly":{"path":"flash/?game=learn-to-fly","aliases":[],"categories":[]},"Learn To Fly 2":{"path":"flash/?game=learn-to-fly-2","aliases":[],"categories":[]},"Learn To Fly 3":{"path":"flash/?game=learn-to-fly-3","aliases":[],"categories":[]},"Learn To Fly Idle":{"path":"flash/?game=learn-to-fly-idle","aliases":[],"categories":[]},"Lion Solider's Vengeance":{"path":"lion-soldiers-vengeance","aliases":[],"cateogires":[]},"Idle Breakout":{"path":"idle-breakout","aliases":[],"categories":[]},"Maptroid":{"path":"maptroid","aliases":[],"categories":[]},"Mario":{"path":"mario-game","aliases":[],"categories":[]},"Monkey Mart":{"path":"monkey-mart","aliases":[],"categories":[]},"MotoX3M":{"path":"motox3m","aliases":[],"categories":[]},"N-gon":{"path":"n-gon","aliases":[],"categories":[]},"N Step Steve Part 1":{"path":"n-step-steve-part-1","aliases":[],"categories":[]},"N Step Steve Part 2":{"path":"n-step-steve-part-2","aliases":[],"categories":[]},"OvO":{"path":"ovo/1.4.4","aliases":[],"categories":[]},"OvO 2":{"path":"ovo/2.0.2alpha","aliases":[],"categories":[]},"Papas Pizzeria":{"path":"flash/?game=papas-pizzaria","aliases":[],"categories":[]},"Papas Freezeria":{"path":"flash/?game=papas-freezeria","aliases":[],"categories":[]},"Particle Clicker":{"path":"particle-clicker","aliases":[],"categories":[]},"Planet Life":{"path":"planet-life","aliases":[],"categories":[]},"P.craft":{"path":"pcraft","aliases":[],"categories":[]},"Progress Knight Quest":{"path":"progress-knight-quest","aliases":[],"categories":[]},"Progress Knight Reborn":{"path":"progress-knight-reborn","aliases":[],"categories":[]},"Pull Of War":{"path":"pull-of-war","aliases":[],"categories":[]},"Reach The Core":{"path":"reach-the-core","aliases":[],"categories":[]},"Restless Wing Syndrome":{"path":"restless-wing-syndrome","aliases":[],"categories":[]},"Raft Wars":{"path":"flash/?game=raft-wars","aliases":[],"categories":[]},"Raft Wars 2":{"path":"flash/?game=raft-wars-2","aliases":[],"categories":[]},"Retro Bowl":{"path":"retro-bowl","aliases":[],"categories":[]},"Retro Bowl Old":{"path":"retro-bowl-old","aliases":[],"categories":[]},"Rift Shift":{"path":"rift-shift","aliases":[],"categories":[]},"Rocket League 2D":{"path":"rocket-league-2d","aliases":[],"categories":[]},"Rogue Soul":{"path":"flash/?game=rogue-soul","aliases":[],"categories":[]},"Rogue Soul 2":{"path":"flash/?game=rogue-soul-2","aliases":[],"categories":[]},"Riddle School":{"path":"flash/?game=riddle-school","aliases":[],"categories":["escape"]},"Riddle School 2":{"path":"flash/?game=riddle-school-2","aliases":[],"categories":["escape"]},"Riddle School 3":{"path":"flash/?game=riddle-school-3","aliases":[],"categories":["escape"]},"Riddle School 4":{"path":"flash/?game=riddle-school-4","aliases":[],"categories":["escape"]},"Riddle School 5":{"path":"flash/?game=riddle-school-5","aliases":[],"categories":["escape"]},"Riddle Transfer":{"path":"flash/?game=riddle-transfer","aliases":[],"categories":["escape"]},"Riddle Transfer 2":{"path":"flash/?game=riddle-transfer-2","aliases":[],"categories":["escape"]},"Rooftop Snipers":{"path":"rooftop-snipers","aliases":[],"categories":[]},"Rookie Bowman":{"path":"rookie-bowman","aliases":[],"categories":[]},"Run 3":{"path":"run-3","aliases":[],"categories":[]},"Run 3 Beta (may not work)":{"path":"run-3-beta","aliases":[],"categories":[]},"Sabercut":{"path":"sabercut","aliases":[],"categories":[]},"Sandspiel":{"path":"sandspiel","aliases":[],"categories":[]},"Scuba Bear":{"path":"scuba-bear","aliases":[],"categories":[]},"Shadow Fight":{"path":"shadow-fight","aliases":[],"categories":[]},"Slice Master":{"path":"slice-master","aliases":[],"categories":[]},"Slope":{"path":"slope","aliases":[],"categories":[]},"Smash Karts":{"path":"smash-karts","aliases":[],"categories":[]},"Soccer Random":{"path":"soccer-random","aliases":[],"categories":[]},"Station Saturn":{"path":"station-saturn","aliases":[],"categories":[]},"Stickman Hook":{"path":"stickman-hook","aliases":[],"categories":[]},"Subway Surfers":{"path":"subway-surfers","aliases":[],"categories":[]},"Subway Surfers New York":{"path":"subway-surfers-ny","aliases":[],"categories":[]},"Stick RPG Complete":{"path":"flash/?game=stick-rpg-complete","aliases":[],"categories":[]},"Stick War":{"path":"flash/?game=stick-war","aliases":[],"categories":[]},"Submachine":{"path":"flash/?game=submachine","aliases":[],"categories":["escape"]},"Submachine 2":{"path":"flash/?game=submachine-2","aliases":[],"categories":["escape"]},"Submachine 3":{"path":"flash/?game=submachine-3","aliases":[],"categories":["escape"]},"Submachine 4":{"path":"flash/?game=submachine-4","aliases":[],"categories":["escape"]},"Submachine 5":{"path":"flash/?game=submachine-5","aliases":[],"categories":["escape"]},"Submachine 6":{"path":"flash/?game=submachine-6","aliases":[],"categories":["escape"]},"Submachine 7":{"path":"flash/?game=submachine-7","aliases":[],"categories":["escape"]},"Submachine 8":{"path":"flash/?game=submachine-8","aliases":[],"categories":["escape"]},"Submachine 9":{"path":"flash/?game=submachine-9","aliases":[],"categories":["escape"]},"Submachine 10":{"path":"flash/?game=submachine-10","aliases":[],"categories":["escape"]},"Submachine 0":{"path":"flash/?game=submachine-0","aliases":[],"categories":["escape"]},"Submachine 32 Chambers":{"path":"flash/?game=submachine-32-chambers","aliases":[],"categories":["escape"]},"Submachine FLF":{"path":"flash/?game=submachine-flf","aliases":[],"categories":["escape"]},"Sugar Sugar":{"path":"flash/?game=sugar-sugar","aliases":[],"categories":[]},"Swords and Souls":{"path":"flash/?game=swords-and-souls","aliases":[],"categories":[]},"The Final Earth":{"path":"the-final-earth","aliases":[],"categories":[]},"The Treasure":{"path":"the-treasure","aliases":[],"categories":["escape"]},"There Is No Game":{"path":"there-is-no-game","aliases":[],"categories":[]},"Tic Tac What?":{"path":"tic-tac-what","aliases":[],"categories":[]},"Time Shooter":{"path":"time-shooter","aliases":[],"categories":[]},"Time Shooter 3":{"path":"time-shooter-3","aliases":[],"categories":[]},"Tiny Fishing":{"path":"tiny-fishing","aliases":[],"categories":[]},"Trace":{"path":"trace","aliases":[],"categories":["escape"]},"Tanuki Sunset":{"path":"tanuki-sunset","aliases":[],"categories":[]},"Temple Run 2":{"path":"temple-run-2","aliases":[],"categories":[]},"The Impossible Quiz":{"path":"flash/?game=the-impossible-quiz","aliases":[],"categories":[]},"This Is The Only Level":{"path":"flash/?game=this-is-the-only-level","aliases":[],"categories":[]},"This Is The Only Level 2":{"path":"flash/?game=this-is-the-only-level-2","aliases":[],"categories":[]},"Tunnel Rush":{"path":"tunnel-rush","aliases":[],"categories":[]},"Two Ball 3d":{"path":"two-ball-3d","aliases":[],"categories":[]},"Vex 3":{"path":"vex-3","aliases":[],"categories":[]},"Vex 4":{"path":"vex-4","aliases":[],"categories":[]},"Vex 5":{"path":"vex-5","aliases":[],"categories":[]},"Vex 6":{"path":"vex-6","aliases":[],"categories":[]},"Vex 7":{"path":"vex-7","aliases":[],"categories":[]},"Volley Random":{"path":"volley-random","aliases":[],"categories":[]},"Web OSU":{"path":"web-osu","aliases":[],"categories":[]},"Ultimate Chess":{"path":"flash/?game=ultimate-chess","aliases":[],"categories":[]},"X Trench Run":{"path":"x-trench-run","aliases":[],"categories":[]},"Yohoho":{"path":"yohoho","aliases":[],"categories":[]}},"themes":{},"config":{"proxy":true,"proxyPath":"https://monkey.nordparrot.ro"}}
\ No newline at end of file