feat: add age-based event gating, event timer, and UI polish

- Add minAge/maxAge to events so infants can't go treasure hunting
- Cache event panel DOM to prevent high-speed button destruction
- Add 10s auto-select countdown for choice events
- Fix event title/text field mapping (name/description → title/text)
- Add rotating clock icon for time flow feedback
- Fix speed/pause button active states
- Fix shop affordability check (disable + show insufficient money)
- Add red styling for unmet choice requirements
- Fix log re-rendering on every tick
This commit is contained in:
2026-05-13 09:09:42 +00:00
parent a05225b514
commit 3f741b4f0a
41 changed files with 9847 additions and 651 deletions
+173
View File
@@ -0,0 +1,173 @@
// ==================== 死亡与结算 ====================
import { state, addLog, resetStateForRebirth } from './state.js';
import { CAREERS } from './expSystem.js';
import { checkNewEntries as checkNewEntriesFromTalents } from '../content/talents.js';
// 检查是否死亡
export function checkDeath() {
if (!state.alive) return true;
// 年龄死亡
if (state.age >= 100) {
die('寿终正寝');
return true;
}
if (state.age >= 50) {
const deathChance = getAgeDeathChance(state.age);
if (Math.random() < deathChance) {
die('寿终正寝');
return true;
}
}
// 外敌死亡
for (const [type, progress] of Object.entries(state.enemyProgress)) {
if (progress >= 100 && Math.random() < 0.4) {
const names = { military: '死于蛮族入侵', spiritual: '被天魔吞噬', political: '王朝覆灭,死于战乱' };
die(names[type] || '死于外敌');
return true;
}
}
// 体质死亡
if (state.stats.body <= 0) {
die('体弱身亡');
return true;
}
// 危机感死亡(极端情况)
if (state.crisisLevel >= 100 && Math.random() < 0.3) {
die('在极度恐惧中精神崩溃');
return true;
}
return false;
}
function getAgeDeathChance(age) {
if (age < 50) return 0;
if (age < 60) return 0.02;
if (age < 70) return 0.08;
if (age < 80) return 0.20;
if (age < 90) return 0.45;
if (age < 100) return 0.80;
return 1.0;
}
// 死亡结算
function die(reason) {
if (!state.alive) return;
state.alive = false;
addLog(`你死了。原因:${reason}`, 'death');
// 结算元经验
const metaGains = {};
for (const [id, career] of Object.entries(state.careers)) {
const config = CAREERS[id];
if (!config) continue;
let totalExp = career.exp;
for (let i = 0; i < career.level; i++) {
totalExp += Math.floor(config.baseExp * Math.pow(config.expCurve, i));
}
const gain = Math.floor(totalExp * 0.1);
state.metaExp[id] = (state.metaExp[id] || 0) + gain;
metaGains[id] = gain;
}
// 解锁新词条
const newEntries = checkNewEntriesFromTalents(state);
for (const entry of newEntries) {
state.unlockedEntries.add(entry.id);
}
// 外敌进度跨轮继承(保留30%
for (const key of Object.keys(state.enemyProgress)) {
state.enemyProgress[key] = Math.min(100, state.enemyProgress[key] * 0.3 + 5);
}
// 触发死亡结算界面
if (typeof window !== 'undefined' && window.onDeath) {
window.onDeath({
reason,
age: state.age,
day: state.day,
careers: Object.fromEntries(
Object.entries(state.careers).map(([k, v]) => [k, { level: v.level, name: CAREERS[k]?.name }])
),
metaGains,
newEntries,
history: getHistoryComparison(),
});
}
}
// 历史对比
function getHistoryComparison() {
if (state.history.length === 0) return null;
const lastLife = state.history[state.history.length - 1];
const bestLife = state.history.reduce((best, current) =>
(current.age > best.age) ? current : best
, state.history[0]);
return {
vsLast: {
ageDiff: state.age - lastLife.age,
levelDiff: compareLevels(state.careers, lastLife.careers),
},
vsBest: {
ageDiff: state.age - bestLife.age,
levelDiff: compareLevels(state.careers, bestLife.careers),
},
};
}
function compareLevels(current, previous) {
const result = {};
const allIds = new Set([...Object.keys(current), ...Object.keys(previous)]);
for (const id of allIds) {
const curr = current[id]?.level || 0;
const prev = previous[id]?.level || 0;
result[id] = curr - prev;
}
return result;
}
// 重生
export function rebirth() {
resetStateForRebirth();
// 随机出身
state.family = Math.floor(Math.random() * 4) - 1; // -1 ~ 2
state.stats = {
body: Math.floor(Math.random() * 5) + 2, // 2~6,避免刚出生就死
wisdom: Math.floor(Math.random() * 5) + 2,
charm: Math.floor(Math.random() * 5) + 2,
luck: Math.floor(Math.random() * 5) + 2,
};
// 应用记忆效果
if (state.memories.includes('wisdom_boost')) state.stats.wisdom += 1;
if (state.memories.includes('body_boost')) state.stats.body += 1;
// 从已解锁词条池中随机抽取
const pool = Array.from(state.unlockedEntries);
state.talents = [];
const count = Math.min(3 + Math.floor(state.reincarnation / 10), 5);
for (let i = 0; i < count && pool.length > 0; i++) {
const idx = Math.floor(Math.random() * pool.length);
state.talents.push({ id: pool[idx] });
pool.splice(idx, 1);
}
addLog(`${state.reincarnation}世开始。出身: ${state.family > 0 ? '富贵' : state.family < 0 ? '贫寒' : '普通'}人家。`);
// 触发重生事件
if (typeof window !== 'undefined' && window.onRebirth) {
window.onRebirth();
}
}