// ============================================================
// 第一部分:核心引擎(弹幕、音效、状态、工具函数、剧情渲染)
// ============================================================
// ---------- 弹幕 ----------
const DANMAKU_QUOTES = [
'66:这个选项有深意~', '66:沈倦又在偷瞄你', '66:江迟的嘴硬心软', '66:许知年字里行间都是情',
'66:陆屿舟的阳光直球', '66:程砚的尾戒故事', '66:温知遥神助攻', '66:你的选择决定命运',
'66:心动值悄悄涨', '66:66在暗中观察', '66:这段剧情我写的时候都笑了',
'66:沈倦的相机里全是你', '66:江迟的痞笑藏不住', '66:许知年耳朵红了',
'66:陆屿舟的篮球是道具吗', '66:程砚的套路全失效', '66:温知遥的八卦魂',
'66:选择困难症犯了?', '66:这条线甜度爆表', '66:66嗑CP上头了',
'66:你选对了吗?', '66:剧情在加速哦'
];
let danmakuTimer = null;
let usedDanmaku = [];
function spawnDanmaku() {
const container = document.getElementById('danmaku-container');
if (!container) return;
if (G && G.turn % 3 !== 0) return;
const count = rand(1, 2);
const quotes = [];
for (let i = 0; i < count; i++) {
let q = pick(DANMAKU_QUOTES);
while (usedDanmaku.includes(q) && DANMAKU_QUOTES.length > 1) q = pick(DANMAKU_QUOTES);
usedDanmaku.push(q);
if (usedDanmaku.length > 20) usedDanmaku.shift();
quotes.push(q);
}
quotes.forEach((text, index) => {
const el = document.createElement('div');
el.className = 'danmaku-item';
el.textContent = text;
const top = rand(10, 80);
const delay = index * 0.5 + rand(0, 0.3);
const size = rand(12, 18);
el.style.top = top + '%';
el.style.fontSize = size + 'px';
el.style.animationDelay = delay + 's';
el.style.animationDuration = (8 + rand(0, 2)) + 's';
container.appendChild(el);
setTimeout(() => el.remove(), 10000);
});
}
function triggerDanmaku() { if (rand(1, 3) === 1) spawnDanmaku(); }
function autoDanmaku() {
if (G && G.initialized) triggerDanmaku();
danmakuTimer = setTimeout(autoDanmaku, 10000 + rand(0, 5000));
}
// ---------- 音效 ----------
let sfxCtx = null;
function playClickSound() {
try {
if (!sfxCtx) sfxCtx = new(window.AudioContext || window.webkitAudioContext)();
const osc = sfxCtx.createOscillator();
const gain = sfxCtx.createGain();
osc.type = 'sine';
osc.frequency.value = 880;
gain.gain.setValueAtTime(0.08, sfxCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, sfxCtx.currentTime + 0.12);
osc.connect(gain);
gain.connect(sfxCtx.destination);
osc.start(sfxCtx.currentTime);
osc.stop(sfxCtx.currentTime + 0.12);
} catch (_) {}
}
// ---------- 背景音乐 ----------
let audioCtx = null;
let audioPlaying = false;
let currentMood = 'default';
let audioTimer = null;
let isStopping = false;
let currentNodes = [];
function initAudio() {
if (audioCtx) return;
try { audioCtx = new(window.AudioContext || window.webkitAudioContext)(); } catch (_) { return; }
}
function stopAllNodes(nodes) { nodes.forEach(n => { try { n.stop(); } catch (_) {} }); }
function clearAudioTimer() { if (audioTimer) { clearTimeout(audioTimer); audioTimer = null; } }
function playMoodMusic(mood = 'default') {
if (!audioCtx) return;
if (isStopping) return;
if (currentNodes.length > 0) {
const now = audioCtx.currentTime;
currentNodes.forEach(node => {
try { const gain = node.gain || node; if (gain.gain) gain.gain.exponentialRampToValueAtTime(0.001, now + 0.3); } catch (_) {}
});
setTimeout(() => { stopAllNodes(currentNodes); currentNodes = []; }, 400);
}
try {
const now = audioCtx.currentTime;
const baseFreqs = [261.63, 329.63, 392.00, 523.25];
let duration = 6;
let gainFactor = 0.018;
if (mood === 'night') { duration = 7; gainFactor = 0.015; } else if (mood === 'rain') { duration = 5.5; gainFactor = 0.022; } else if (mood === 'snow') { duration = 8; gainFactor = 0.012; } else { duration = 6; gainFactor = 0.018; }
const newNodes = [];
for (let i = 0; i < 3; i++) {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.value = baseFreqs[i] + (i * 0.5);
const vol = gainFactor * (0.6 + Math.random() * 0.2);
gain.gain.setValueAtTime(0.001, now);
gain.gain.exponentialRampToValueAtTime(vol, now + 0.2);
gain.gain.exponentialRampToValueAtTime(vol * 0.3, now + duration - 0.4);
gain.gain.exponentialRampToValueAtTime(0.001, now + duration + 0.2);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(now + i * 0.15);
osc.stop(now + duration + 0.3);
newNodes.push(osc);
}
const oscLow = audioCtx.createOscillator();
const gainLow = audioCtx.createGain();
oscLow.type = 'sine';
oscLow.frequency.value = 130.81;
gainLow.gain.setValueAtTime(0.001, now);
gainLow.gain.exponentialRampToValueAtTime(0.012, now + 0.3);
gainLow.gain.exponentialRampToValueAtTime(0.005, now + duration - 0.3);
gainLow.gain.exponentialRampToValueAtTime(0.001, now + duration + 0.2);
oscLow.connect(gainLow);
gainLow.connect(audioCtx.destination);
oscLow.start(now);
oscLow.stop(now + duration + 0.3);
newNodes.push(oscLow);
currentNodes = newNodes;
audioPlaying = true;
document.getElementById('music-toggle').textContent = '🎵 音乐播放中';
clearAudioTimer();
audioTimer = setTimeout(() => { if (audioPlaying && !isStopping) playMoodMusic(mood); }, (duration - 0.2) * 1000);
} catch (_) { audioPlaying = false; }
}
function stopMusic() {
isStopping = true;
clearAudioTimer();
if (currentNodes.length > 0) {
const now = audioCtx ? audioCtx.currentTime : 0;
currentNodes.forEach(node => { try { const gain = node.gain || node; if (gain.gain) gain.gain.exponentialRampToValueAtTime(0.001, now + 0.3); } catch (_) {} });
setTimeout(() => { stopAllNodes(currentNodes); currentNodes = []; audioPlaying = false; document.getElementById('music-toggle').textContent = '🎵 背景音乐'; isStopping = false; }, 400);
} else { audioPlaying = false; document.getElementById('music-toggle').textContent = '🎵 背景音乐'; isStopping = false; }
}
function toggleMusic() {
if (audioPlaying) stopMusic();
else { initAudio(); if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume(); isStopping = false; playMoodMusic(currentMood); }
}
function updateMood(weather, hour) {
let mood = 'default';
if (weather === '雨') mood = 'rain';
else if (weather === '雪') mood = 'snow';
else if (hour >= 0 && hour < 6) mood = 'night';
else mood = 'default';
if (mood !== currentMood) { currentMood = mood; if (audioPlaying) playMoodMusic(mood); }
}
// ---------- 确认弹窗 ----------
function showConfirm(options) {
return new Promise((resolve) => {
const container = document.getElementById('modal-container');
if (!container) { resolve(false); return; }
const overlay = document.createElement('div');
overlay.className = 'modal-overlay show confirm-modal';
overlay.innerHTML = `
${options.message || ''}
`;
container.appendChild(overlay);
let resolved = false;
function resolveAndCleanup(result) {
if (resolved) return;
resolved = true;
resolve(result);
if (overlay.parentNode) overlay.remove();
playClickSound();
}
const yesBtn = overlay.querySelector('#confirm-yes-btn');
const noBtn = overlay.querySelector('#confirm-no-btn');
const closeBtn = overlay.querySelector('#confirm-close-btn');
yesBtn.addEventListener('click', function(e) {
e.stopPropagation();
resolveAndCleanup(true);
});
noBtn.addEventListener('click', function(e) {
e.stopPropagation();
resolveAndCleanup(false);
});
closeBtn.addEventListener('click', function(e) {
e.stopPropagation();
resolveAndCleanup(false);
});
overlay.addEventListener('click', function(e) {
if (e.target === overlay) { /* 不关闭 */ }
});
playClickSound();
triggerDanmaku();
});
}
// ---------- 游戏状态 ----------
const G = {
player: { name: '', age: '', birth: '', mbti: '', home: '', height: '', body: '', look: '', personality: '' },
turn: 1,
time: '11月第3周·周四·凌晨01:30',
weather: '晴',
temp: 3,
scene: '自习教室',
stamina: 35,
maxStamina: 100,
heart: 0,
shy: 0,
trust: 50,
affection: { shenjuan: 8, jiangchi: 2, xuzhinian: 2, luyuzhou: 3, chengyan: 0 },
wechatFriends: ['wenyao'],
phoneContacts: ['wenyao'],
contacts: {
wenyao: { name: '温知遥', avatar: '👩', note: '发小' },
shenjuan: { name: '沈倦', avatar: '📷', note: '' },
jiangchi: { name: '江迟', avatar: '🍬', note: '' },
xuzhinian: { name: '许知年', avatar: '📖', note: '' },
luyuzhou: { name: '陆屿舟', avatar: '🏀', note: '' },
chengyan: { name: '程砚', avatar: '💍', note: '' }
},
achievements: { first_meet: true, night_stay: true, photo: false, umbrella: false, snow: false, heart_moment: false },
social: { nickname: '', signature: '✨ 今天也是元气满满~', style: '日常', love: '单身' },
log: [],
moments: [],
chats: {},
groupChats: [],
diary: [],
forumPosts: [],
gameInvite: null,
gameBoard: null,
gameTurn: null,
gameOver: false,
shenjuan_wechat_sent: false,
jiangchi_bread: false,
met_luyuzhou: false,
met_chengyan: false,
first_heart_triggered: false,
option_history: [],
initialized: false,
eggCount: 0,
isSnow: false,
isRain: false,
snowTriggered: false,
_dating: false,
_datingTarget: null,
_married: false,
_story_flags: {},
scenes: {
'自习教室': { unlock: true, people: ['沈倦', '江迟', '许知年'] },
'一食堂': { unlock: true, people: ['陆屿舟', '江迟', '许知年', '沈倦'] },
'图书馆': { unlock: false, unlockTurn: 8, people: ['沈倦', '温知遥'] },
'咖啡厅': { unlock: false, unlockTurn: 10, people: ['沈倦', '程砚'] },
'篮球场': { unlock: false, unlockTurn: 12, people: ['陆屿舟', '江迟'] },
'镜湖长椅': { unlock: false, unlockTurn: 14, people: ['温知遥', '陆屿舟'] },
'小吃街': { unlock: false, unlockTurn: 20, people: ['江迟', '陆屿舟', '许知年', '程砚'] },
'隔壁理工': { unlock: false, unlockTurn: 999, people: ['程砚'] }
}
};
// ---------- 工具函数 ----------
function clamp(v, min, max) { return Math.max(min, Math.min(max, v)); }
function rand(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
function pick(arr) { return arr[rand(0, arr.length - 1)]; }
function getStaminaLabel(s) {
if (s >= 81) return '精力充沛💪';
if (s >= 61) return '状态不错✨';
if (s >= 41) return '有点累😅';
if (s >= 21) return '需要休息😴';
return '快撑不住了🥱';
}
function getTrustLabel(t) {
if (t >= 80) return '信任💕';
if (t >= 50) return '普通🤝';
if (t >= 20) return '保留🤔';
return '疏远😶';
}
function getPhaseLabel(v) {
if (v >= 81) return '心动';
if (v >= 61) return '喜欢';
if (v >= 41) return '在意';
if (v >= 21) return '有好感';
return '陌生人';
}
function addLog(msg) { G.log.unshift(`✦ T${G.turn} ${msg}`); if (G.log.length > 30) G.log.pop(); }
function changeAffection(who, delta) {
if (!G.affection[who]) return;
const old = G.affection[who];
let effectiveDelta = delta * 0.25;
if (Math.abs(effectiveDelta) < 0.2 && delta !== 0) effectiveDelta = delta > 0 ? 0.2 : -0.2;
let newv = clamp(old + effectiveDelta, 0, 100);
if (G.trust < 20 && effectiveDelta > 0) newv = Math.max(old, old + effectiveDelta * 0.5);
if (G.trust >= 80 && effectiveDelta > 0) newv = Math.min(100, old + effectiveDelta * 1.2);
G.affection[who] = clamp(newv, 0, 100);
if (Math.abs(G.affection[who] - old) > 0.01) { addLog(`${who} 好感 ${effectiveDelta>0?'+':''}${effectiveDelta.toFixed(1)} → ${G.affection[who].toFixed(1)}`); }
checkAffectionEgg(who);
}
function changeTrust(delta) { const old = G.trust; G.trust = clamp(G.trust + delta, 0, 100); if (G.trust !== old) addLog(`信任 ${delta>0?'+':''}${delta} → ${G.trust}`); if (G.trust === 0) showEgg('⚠️ 信任归零', '关系濒临破裂...', '💔'); }
function changeStamina(delta) { G.stamina = clamp(G.stamina + delta, 0, G.maxStamina); if (G.stamina === 0) showEgg('😴 体力透支', '你累倒了……\n朦胧中好像有人把你背了起来。是谁呢?', '😴'); }
function changeHeart(delta) {
const old = G.heart;
G.heart = clamp(G.heart + delta, 0, 100);
if (G.heart !== old && delta > 0) addLog(`心动 +${delta} → ${G.heart}`);
if (G.heart >= 5 && !G.first_heart_triggered) {
G.first_heart_triggered = true;
G.achievements.heart_moment = true;
showEgg('💕 心动瞬间!', '你第一次清晰地感受到了心动——\n是沈倦的镜头,还是江迟的软糖?', '💕');
}
if (G.heart >= 10 && rand(1, 3) === 1) {
showEgg('💗 心跳加速', pick(['你的视线总是不自觉地追随着某个人……', '你发现自己开始期待偶遇了。', '心里的小鹿在乱撞!']), '💗');
}
}
function changeShy(delta) { G.shy = clamp(G.shy + delta, 0, 100); }
function checkAffectionEgg(who) {
const v = G.affection[who];
const names = { shenjuan: '沈倦', jiangchi: '江迟', xuzhinian: '许知年', luyuzhou: '陆屿舟', chengyan: '程砚' };
const name = names[who] || who;
if (v >= 50 && v < 55) showEgg(`💕 ${name} 好感度突破50`, pick([`${name} 看你的眼神好像温柔了一些……`, `你注意到 ${name} 偷偷在看你。`, `${name} 对你说的话变多了。`]), '💕');
if (v >= 80 && v < 85) showEgg(`💗 ${name} 好感度突破80`, pick([`${name} 似乎已经离不开你了。`, `你发现 ${name} 在你面前会脸红。`, `${name} 把最脆弱的一面展现给了你。`]), '💗');
}
// ---------- 彩蛋 ----------
function showEgg(title, desc, icon = '🥚') {
const modal = document.getElementById('egg-modal');
if (!modal) return;
document.getElementById('egg-icon').textContent = icon;
document.getElementById('egg-title').textContent = title;
document.getElementById('egg-desc').textContent = desc;
modal.classList.add('show');
modal.style.display = 'flex';
G.eggCount++;
if (G.eggCount === 3) setTimeout(() => { document.getElementById('egg-desc').textContent += '\n\n✨ 你已经发现3个彩蛋啦! 66说:谢谢你玩这个游戏~ 💕'; }, 200);
if (G.eggCount === 7) setTimeout(() => { document.getElementById('egg-desc').textContent += '\n\n🎉 隐藏成就:彩蛋猎人! 你太厉害了~ 66给你比心 ❤️'; }, 300);
if (G.eggCount === 12) setTimeout(() => { document.getElementById('egg-desc').textContent += '\n\n🌟 终极彩蛋! 你是66最爱的玩家! 送你一个虚拟抱抱 🧸'; }, 400);
playClickSound();
triggerDanmaku();
}
document.getElementById('egg-close').addEventListener('click', () => {
document.getElementById('egg-modal').classList.remove('show');
document.getElementById('egg-modal').style.display = 'none';
playClickSound();
});
document.getElementById('egg-modal').addEventListener('click', (e) => {
if (e.target === e.currentTarget) {
document.getElementById('egg-modal').classList.remove('show');
document.getElementById('egg-modal').style.display = 'none';
}
});
function createFloatingHeart(x, y) {
const emojis = ['❤️', '💕', '💗', '✨', '🌸'];
const el = document.createElement('div');
el.className = 'floating-heart';
el.textContent = pick(emojis);
el.style.left = x + 'px';
el.style.top = y + 'px';
el.style.fontSize = (rand(20, 36)) + 'px';
document.body.appendChild(el);
setTimeout(() => el.remove(), 2800);
}
// ---------- 模态 ----------
function openModal(contentHTML, title = '') {
const container = document.getElementById('modal-container');
const overlay = document.createElement('div');
overlay.className = 'modal-overlay show';
overlay.innerHTML = ``;
container.appendChild(overlay);
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
if (title.includes('朋友圈') || title.includes('通讯录') || title.includes('关系图') || title.includes('群聊') || title.includes('日记') || title.includes('论坛') || title.includes('小游戏')) {
if (rand(1, 3) === 1) showEgg('📱 面板彩蛋', '你打开了 ' + title + ',66 在此等候多时了~', '📱');
}
playClickSound();
triggerDanmaku();
}
// ---------- 本地智能回复(备用) ----------
function getSmartReply(userText, contactName, affectionLevel) {
const text = userText.toLowerCase();
if (userText.length <= 1) return pick(['嗯?', '你说什么?', '我没听清~', '再说一遍好不好?']);
if (text.includes('烦') || text.includes('讨厌') || text.includes('生气') || text.includes('烦死了') || text.includes('糟糕') || text.includes('无语') || text.includes('崩溃')) {
return pick(['摸摸头,怎么了?', '别烦了,我陪你聊聊', '谁惹你生气了?告诉我', '抱抱你,一切都会好的', '要不要出来走走?']);
}
if (text.includes('难过') || text.includes('伤心') || text.includes('委屈') || text.includes('哭了') || text.includes('想哭')) {
return pick(['别难过,还有我呢', '想哭就哭吧,我陪着你', '是不是受委屈了?跟我说', '我给你讲个笑话好不好?']);
}
if ((text.includes('不') || text.includes('没') || text.includes('别')) && (text.includes('算了') || text.includes('不用') || text.includes('没事') || text.includes('真的不用') || text.includes('不想'))) {
return pick(['好的,那你自己注意点', '嗯嗯,听你的', '那好吧,你有需要再找我', '好,我不勉强你']);
}
if (text.includes('你好') || text.includes('嗨') || text.includes('hi') || text.includes('hello') || text.includes('在吗') || text.includes('在不在')) {
return pick(['嗨!我在呢', '你好呀~', '在的在的,想我了吗?', '哈哈,你在我就一直都在']);
}
if (text.includes('想你') || text.includes('想见') || text.includes('想你了') || text.includes('好久不见') || text.includes('梦到你')) {
if (affectionLevel >= 50) return pick(['我也好想你…', '想见我就来呀,我等你', '你知不知道我每天都在想你', '听到你这么说,我好开心']);
else return pick(['哈哈,你这么说我都不好意思了', '真的吗?那我有点开心', '你是不是想找我说什么?']);
}
if (text.includes('吗') || text.includes('呢') || text.includes('哪') || text.includes('怎么') || text.includes('为什么') || text.includes('哪里')) {
if (text.includes('吃')) return pick(['还没吃,你请客吗?', '刚吃过,你呢?', '有点饿,在想吃什么']);
if (text.includes('在哪') || text.includes('哪里')) return pick(['我在宿舍/家里', '在外面散步呢', '在等你消息呀']);
if (text.includes('干嘛') || text.includes('做什么') || text.includes('忙')) return pick(['刚忙完,在想你', '在看剧,好无聊', '在听歌,你要一起吗?', '在发呆,你来陪陪我']);
if (text.includes('有空') || text.includes('时间')) return pick(['有空!随时都有!', '你想约我?那肯定有', '让我看看…嗯,对你永远有空']);
if (text.includes('天气')) return pick(['今天天气不错,适合出门', '好像要下雨了,记得带伞', '有点冷,你多穿点']);
if (text.includes('几点') || text.includes('时间')) return pick(['现在刚好是我想你的时间', '快了,马上就能见到你']);
return pick(['嗯…这个问题我得想想', '你觉得呢?我想听听你的想法', '好问题,我也想知道', '你猜猜看?']);
}
if (text.includes('今天') || text.includes('刚刚') || text.includes('刚才') || text.includes('路上') || text.includes('看到') || text.includes('遇到')) {
if (text.includes('猫') || text.includes('狗') || text.includes('动物')) { return pick(['啊啊啊!猫猫!我也想看!', '好可爱!下次带我去看', '你是不是又投喂了?']); }
if (text.includes('饭') || text.includes('吃') || text.includes('煮') || text.includes('炒')) { return pick(['你居然会做饭?好厉害!', '下次做给我尝尝', '听起来好好吃,我酸了']); }
return pick(['哇,听起来好有趣!', '然后呢然后呢?', '你总是能遇到好玩的事', '我也想跟你一起经历']);
}
if (text.includes('一起') || text.includes('约') || text.includes('出来') || text.includes('见面') || text.includes('去')) {
if (text.includes('吃饭') || text.includes('吃')) { return pick(['好呀!我正好饿了', '你请客我就去!', '我想吃火锅,可以吗?']); }
if (text.includes('走') || text.includes('逛') || text.includes('散步')) { return pick(['好!我换件衣服就来', '你等我一下,马上到', '我们是不是像在约会?']); }
return pick(['好呀,我们去哪?', '嗯嗯,我很期待!', '你等我一下,我马上到']);
}
if (text.includes('晚安') || text.includes('睡了') || text.includes('困') || text.includes('早点睡') || text.includes('休息')) {
return pick(['晚安,好梦', '早点休息,明天见', '嗯嗯,梦里见', '睡吧,我也要睡了', '你睡着的样子一定很好看']);
}
if (text.includes('早安') || text.includes('早啊') || text.includes('起床') || text.includes('醒了')) {
return pick(['早安!新的一天开始了', '这么早就醒了?好厉害', '你也是,今天阳光很好', '早安,想你了']);
}
if (text.includes('帅') || text.includes('好看') || text.includes('可爱') || text.includes('厉害') || text.includes('棒')) {
if (affectionLevel >= 50) return pick(['真的吗?你这么说我好开心', '只有你觉得我好看吧…', '那你要不要多看看我?', '你也很可爱呀']);
else return pick(['哈哈,谢谢夸奖!', '你这么说我有点害羞了', '你也不差嘛']);
}
if (text.includes('喜欢') || text.includes('爱你') || text.includes('心动') || text.includes('好感')) {
if (affectionLevel >= 60) return pick(['我也喜欢你!真的好喜欢', '你终于说了,我等这句话好久了', '我们在一起好不好?', '我的心跳得好快…']);
else if (affectionLevel >= 30) return pick(['真的吗?我有点意外…', '你这么说我有点不知所措', '我觉得我们需要多了解一下']);
else return pick(['哈哈,你是在开玩笑吧?', '我们才刚认识不久呢', '你认真的吗?']);
}
if (text.includes('对不起') || text.includes('抱歉') || text.includes('我错了') || text.includes('是我的错')) {
return pick(['没关系,我原谅你了', '你知道错就好', '其实我也没生气', '以后注意就好啦']);
}
if (text.includes('图') || text.includes('照片') || text.includes('视频') || text.includes('发你')) {
return pick(['让我看看!', '哇,你拍的?好棒!', '这张照片我要保存起来', '你总是能拍到好东西']);
}
if (text.includes('哈哈') || text.includes('嘿嘿') || text.includes('嘻嘻')) {
return pick(['你这么开心呀?', '看到你笑我也开心', '有什么好事也分享给我', '你笑起来一定很好看']);
}
if (text.includes('呜呜') || text.includes('555') || text.includes('唉')) {
return pick(['怎么了?别叹气', '抱抱你,不哭了', '谁欺负你了?告诉我']);
}
if (text.includes('干嘛') || text.includes('在做什么') || text.includes('忙吗') || text.includes('在吗')) {
return pick(['刚忙完,正想你呢', '在等你消息呀', '在想怎么回你比较好', '没干嘛,有点无聊,你来了就好了']);
}
const defaults = ['嗯嗯,你说得对', '哈哈,你真有意思', '好呀,听你的', '我再想想', '这个嘛…让我想想怎么回', '你总是能让我开心', '好,那就这样吧', '你说什么我都觉得有道理'];
return pick(defaults);
}
// ---------- 渲染函数 ----------
function renderTopStats() {
document.getElementById('top-time').textContent = `T${G.turn}`;
document.getElementById('top-weather').textContent = `${G.weather}·${G.temp}℃`;
document.getElementById('top-stamina').textContent = G.stamina;
document.getElementById('top-heart').textContent = G.heart;
const hour = parseInt(G.time.split('·')[2].trim().split(':')[0]);
updateMood(G.weather, hour);
}
// ---------- 剧情渲染 ----------
function renderStory(text, options) {
if (typeof text === 'function') text = text();
if (typeof options === 'function') options = options();
if (G && G.player && G.player.name) {
text = text.replace(/刘昕怡/g, G.player.name).replace(/昕怡/g, G.player.name);
}
document.getElementById('story-text').innerHTML = text;
const optArea = document.getElementById('options-area');
optArea.innerHTML = '';
if (options && options.length > 0) {
options.forEach((opt, i) => {
let optText = typeof opt.text === 'function' ? opt.text() : opt.text;
if (G && G.player && G.player.name) {
optText = optText.replace(/刘昕怡/g, G.player.name).replace(/昕怡/g, G.player.name);
}
const btn = document.createElement('button');
btn.className = 'option-btn';
btn.innerHTML = `${String.fromCharCode(65 + i)}. ${optText}`;
btn.addEventListener('click', async () => {
playClickSound();
if (opt.aff) {
for (let [k, v] of Object.entries(opt.aff)) {
if (k === 'all') { for (let key of Object.keys(G.affection)) changeAffection(key, v); }
else if (k === 'heart') changeHeart(v);
else if (k === 'shy') changeShy(v);
else if (k === 'trust') changeTrust(v);
else changeAffection(k, v);
}
}
if (opt.setDating) {
G._dating = true;
G._datingTarget = opt.setDating;
G._married = false;
addLog(`你和${G.contacts[opt.setDating]?.name||'对方'}确立了恋爱关系!`);
}
if (opt.action) await opt.action();
G.option_history.push(optText);
if (G.option_history.length >= 5) {
showEgg('🤔 纠结症', '选了这么多次,你心里有答案了吗?\n—— 66 悄悄说', '🤔');
G.option_history = [];
}
const rect = btn.getBoundingClientRect();
createFloatingHeart(rect.left + rect.width / 2, rect.top);
triggerDanmaku();
if (opt.next) {
G.turn++;
if (opt.next === 'ai_next' || opt._aiGenerated) {
renderAIGeneratedStory();
} else {
renderTurn(opt.next);
}
}
});
optArea.appendChild(btn);
});
} else {
const msg = document.createElement('div');
msg.style.cssText = 'text-align:center;color:var(--gray);font-size:0.85rem;padding:12px 0;';
msg.textContent = '💫 稍等片刻...';
optArea.appendChild(msg);
}
document.getElementById('story-panel').scrollTop = document.getElementById('story-panel').scrollHeight;
}
// ============================================================
// 第二部分:DeepSeek API 接入 + 全部功能函数
// 粘贴到第一部分 的位置(删掉原来的 )
// ============================================================
// ---------- 1. API 设置(用户自定义) ----------
let DEEPSEEK_API_KEY = '';
// 从 localStorage 读取
function getStoredAPIKey() {
return localStorage.getItem('deepseek_api_key') || '';
}
function saveAPIKey(key) {
localStorage.setItem('deepseek_api_key', key);
DEEPSEEK_API_KEY = key;
}
// 页面加载时读取
DEEPSEEK_API_KEY = getStoredAPIKey();
// ---------- 2. API 设置面板 ----------
document.getElementById('btn-api-settings').addEventListener('click', function() {
const currentKey = getStoredAPIKey();
const container = document.getElementById('modal-container');
const overlay = document.createElement('div');
overlay.className = 'modal-overlay show';
overlay.innerHTML = `
${currentKey ? '✅ AI 已启用' : '⚠️ 未设置 API Key,AI 功能将使用本地回复'}
`;
container.appendChild(overlay);
document.getElementById('api-save-btn').addEventListener('click', function() {
const key = document.getElementById('api-key-input').value.trim();
if (!key) {
document.getElementById('api-status').textContent = '⚠️ 请输入 API Key';
document.getElementById('api-status').style.color = '#e8a0a0';
return;
}
if (!key.startsWith('sk-')) {
document.getElementById('api-status').textContent = '⚠️ 格式不正确,Key 应以 sk- 开头';
document.getElementById('api-status').style.color = '#e8a0a0';
return;
}
saveAPIKey(key);
document.getElementById('api-status').textContent = '✅ 保存成功!AI 功能已启用';
document.getElementById('api-status').style.color = '#7bc8a0';
document.getElementById('btn-api-settings').textContent = '⚙️ AI 已启用';
document.getElementById('btn-api-settings').style.color = 'var(--pink)';
playClickSound();
});
document.getElementById('api-clear-btn').addEventListener('click', function() {
if (confirm('确定要清除 API Key 吗?')) {
localStorage.removeItem('deepseek_api_key');
DEEPSEEK_API_KEY = '';
document.getElementById('api-key-input').value = '';
document.getElementById('api-status').textContent = '🗑️ 已清除,AI 功能将禁用';
document.getElementById('api-status').style.color = 'var(--gray)';
document.getElementById('btn-api-settings').textContent = '⚙️ 设置 API';
document.getElementById('btn-api-settings').style.color = '';
playClickSound();
}
});
document.getElementById('api-key-input').addEventListener('keydown', function(e) {
if (e.key === 'Enter') document.getElementById('api-save-btn').click();
});
overlay.addEventListener('click', function(e) {
if (e.target === overlay) { /* 不关闭 */ }
});
playClickSound();
});
// 初始化 API 按钮状态
if (getStoredAPIKey()) {
document.getElementById('btn-api-settings').textContent = '⚙️ AI 已启用';
document.getElementById('btn-api-settings').style.color = 'var(--pink)';
}
// ---------- 3. DeepSeek API 配置 ----------
const DEEPSEEK_API_URL = 'https://api.deepseek.com/v1/chat/completions';
async function callDeepSeek(messages, temperature = 0.85) {
const apiKey = getStoredAPIKey();
if (!apiKey) {
console.warn('⚠️ 未设置 API Key');
return null;
}
try {
const response = await fetch(DEEPSEEK_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: messages,
temperature: temperature,
max_tokens: 600,
top_p: 0.9,
frequency_penalty: 0.3,
presence_penalty: 0.3
})
});
if (!response.ok) {
console.error('API 请求失败:', response.status);
return null;
}
const data = await response.json();
if (data.choices && data.choices.length > 0) {
return data.choices[0].message.content;
}
return null;
} catch (error) {
console.error('AI 调用异常:', error);
return null;
}
}
// ---------- 4. 角色人设 ----------
const CHARACTER_PROMPTS = {
shenjuan: '你是沈倦,话不多但说话直接,不喜欢绕弯子。喜欢用相机拍东西,但不会聊摄影术语。说话自然,偶尔会损人,但心里是好的。',
jiangchi: '你是江迟,爱开玩笑,说话带点痞气,但心很细。不会用大词,就是普通人说话的方式,偶尔会认真一下。',
xuzhinian: '你是许知年,性格安静,说话轻声细语,喜欢看书但不会聊文学理论。表达感情很含蓄,不会说肉麻的话。',
luyuzhou: '你是陆屿舟,阳光开朗,说话直来直去,不会拐弯抹角。喜欢用行动表达,不会说漂亮话,但很真诚。',
chengyan: '你是程砚,以前有点玩世不恭,现在认真了。说话有分寸,不会装深沉,偶尔会露出一点不自信。'
};
// ---------- 5. AI 智能回复 ----------
async function getAIResponse(characterId, userMessage, chatHistory = []) {
const apiKey = getStoredAPIKey();
if (!apiKey) return null;
const characterName = G.contacts[characterId]?.name || '对方';
const systemPrompt = CHARACTER_PROMPTS[characterId] || '你是一个普通大学生,说话自然、口语化。';
const messages = [
{ role: 'system', content: systemPrompt + `你的名字是${characterName}。请用${characterName}的身份回复我,就像普通人在微信聊天那样自然,不要用书面语,不要说肉麻的话,不要用任何专业术语。回复长度控制在30-60字之间。` }
];
const recentHistory = chatHistory.slice(-6);
for (let msg of recentHistory) {
const role = msg.from === 'me' ? 'user' : 'assistant';
messages.push({ role: role, content: msg.text });
}
messages.push({ role: 'user', content: userMessage });
const result = await callDeepSeek(messages, 0.85);
if (result) return result.trim();
return null;
}
// ---------- 6. AI 动态剧情生成 ----------
const STORY_GENERATION_PROMPT = `
你正在创作一款校园恋爱文字冒险游戏《自习室之后》。
【当前游戏状态】
- 玩家姓名:{playerName}
- 当前回合:第{turn}回合
- 当前场景:{scene}
- 当前好感度:{affection}
- 当前已确立关系的男主:{datingTarget}
【剧情背景】
{background}
【最近剧情回顾】
{recentLogs}
【要求】
1. 生成一段 150-250 字的场景描述,用自然的口语化中文,像在讲故事一样。
2. 不要用文艺腔、不要用诗歌化的语言,就像普通人说话那样。
3. 不要出现任何专业术语(如摄影术语、文学术语、心理学词汇等)。
4. 对话要自然,像真实校园里会说的话。
5. 生成 3 个选项,每个选项 8-12 字,必须是具体的行动或对话。
6. 每个选项附带好感度影响(格式:{"key": "影响值"},key为男主ID,如shenjuan、jiangchi等)。
输出格式:
{
"text": "场景描述文字...",
"options": [
{ "text": "选项一文字", "aff": { "shenjuan": 2 } },
{ "text": "选项二文字", "aff": { "jiangchi": 1 } },
{ "text": "选项三文字", "aff": { "xuzhinian": 1 } }
]
}
`;
async function generateAIDynamicStory() {
const apiKey = getStoredAPIKey();
if (!apiKey) return null;
const playerName = G.player.name || '玩家';
const turn = G.turn;
const scene = G.scene || '校园';
const affection = JSON.stringify(G.affection);
const datingTarget = G._datingTarget ? G.contacts[G._datingTarget]?.name || '无' : '无';
let background = '';
if (G._datingTarget) {
const targetName = G.contacts[G._datingTarget]?.name || '';
background = `玩家已经和${targetName}在一起了,两人正在谈恋爱。`;
} else {
background = '玩家还在校园里和几个人保持着朋友关系,还没确定喜欢谁。';
}
const recentLogs = G.log.slice(0, 4).join('\n');
let prompt = STORY_GENERATION_PROMPT
.replace(/{playerName}/g, playerName)
.replace(/{turn}/g, turn)
.replace(/{scene}/g, scene)
.replace(/{affection}/g, affection)
.replace(/{datingTarget}/g, datingTarget)
.replace(/{background}/g, background)
.replace(/{recentLogs}/g, recentLogs || '暂无');
if (G._datingTarget) {
const targetName = G.contacts[G._datingTarget]?.name || '';
prompt += `\n【角色特性】${targetName}是玩家的恋人,说话和普通校园情侣一样自然,不要文艺腔。`;
}
const messages = [
{ role: 'system', content: '你是一个写校园故事的作者,语言要自然口语化,像在跟朋友讲故事一样。不要用文艺腔,不要用专业术语。请严格按照JSON格式输出。' },
{ role: 'user', content: prompt }
];
const result = await callDeepSeek(messages, 0.9);
if (!result) return null;
try {
let jsonStr = result;
const jsonMatch = result.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
if (jsonMatch) jsonStr = jsonMatch[1];
const parsed = JSON.parse(jsonStr);
if (parsed.text && parsed.options && parsed.options.length >= 2) {
return parsed;
}
return null;
} catch (e) {
console.error('解析AI返回失败:', e);
return null;
}
}
// ---------- 7. 兜底剧情 ----------
function getFallbackStory() {
const texts = [
'你走在校园里,阳光不错。你想着刚才的事,觉得有些事顺其自然就好。你决定去镜湖边坐一会儿。',
'教室里很安静,你翻了几页书,但心思不在上面。你拿出手机,想看看有没有新消息。',
'傍晚的风很舒服,你在操场边慢慢走着,觉得这样的日子也挺好的。'
];
const options = [
{ text: "去图书馆转转", aff: { trust: 1 }, next: 'ai_next' },
{ text: "去食堂吃点东西", aff: { stamina: 5 }, next: 'ai_next' },
{ text: "在校园里散散步", aff: { heart: 1 }, next: 'ai_next' }
];
return { text: pick(texts), options: options };
}
// ---------- 8. AI 动态剧情渲染 ----------
async function renderAIGeneratedStory() {
renderStory('📖 正在生成新的故事...\n\n✨ 请稍等片刻', []);
const result = await generateAIDynamicStory();
let finalResult = result;
if (!finalResult) {
finalResult = getFallbackStory();
addLog('⚠️ AI 生成失败,使用兜底剧情');
} else {
addLog(`🤖 AI 动态剧情生成 (T${G.turn})`);
}
const options = finalResult.options.map((opt) => {
return {
text: opt.text,
aff: opt.aff || {},
next: 'ai_next',
_aiGenerated: true
};
});
G._aiCurrentStory = {
text: finalResult.text,
options: options,
turn: G.turn
};
renderStory(finalResult.text, options);
}
// ---------- 9. renderTurn 函数 ----------
async function renderTurn(turnId) {
let node = null;
for (let n of STORY_DATA) {
if (n.id === turnId) { node = n; break; }
}
if (node) {
if (node.condition && !node.condition()) {
let nextNode = null;
for (let n of STORY_DATA) {
if (n.id > turnId && (!n.condition || n.condition())) {
nextNode = n;
break;
}
}
if (nextNode) { renderTurn(nextNode.id); return; }
else { await renderAIGeneratedStory(); return; }
}
let text = node.text;
let options = node.options;
if (node.scene) G.scene = node.scene;
renderStory(text, options);
return;
}
await renderAIGeneratedStory();
}
// ---------- 10. 私聊(接入 AI) ----------
window.sendChat = async function(key) {
const input = document.getElementById(`chat-input-${key}`);
if (!input || !input.value.trim()) return;
const text = input.value.trim();
const time = new Date().toLocaleString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' });
if (!G.chats[key]) G.chats[key] = [];
G.chats[key].push({ from: 'me', text, time });
addLog(`私聊 ${G.contacts[key].name}:${text}`);
input.value = '';
const contact = G.contacts[key];
const chatHistory = G.chats[key] || [];
const chatDiv = document.getElementById('chat-msgs');
if (chatDiv) {
const typingMsg = document.createElement('div');
typingMsg.className = 'chat-msg other';
typingMsg.id = 'typing-indicator';
typingMsg.textContent = '对方正在输入…';
chatDiv.appendChild(typingMsg);
chatDiv.scrollTop = chatDiv.scrollHeight;
}
let reply = await getAIResponse(key, text, chatHistory);
const typingEl = document.getElementById('typing-indicator');
if (typingEl) typingEl.remove();
if (!reply) {
reply = getSmartReply(text, contact.name, G.affection[key] || 0);
console.log('⚠️ AI 回复失败,使用本地回复');
}
setTimeout(() => {
if (!G.chats[key]) G.chats[key] = [];
G.chats[key].push({ from: key, text: reply, time: new Date().toLocaleString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' }) });
addLog(`${G.contacts[key].name} 回复私聊:${reply}`);
if (chatDiv) {
let msgsHtml = '';
G.chats[key].forEach(m => {
const isMe = m.from === 'me';
msgsHtml += ``;
});
chatDiv.innerHTML = msgsHtml;
chatDiv.scrollTop = chatDiv.scrollHeight;
}
const gain = rand(1, 3);
changeAffection(key, gain);
if (rand(1, 3) === 1) showEgg('💬 新消息', `${contact.name} 回复了你:${reply}`, '💬');
playClickSound();
triggerDanmaku();
}, rand(600, 1500));
};
// ---------- 11. 群聊 ----------
function renderGroupChat() {
const members = G.wechatFriends.map(id => G.contacts[id]?.name).filter(Boolean);
if (members.length === 0) {
return `暂无群聊成员,请先添加微信好友
`;
}
const msgs = G.groupChats || [];
let html = `
群成员:
${members.map(m => `${m}`).join('')}
`;
if (msgs.length === 0) {
html += `
群聊空空如也,发条消息吧~
`;
} else {
msgs.forEach(m => {
const isMe = m.from === 'me';
const senderName = isMe ? (G.player.name || '我') : (G.contacts[m.from]?.name || m.from);
html += `
${senderName}
${m.text}
${m.time || ''}
`;
});
}
html += `
`;
return html;
}
window.sendGroupMsg = function() {
const input = document.getElementById('group-input');
if (!input || !input.value.trim()) return;
const text = input.value.trim();
const time = new Date().toLocaleString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' });
if (!G.groupChats) G.groupChats = [];
G.groupChats.push({ from: 'me', text, time });
addLog(`群聊 我:${text}`);
input.value = '';
const members = G.wechatFriends.filter(id => id !== 'wenyao' && G.contacts[id]);
if (members.length > 0 && rand(1, 2) === 1) {
const replierId = pick(members);
const replier = G.contacts[replierId];
if (replier) {
const replies = ['哈哈', '真的吗', '好呀', '收到', '不错', '😄', '支持', '+1', '我也这么觉得', '嗯嗯', '对!', '有道理'];
const reply = pick(replies);
setTimeout(() => {
G.groupChats.push({ from: replierId, text: reply, time: new Date().toLocaleString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' }) });
addLog(`群聊 ${replier.name}:${reply}`);
const msgsDiv = document.getElementById('group-msgs');
if (msgsDiv) {
let html = '';
G.groupChats.forEach(m => {
const isMe = m.from === 'me';
const senderName = isMe ? (G.player.name || '我') : (G.contacts[m.from]?.name || m.from);
html += `${senderName}
${m.text}
${m.time || ''}
`;
});
msgsDiv.innerHTML = html;
msgsDiv.scrollTop = msgsDiv.scrollHeight;
}
changeAffection(replierId, 1);
if (rand(1, 3) === 1) showEgg('💬 群聊消息', `${replier.name} 回复了:${reply}`, '💬');
playClickSound();
triggerDanmaku();
}, rand(600, 1800));
}
}
const msgsDiv = document.getElementById('group-msgs');
if (msgsDiv) {
let html = '';
G.groupChats.forEach(m => {
const isMe = m.from === 'me';
const senderName = isMe ? (G.player.name || '我') : (G.contacts[m.from]?.name || m.from);
html += `${senderName}
${m.text}
${m.time || ''}
`;
});
msgsDiv.innerHTML = html;
msgsDiv.scrollTop = msgsDiv.scrollHeight;
}
playClickSound();
};
// ---------- 12. 朋友圈 ----------
function generateRandomMoment() {
const friendIds = G.wechatFriends;
if (friendIds.length === 0) return;
const id = pick(friendIds);
const contact = G.contacts[id];
if (!contact) return;
const texts = ['今天天气真好,适合出去走走。', '深夜自习室,只有我和我的咖啡。', '图书馆的猫又来了,它好像很喜欢我。', '篮球场今天人好多,打了三场才尽兴。', '刚看完一部电影,有点感动。', '食堂的糖醋排骨今天特别好吃!', '收到一条有趣的留言,心情不错。', '下雨了,没带伞,有人在等我吗?', '初雪,许个愿吧。', '又熬夜了,但为了梦想值得。', '今天遇到一个很有趣的人。', '生活就像巧克力,你永远不知道下一颗是什么味道。'];
const content = pick(texts);
const time = new Date().toLocaleString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' });
const likeUsers = [], commentUsers = [];
const potentialLikers = friendIds.filter(f => f !== id);
if (potentialLikers.length > 0) {
const numLikes = rand(0, Math.min(2, potentialLikers.length));
for (let i = 0; i < numLikes; i++) { const liker = pick(potentialLikers); if (!likeUsers.includes(liker)) likeUsers.push(liker); }
}
if (potentialLikers.length > 0 && rand(1, 4) === 1) {
const commenter = pick(potentialLikers);
const cmtTexts = ['好棒!', '我也想去', '哈哈', '真不错', '加油~', '好美'];
const cmt = pick(cmtTexts);
commentUsers.push({ userId: commenter, text: cmt });
}
const moment = { id, name: contact.name, avatar: contact.avatar || '👤', content, time, likes: likeUsers, comments: commentUsers };
G.moments.unshift(moment);
if (G.moments.length > 30) G.moments.pop();
}
function renderMoments() {
let html = `
💡 只有你的微信好友动态会显示,评论仅共同好友可见
`;
const visibleMoments = G.moments.filter(m => {
if (m.id === 'me') return true;
return G.wechatFriends.includes(m.id);
});
if (visibleMoments.length === 0) {
html += `暂无动态,发布第一条吧~
`;
} else {
visibleMoments.forEach((m, idx) => {
const visibleLikes = m.likes.filter(uid => G.wechatFriends.includes(uid) || uid === 'me');
const visibleComments = m.comments.filter(c => G.wechatFriends.includes(c.userId) || c.userId === 'me');
html += `
${m.content}
❤️ ${visibleLikes.length}
💬 ${visibleComments.length}
`;
});
}
return html;
}
window.publishMoment = function() {
const input = document.getElementById('moment-input');
if (!input || !input.value.trim()) return;
const content = input.value.trim();
const moment = {
id: 'me',
name: G.player.name || '我',
avatar: '🌸',
content,
time: new Date().toLocaleString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' }),
likes: [],
comments: []
};
G.moments.unshift(moment);
if (G.moments.length > 30) G.moments.pop();
input.value = '';
const modalBody = document.querySelector('.modal-overlay.show .modal-body');
if (modalBody) modalBody.innerHTML = renderMoments();
showEgg('📱 发布成功', '你的动态已经发出,说不定有人会点赞哦~', '📱');
addLog(`发布朋友圈:${content}`);
playClickSound();
triggerDanmaku();
};
window.likeMoment = function(id) {
for (let m of G.moments) {
if (m.id === id) {
if (!m.likes.includes('me')) {
m.likes.push('me');
if (rand(1, 3) === 1) showEgg('❤️ 点赞', '你给 ' + m.name + ' 点了个赞!', '❤️');
}
break;
}
}
const modalBody = document.querySelector('.modal-overlay.show .modal-body');
if (modalBody) modalBody.innerHTML = renderMoments();
playClickSound();
triggerDanmaku();
};
// ---------- 13. 论坛 ----------
function renderForum() {
let html = `
📋 校园论坛 · 全体校友发帖区
`;
if (!G.forumPosts) G.forumPosts = [];
if (G.forumPosts.length === 0) {
html += `还没有帖子,快来发第一篇吧~
`;
} else {
G.forumPosts.forEach(p => {
const visibleLikes = p.likes.filter(uid => uid === 'me' || G.wechatFriends.includes(uid));
const visibleComments = p.comments.filter(c => c.userId === 'me' || G.wechatFriends.includes(c.userId));
html += `
${p.title}
${p.content}
❤️ ${visibleLikes.length}
💬 ${visibleComments.length}
`;
});
}
return html;
}
window.publishForumPost = function() {
if (!G.forumPosts) G.forumPosts = [];
const titleInput = document.getElementById('forum-title');
const contentInput = document.getElementById('forum-content');
if (!titleInput || !contentInput) {
showEgg('⚠️ 错误', '论坛输入框未找到,请刷新页面', '⚠️');
return;
}
const title = titleInput.value.trim();
const content = contentInput.value.trim();
if (!title || !content) {
showEgg('⚠️ 提示', '标题和内容都不能为空!', '⚠️');
return;
}
const post = {
id: 'post_' + Date.now(),
name: G.player.name || '我',
avatar: '🌸',
title,
content,
time: new Date().toLocaleString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' }),
likes: [],
comments: []
};
G.forumPosts.unshift(post);
if (G.forumPosts.length > 50) G.forumPosts.pop();
titleInput.value = '';
contentInput.value = '';
const modalBody = document.querySelector('.modal-overlay.show .modal-body');
if (modalBody) modalBody.innerHTML = renderForum();
showEgg('📋 发帖成功', '你的帖子已发布到校园论坛!', '📋');
addLog(`论坛发帖:${title}`);
playClickSound();
triggerDanmaku();
};
window.likePost = function(id) {
for (let p of G.forumPosts) {
if (p.id === id) {
if (!p.likes.includes('me')) {
p.likes.push('me');
if (rand(1, 3) === 1) showEgg('❤️ 点赞', '你给帖子点了个赞!', '❤️');
}
break;
}
}
const modalBody = document.querySelector('.modal-overlay.show .modal-body');
if (modalBody) modalBody.innerHTML = renderForum();
playClickSound();
triggerDanmaku();
};
// ---------- 14. 通讯录 ----------
function renderContacts() {
const wechatList = G.wechatFriends;
const phoneList = G.phoneContacts;
let html = `
📱 微信好友
`;
if (wechatList.length === 0) html += `暂无微信好友
`;
else {
wechatList.forEach(id => {
const c = G.contacts[id]; if (!c) return;
html += `
`;
});
}
html += `📞 手机联系人
`;
if (phoneList.length === 0) html += `暂无手机联系人
`;
else {
phoneList.forEach(id => {
const c = G.contacts[id]; if (!c) return;
html += `
`;
});
}
return html;
}
window.addWechat = async function() {
const input = document.getElementById('add-contact-input');
if (!input) return;
const id = input.value.trim();
const map = { 'shenjuan':'沈倦', 'jiangchi':'江迟', 'xuzhinian':'许知年', 'luyuzhou':'陆屿舟', 'chengyan':'程砚', 'wenyao':'温知遥' };
if (map[id]) {
if (!G.wechatFriends.includes(id)) {
const result = await showConfirm({
title: '🌸 添加好友',
message: `是否添加 ${map[id]} 为微信好友?`,
confirmText: '💕 同意',
cancelText: '😅 拒绝'
});
if (result) {
G.wechatFriends.push(id);
if (!G.phoneContacts.includes(id)) G.phoneContacts.push(id);
showEgg('✅ 添加成功', `已添加 ${map[id]} 为微信好友!`, '✅');
addLog(`添加了 ${map[id]} 为微信好友`);
refreshContactsModal();
} else {
showEgg('❌ 已取消', '你拒绝了添加好友', '❌');
}
} else showEgg('⚠️ 已存在', `${map[id]} 已经是你的好友了`, '⚠️');
} else showEgg('❌ 未找到', '该ID不存在,可尝试 shenjuan, jiangchi 等', '❌');
input.value = '';
refreshContactsModal();
playClickSound();
triggerDanmaku();
};
window.deleteWechat = async function(id) {
const result = await showConfirm({ title: '🗑️ 删除好友', message: `确定删除 ${G.contacts[id].name} 吗?`, confirmText: '💔 删除', cancelText: '😊 取消' });
if (result) { G.wechatFriends = G.wechatFriends.filter(f => f !== id); refreshContactsModal(); showEgg('🗑️ 已删除', `你删除了 ${G.contacts[id].name}`, '🗑️'); addLog(`删除了微信好友 ${G.contacts[id].name}`); }
playClickSound(); triggerDanmaku();
};
window.deletePhone = async function(id) {
const result = await showConfirm({ title: '🗑️ 删除联系人', message: `确定删除手机联系人 ${G.contacts[id].name} 吗?`, confirmText: '💔 删除', cancelText: '😊 取消' });
if (result) { G.phoneContacts = G.phoneContacts.filter(f => f !== id); refreshContactsModal(); showEgg('🗑️ 已删除', `删除了 ${G.contacts[id].name}`, '🗑️'); addLog(`删除了手机联系人 ${G.contacts[id].name}`); }
playClickSound(); triggerDanmaku();
};
window.editContact = function(id) {
const newNote = prompt('输入备注名:', G.contacts[id].note || '');
if (newNote !== null) { G.contacts[id].note = newNote; refreshContactsModal(); showEgg('✏️ 备注已更新', `备注为:${newNote||'无'}`, '✏️'); addLog(`修改了 ${G.contacts[id].name} 的备注为 ${newNote}`); }
playClickSound(); triggerDanmaku();
};
window.callContact = function(id) {
const contact = G.contacts[id]; if (!contact) return;
showEgg('📞 通话中', `你正在和 ${contact.name} 通话……`, '📞');
changeAffection(id, 1);
playClickSound(); triggerDanmaku();
};
function refreshContactsModal() {
const overlays = document.querySelectorAll('.modal-overlay.show');
for (let overlay of overlays) {
const titleEl = overlay.querySelector('.modal-header h3');
if (titleEl && (titleEl.textContent.includes('通讯录') || titleEl.textContent.includes('📱 通讯录'))) {
const body = overlay.querySelector('.modal-body');
if (body) {
body.innerHTML = renderContacts();
const addBtn = body.querySelector('#add-contact-btn');
if (addBtn) addBtn.onclick = addWechat;
const input = body.querySelector('#add-contact-input');
if (input) input.onkeydown = function(e) { if (e.key === 'Enter') addWechat(); };
}
}
}
}
function openChat(key) {
const contact = G.contacts[key]; if (!contact) return;
if (!G.chats[key]) G.chats[key] = [];
const msgs = G.chats[key];
let html = ``;
if (msgs.length === 0) html += `
开始聊天吧~
`;
else { msgs.forEach(m => { const isMe = m.from === 'me'; html += `
`; }); }
html += `
`;
const container = document.getElementById('modal-container');
const overlay = document.createElement('div');
overlay.className = 'modal-overlay show';
overlay.innerHTML = ``;
container.appendChild(overlay);
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
setTimeout(() => { const msgsDiv = document.getElementById('chat-msgs'); if (msgsDiv) msgsDiv.scrollTop = msgsDiv.scrollHeight; }, 50);
playClickSound(); triggerDanmaku();
}
// ---------- 15. 日记 ----------
function renderDiary() {
let html = `
📖 记录下每天的点点滴滴
`;
if (G.diary.length === 0) { html += `还没有日记,写下第一篇吧~
`; }
else {
G.diary.slice().reverse().forEach(entry => {
html += `📅 T${entry.turn} · ${entry.time}
${entry.text}
`;
});
}
return html;
}
window.addDiary = function() {
const input = document.getElementById('diary-input');
if (!input || !input.value.trim()) return;
const text = input.value.trim();
G.diary.push({ turn: G.turn, time: G.time, text: text });
input.value = '';
const modalBody = document.querySelector('.modal-overlay.show .modal-body');
if (modalBody) modalBody.innerHTML = renderDiary();
showEgg('📖 日记已记录', '你的心情被珍藏了~', '📖');
addLog(`日记:${text}`);
playClickSound(); triggerDanmaku();
};
// ---------- 16. 小游戏 ----------
function renderGameLobby() {
const friends = G.wechatFriends.filter(id => id !== 'wenyao' && G.contacts[id]);
let html = `🎮 选择游戏并邀请好友
`;
if (friends.length === 0) { html += `请先添加微信好友才能邀请一起玩哦~
`; return html; }
html += ``;
const games = [{ id: 'rps', name: '✊ 石头剪刀布' }, { id: 'gomoku', name: '⚫ 五子棋' }];
games.forEach(game => {
html += `
${game.name}
`;
friends.forEach(id => {
const c = G.contacts[id];
html += ``;
});
html += `
`;
});
html += `
`;
return html;
}
window.inviteGame = function(opponentId, gameType) {
const container = document.getElementById('modal-container');
const overlay = document.createElement('div');
overlay.className = 'modal-overlay show';
let gameHtml = '';
if (gameType === 'rps') gameHtml = renderRPS(opponentId);
else if (gameType === 'gomoku') gameHtml = renderGomoku(opponentId);
overlay.innerHTML = `
`;
container.appendChild(overlay);
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
playClickSound(); triggerDanmaku();
};
function renderRPS(opponentId) {
return `
✊ 石头 ✋ 剪刀 ✌️ 布
点击选择你的手势
`;
}
window.playRPS = function(opponentId, myChoice) {
const choices = ['石头', '剪刀', '布'];
const oppChoice = pick(choices);
let result = '';
if (myChoice === oppChoice) result = '平局 🤝';
else if ((myChoice === '石头' && oppChoice === '剪刀') || (myChoice === '剪刀' && oppChoice === '布') || (myChoice === '布' && oppChoice === '石头')) {
result = '你赢了! 🎉';
changeAffection(opponentId, 0.5);
showEgg('🎉 赢了', `你赢了 ${G.contacts[opponentId].name}!`, '🎉');
} else {
result = '你输了 😅';
changeAffection(opponentId, -0.3);
showEgg('😅 输了', `你输给了 ${G.contacts[opponentId].name}`, '😅');
}
const resultDiv = document.getElementById('rps-result');
if (resultDiv) {
resultDiv.innerHTML = `你出了 ${myChoice},对方出了 ${oppChoice}
${result}`;
}
addLog(`石头剪刀布:你出${myChoice},${G.contacts[opponentId].name}出${oppChoice},${result}`);
playClickSound(); triggerDanmaku();
};
function renderGomoku(opponentId) {
const size = 15;
let boardHtml = `⚫ 黑棋先行 · 点击落子
`;
for (let r = 0; r < size; r++) {
for (let c = 0; c < size; c++) {
boardHtml += `
`;
}
}
boardHtml += `
⚫ 轮到你走
`;
window._gomoku = { board: Array.from({ length: size }, () => Array(size).fill(null)), turn: 'black', over: false, opponent: opponentId };
return boardHtml;
}
window.gomokuMove = function(opponentId, row, col) {
const g = window._gomoku;
if (!g || g.over || g.turn !== 'black') return;
if (g.board[row][col]) return;
g.board[row][col] = 'black';
const cell = document.querySelector(`.gomoku-cell[data-row="${row}"][data-col="${col}"]`);
if (cell) { cell.classList.add('black'); cell.textContent = '●'; }
if (checkGomokuWin(g.board, row, col, 'black')) {
g.over = true;
document.getElementById('gomoku-status').textContent = '🎉 你赢了!';
showEgg('🎉 五子棋胜利', `你赢了 ${G.contacts[opponentId].name}!`, '🎉');
changeAffection(opponentId, 1);
addLog(`五子棋:击败了 ${G.contacts[opponentId].name}`);
triggerDanmaku();
return;
}
g.turn = 'white';
document.getElementById('gomoku-status').textContent = '🤔 对方思考中...';
setTimeout(() => {
if (g.over) return;
let empty = [];
for (let r = 0; r < 15; r++) for (let c = 0; c < 15; c++) if (!g.board[r][c]) empty.push([r, c]);
if (empty.length === 0) { g.over = true; document.getElementById('gomoku-status').textContent = '平局 🤝'; return; }
const [aiR, aiC] = pick(empty);
g.board[aiR][aiC] = 'white';
const cell2 = document.querySelector(`.gomoku-cell[data-row="${aiR}"][data-col="${aiC}"]`);
if (cell2) { cell2.classList.add('white'); cell2.textContent = '○'; }
if (checkGomokuWin(g.board, aiR, aiC, 'white')) {
g.over = true;
document.getElementById('gomoku-status').textContent = '😅 你输了...';
showEgg('😅 五子棋失败', `你输给了 ${G.contacts[opponentId].name}`, '😅');
changeAffection(opponentId, -0.5);
addLog(`五子棋:输给了 ${G.contacts[opponentId].name}`);
triggerDanmaku();
return;
}
g.turn = 'black';
document.getElementById('gomoku-status').textContent = '⚫ 轮到你走';
}, 500);
};
function checkGomokuWin(board, row, col, color) {
const directions = [[1,0],[0,1],[1,1],[1,-1]];
for (let [dr, dc] of directions) {
let count = 1;
for (let step = 1; step < 5; step++) {
const nr = row + dr*step, nc = col + dc*step;
if (nr < 0 || nr >= 15 || nc < 0 || nc >= 15 || board[nr][nc] !== color) break;
count++;
}
for (let step = 1; step < 5; step++) {
const nr = row - dr*step, nc = col - dc*step;
if (nr < 0 || nr >= 15 || nc < 0 || nc >= 15 || board[nr][nc] !== color) break;
count++;
}
if (count >= 5) return true;
}
return false;
}
// ---------- 17. 地图 ----------
function renderMap() {
let html = `🗺️ 当前场景:${G.scene}
`;
for (const [name, data] of Object.entries(G.scenes)) {
const unlocked = data.unlock || (data.unlockTurn && G.turn >= data.unlockTurn);
const people = data.people.join('、');
const status = unlocked ? '✅' : '🔒';
const extra = unlocked ? '' : ` (解锁条件:T${data.unlockTurn})`;
html += `${status} ${name}${unlocked ? people : '未解锁'}${extra}
`;
}
return html;
}
// ---------- 18. 状态 ----------
function renderStatus() {
const a = G.affection;
const trustLabel = getTrustLabel(G.trust);
return `
时间
${G.time}
天气
${G.weather} · ${G.temp}℃
场景
${G.scene}
体力
${G.stamina}/100 ${getStaminaLabel(G.stamina)}
心动
${G.heart}
害羞
${G.shy}
信任
${G.trust} · ${trustLabel}
回合
T${G.turn}
好感度
${Object.entries(a).map(([k,v]) => `${({shenjuan:'沈倦',jiangchi:'江迟',xuzhinian:'许知年',luyuzhou:'陆屿舟',chengyan:'程砚'})[k]||k}${Math.round(v)}
`).join('')}
微信好友:${G.wechatFriends.map(id=>G.contacts[id]?.name).filter(Boolean).join('、')||'无'}
手机联系人:${G.phoneContacts.map(id=>G.contacts[id]?.name).filter(Boolean).join('、')||'无'}
日记数:${G.diary.length} 篇
论坛帖子:${G.forumPosts.length} 条
`;
}
// ---------- 19. 成就 ----------
function renderAchievements() {
const list = Object.entries(G.achievements).filter(([k,v]) => v).map(([k]) => k);
const emojis = { first_meet:'🌸', night_stay:'🌙', photo:'📷', umbrella:'☔', snow:'❄️', heart_moment:'💕' };
const names = { first_meet:'初遇碎片', night_stay:'深夜共处', photo:'被偷拍了!', umbrella:'雨中共伞', snow:'初雪告白', heart_moment:'心动瞬间' };
if (list.length === 0) return '🔒 暂无成就
';
return list.map(k => `${emojis[k]||'✨'} ${names[k]||k}
`).join('');
}
// ---------- 20. 微信设置 ----------
function renderSocialSettings() {
return `
`;
}
window.saveSocial = function() {
const name = document.getElementById('social-name').value.trim();
const sig = document.getElementById('social-sig').value.trim();
if (name) G.social.nickname = name;
G.social.signature = sig || '✨ 今天也是元气满满~';
G.player.name = G.social.nickname;
showEgg('✅ 已更新', `昵称:${G.social.nickname}\n签名:${G.social.signature}`, '✅');
addLog(`更新微信资料:昵称 ${G.social.nickname},签名 ${G.social.signature}`);
const overlay = document.querySelector('.modal-overlay.show');
if (overlay) overlay.remove();
playClickSound(); triggerDanmaku();
};
// ---------- 21. 关系图 ----------
function renderRelation() {
const a = G.affection;
const names = { shenjuan:'沈倦', jiangchi:'江迟', xuzhinian:'许知年', luyuzhou:'陆屿舟', chengyan:'程砚' };
let html = `💕 当前关系进度
`;
for (const [key, val] of Object.entries(a)) {
const name = names[key] || key;
const phase = getPhaseLabel(val);
const pct = val;
html += `
${name}
${Math.round(val)}
${phase}
`;
}
html += `💡 点击通讯录中的好友可聊天增进感情
`;
return html;
}
// ---------- 22. 体力恢复 ----------
document.getElementById('btn-rest').addEventListener('click', function() {
const container = document.getElementById('modal-container');
const overlay = document.createElement('div');
overlay.className = 'modal-overlay show';
overlay.innerHTML = `
当前体力:${G.stamina}/100
每次休息会推进15分钟(1回合)
`;
container.appendChild(overlay);
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
playClickSound();
});
window.doRest = function(action, amount) {
const overlay = document.querySelector('.modal-overlay.show');
if (overlay) overlay.remove();
const oldStamina = G.stamina;
G.stamina = Math.min(G.stamina + amount, G.maxStamina);
const actualGain = G.stamina - oldStamina;
if (actualGain === 0) { showEgg('💪 体力已满', '你已经精力充沛了,不需要休息~', '💪'); return; }
const t = G.time.split('·');
let hour = parseInt(t[2].trim().split(':')[0]);
let min = parseInt(t[2].trim().split(':')[1]) + 15;
if (min >= 60) { min -= 60; hour += 1; }
if (hour >= 24) { hour -= 24; }
const hh = String(hour).padStart(2, '0');
const mm = String(min).padStart(2, '0');
G.time = t[0] + '·' + t[1] + '·' + hh + ':' + mm;
addLog(`休息了一下(${action}),体力 +${actualGain}`);
showEgg(`🍵 休息完毕`, `你${action}了一下,体力恢复了 ${actualGain} 点(当前 ${G.stamina}/100)`, '🍵');
renderTopStats();
playClickSound();
};
// ---------- 23. 结局系统 ----------
function getEnding() {
const a = G.affection;
const trust = G.trust;
const heart = G.heart;
const hasDating = G._dating || false;
const target = G._datingTarget;
if (G._married) {
const name = target ? G.contacts[target]?.name || '某人' : '某人';
return { title: '💕 白头偕老', desc: `你和${name}步入了婚姻的殿堂,从此携手一生。`, emoji: '💕' };
}
if (hasDating && target) {
const name = G.contacts[target]?.name || '某人';
const aff = a[target] || 0;
if (aff >= 80 && trust >= 70) return { title: '💕 余生是你', desc: `你和${name}经历了风风雨雨,最终决定共度余生。`, emoji: '💕' };
else if (aff >= 60 && trust >= 50) return { title: '💛 细水长流', desc: `你们在一起很幸福,平淡中满是温馨。`, emoji: '💛' };
else if (aff >= 40) return { title: '😶 渐行渐远', desc: `你们曾经相爱,但生活的琐碎让你们慢慢走散了。`, emoji: '😶' };
else return { title: '💔 分道扬镳', desc: `你们最终选择了分开。有些人来到生命里,只是为了陪你走一段路。`, emoji: '💔' };
}
let maxAff = 0, maxKey = '';
for (let [k, v] of Object.entries(a)) { if (v > maxAff) { maxAff = v; maxKey = k; } }
const maxName = maxKey ? G.contacts[maxKey]?.name || '某人' : '某人';
if (maxAff >= 80 && heart >= 70) return { title: '💕 恋人未满', desc: `你和${maxName}彼此深爱,却始终没有说出口。`, emoji: '💕' };
else if (maxAff >= 60 && heart >= 50) return { title: '💛 友达以上', desc: `你和${maxName}的关系超越了友情,但始终差一步。`, emoji: '💛' };
else if (maxAff >= 40 && trust >= 60) return { title: '😊 最好的朋友', desc: `你和${maxName}是彼此最信任的朋友。`, emoji: '😊' };
else if (maxAff >= 30) return { title: '😐 普通朋友', desc: `你们是偶尔联系的朋友,想起时依然会笑。`, emoji: '😐' };
else if (trust <= 20 || maxAff < 20) return { title: '😠 误会与疏远', desc: `你们之间发生了太多误会,最终渐行渐远。`, emoji: '😠' };
else if (G.turn > 60 && maxAff < 40) return { title: '📭 各自安好', desc: `毕业后,你们各自奔向不同的人生,没有刻意联系。`, emoji: '📭' };
else return { title: '🌸 未完待续', desc: `你的故事还没有结束……也许在未来的某一天,还会有新的相遇。`, emoji: '🌸' };
}
function showEnding() {
const ending = getEnding();
showEgg(`🏁 结局 · ${ending.title}`, `${ending.desc}\n\n—— 感谢你参与《自习室之后》的故事,愿你的人生也如这般精彩。`, ending.emoji);
}
// ---------- 24. 启动游戏 ----------
function startGame() {
G.player.name = document.getElementById('f-name').value || '玩家';
G.player.age = document.getElementById('f-age').value || '18';
G.player.birth = document.getElementById('f-birth').value || '未知';
G.player.mbti = document.getElementById('f-mbti').value || 'ENFP';
G.player.home = document.getElementById('f-home').value || '南方';
G.player.height = document.getElementById('f-height').value || '158';
G.player.body = document.getElementById('f-body').value || '娇小匀称';
G.player.look = document.getElementById('f-look').value || '齐刘海黑长直';
G.player.personality = document.getElementById('f-personality').value || '开朗爱笑';
G.social.nickname = G.player.name;
G.social.signature = '✨ 今天也是元气满满~';
document.getElementById('splash').classList.add('hide');
document.getElementById('game').classList.add('active');
initAudio();
G.initialized = true;
renderTopStats();
if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
if (!audioPlaying) { isStopping = false; playMoodMusic(currentMood); }
const sampleMoments = [{ id:'wenyao', name:'温知遥', avatar:'👩', content:'今天和好朋友一起吃了火锅,好开心!', time:'20:30', likes:[], comments:[] }];
G.moments = sampleMoments;
for (let i = 0; i < 5; i++) generateRandomForumPost();
if (danmakuTimer) clearTimeout(danmakuTimer);
setTimeout(autoDanmaku, 3000);
showEgg('🌸 欢迎来到自习室之后', `你好,${G.player.name}!\n你的故事即将开始,每一个选择都会带来不同的相遇。\n—— 66 祝你玩得开心 💕`, '📖');
renderTurn(1);
}
document.getElementById('splash-start').addEventListener('click', startGame);
document.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !document.getElementById('splash').classList.contains('hide')) startGame();
});
// ---------- 25. 彩蛋触发器 ----------
document.getElementById('author-egg').addEventListener('click', () => {
showEgg('💖 作者:66', '66 说:\n“写这个游戏的时候,我一直在想,\n如果青春可以重来,你会选择谁?”\n—— 希望你在游戏里找到答案 ✨', '🖋️');
createFloatingHeart(event.clientX, event.clientY);
playClickSound(); triggerDanmaku();
});
document.getElementById('title-egg').addEventListener('dblclick', () => {
showEgg('📖 自习室之后', '“深夜的自习室,藏着多少未说出口的心动?”\n—— 66 的寄语', '📖');
playClickSound(); triggerDanmaku();
});
document.getElementById('stat-turn').addEventListener('click', () => {
showEgg('🕐 时间', `已经是第 ${G.turn} 回合了,\n你离那个重要的时刻越来越近。`, '⏳');
playClickSound(); triggerDanmaku();
});
document.getElementById('stat-weather').addEventListener('click', () => {
const msgs = ['今天的天气像你的心情一样明媚。', '下雨天,适合躲在同一把伞下。', '雪天,适合告白。'];
showEgg('🌤 天气', pick(msgs) + '\n—— 66 说', '🌤');
playClickSound(); triggerDanmaku();
});
document.getElementById('stat-stamina').addEventListener('click', () => {
showEgg('💪 体力', `当前体力 ${G.stamina},\n累了就休息,说不定会有人来关心你。`, '💤');
playClickSound(); triggerDanmaku();
});
document.getElementById('stat-heart').addEventListener('click', () => {
const names = ['沈倦','江迟','许知年','陆屿舟','程砚'];
const name = pick(names);
showEgg('💗 心动', `你突然想到 ${name} 的身影,\n心跳漏了一拍。\n—— 66:这就是心动的感觉吗?`, '❤️');
createFloatingHeart(event.clientX, event.clientY);
playClickSound(); triggerDanmaku();
});
// ---------- 26. 底部按钮事件 ----------
document.getElementById('btn-status').addEventListener('click', () => { openModal(renderStatus(), '📊 状态面板'); });
document.getElementById('btn-achievement').addEventListener('click', () => { openModal(renderAchievements(), '🏆 成就收集'); });
document.getElementById('btn-relation').addEventListener('click', () => { openModal(renderRelation(), '💕 关系图'); });
document.getElementById('btn-contacts').addEventListener('click', () => { openModal(renderContacts(), '📱 通讯录'); });
document.getElementById('btn-social').addEventListener('click', () => {
openModal(renderMoments() + `
`, '🌸 朋友圈');
});
document.getElementById('btn-map').addEventListener('click', () => { openModal(renderMap(), '🗺️ 场景地图'); });
document.getElementById('btn-group').addEventListener('click', () => {
openModal(renderGroupChat(), '💬 群聊');
setTimeout(() => { const msgsDiv = document.getElementById('group-msgs'); if (msgsDiv) msgsDiv.scrollTop = msgsDiv.scrollHeight; }, 100);
});
document.getElementById('btn-diary').addEventListener('click', () => { openModal(renderDiary(), '📖 日记'); });
document.getElementById('btn-forum').addEventListener('click', () => { openModal(renderForum(), '📋 校园论坛'); });
document.getElementById('btn-game').addEventListener('click', () => { openModal(renderGameLobby(), '🎮 小游戏'); });
document.getElementById('btn-ending').addEventListener('click', function() {
const ending = getEnding();
showEgg(`🏁 结局 · ${ending.title}`, `${ending.desc}\n\n—— 感谢你参与《自习室之后》的故事,愿你的人生也如这般精彩。`, ending.emoji);
playClickSound();
});
document.getElementById('btn-restart').addEventListener('click', () => {
if (confirm('🔄 确定要重新开始吗?当前进度将丢失。')) {
showEgg('🔄 重新开始', '66 说:\n“每一次重来,都是一次新的可能。”\n—— 祝你好运!');
setTimeout(() => location.reload(), 800);
}
});
document.getElementById('music-toggle').addEventListener('click', toggleMusic);
document.addEventListener('keydown', (e) => {
if (e.key === 'm' || e.key === 'M') toggleMusic();
if (e.key === 'r' || e.key === 'R') { if (confirm('🔄 确定要重新开始吗?')) location.reload(); }
const optBtns = document.querySelectorAll('.option-btn');
const idx = parseInt(e.key) - 1;
if (idx >= 0 && idx < optBtns.length) optBtns[idx].click();
});
// ---------- 27. 免责声明弹窗 ----------
window.addEventListener('DOMContentLoaded', function() {
setTimeout(function() {
showStartDisclaimer();
}, 300);
});
function showStartDisclaimer() {
const container = document.getElementById('modal-container');
if (!container) return;
const overlay = document.createElement('div');
overlay.className = 'modal-overlay show';
overlay.id = 'disclaimer-overlay';
overlay.style.zIndex = '9999';
overlay.innerHTML = `
✧ 欢迎来到《自习室之后》 ✧
本游戏接入了 DeepSeek AI 作为智能对话引擎,私聊、群聊及剧情生成会由 AI 实时驱动。
💬 私聊和群聊:AI 会根据你的消息和角色人设生成自然回复,尽量像真人聊天一样,不会说肉麻的话,也不会用专业术语。
📖 剧情生成:当预设剧情结束后,AI 会基于当前故事状态自动生成新剧情,让故事无限延续。
🎨 本作希望提供自然、轻松、不油腻的校园恋爱体验,所有对话均刻意避免文艺腔和专业术语。
✦ AI 生成的回复可能不完美,如有偏差请多包涵。
✦ 你的每一次互动都会影响剧情走向,请慢慢体验 ❤️
—— 作者 66 敬上
`;
container.appendChild(overlay);
const confirmBtn = document.getElementById('disclaimer-confirm-btn');
if (confirmBtn) {
confirmBtn.addEventListener('click', function() {
if (overlay.parentNode) overlay.remove();
playClickSound();
});
}
overlay.addEventListener('click', function(e) { if (e.target === overlay) { /* 不关闭 */ } });
playClickSound();
}
// ---------- 28. 初始化 ----------
renderTopStats();
console.log('📖 自习室之后 · 完整版(接入 DeepSeek AI)已加载!');
console.log('🔑 请点击 ⚙️ 设置 API 输入你的 Key');
console.log('🎵 按 M 切换背景音乐');
document.addEventListener('click', () => {
if (!audioCtx && !document.getElementById('splash').classList.contains('hide')) {
initAudio();
if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
if (G.initialized && !audioPlaying) { isStopping = false; playMoodMusic(currentMood); }
}
}, { once: true });
document.addEventListener('dblclick', (e) => {
if (!e.target.closest('.modal-overlay') && !e.target.closest('#egg-modal')) {
showEgg('✨ 双击彩蛋', '你双击了画面!\n—— 66:看来你很喜欢这个游戏呢 💕', '✨');
triggerDanmaku();
}
});
document.addEventListener('mousemove', (e) => {
if (G.initialized && rand(1, 100) === 1) createFloatingHeart(e.clientX, e.clientY);
});