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:
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user