feat: add career engine with unlock, income, exp, level-up

This commit is contained in:
2026-05-13 03:35:02 +00:00
parent c78e4bba0b
commit a063f51b20
3 changed files with 406 additions and 0 deletions
+111
View File
@@ -0,0 +1,111 @@
{
"careers": [
{
"id": "student",
"name": "学童",
"type": "scholar",
"unlockConditions": {
"type": "age",
"op": ">=",
"value": 6
},
"dailyIncome": 1,
"expCurve": {
"base": 10,
"factor": 1.15
}
},
{
"id": "book_boy",
"name": "书童",
"type": "scholar",
"unlockConditions": {
"type": "careerLevel",
"op": ">=",
"careerId": "student",
"value": 100
},
"dailyIncome": 2,
"expCurve": {
"base": 50,
"factor": 1.2
}
},
{
"id": "scholar",
"name": "书生",
"type": "scholar",
"unlockConditions": {
"type": "and",
"conditions": [
{
"type": "careerLevel",
"op": ">=",
"careerId": "student",
"value": 250
},
{
"type": "age",
"op": ">=",
"value": 14
}
]
},
"dailyIncome": -3,
"expCurve": {
"base": 100,
"factor": 1.25
}
},
{
"id": "soldier",
"name": "士兵",
"type": "military",
"unlockConditions": {
"type": "and",
"conditions": [
{
"type": "age",
"op": ">=",
"value": 14
},
{
"type": "stat",
"op": ">=",
"stat": "body",
"value": 10
}
]
},
"dailyIncome": 2,
"expCurve": {
"base": 20,
"factor": 1.18
}
},
{
"id": "taoist_priest",
"name": "道士",
"type": "immortal",
"unlockConditions": {
"type": "and",
"conditions": [
{
"type": "age",
"op": ">=",
"value": 10
},
{
"type": "hasItem",
"itemId": "taoist_license"
}
]
},
"dailyIncome": 1,
"expCurve": {
"base": 30,
"factor": 1.22
}
}
]
}
+74
View File
@@ -0,0 +1,74 @@
import { evaluateCondition } from './conditionEvaluator.js';
const STAT_MAP = {
scholar: 'wisdom',
military: 'body',
jianghu: 'charm',
political: 'intelligence',
immortal: 'wisdom',
business: 'business',
};
let _config = null;
export function setCareerConfig(config) {
_config = config;
}
export function getCareerConfig() {
return _config;
}
export function checkUnlocks(config, state) {
if (!state.careers) {
state.careers = {};
}
for (const career of config.careers) {
if (!state.careers[career.id]) {
if (evaluateCondition(career.unlockConditions, state)) {
state.careers[career.id] = { level: 0, exp: 0 };
}
}
}
}
export function calcDailyIncome(config, state) {
let total = 0;
for (const career of config.careers) {
if (state.careers?.[career.id]) {
total += career.dailyIncome;
}
}
const businessStat = state.stats?.business ?? 0;
const bonus = 1 + Math.min(businessStat, 100) * 0.01;
return total * bonus;
}
export function calcDailyExp(config, state, careerId) {
const careerCfg = config.careers.find(c => c.id === careerId);
if (!careerCfg) return 0;
const base = careerCfg.expCurve.base;
const statKey = STAT_MAP[careerCfg.type];
const stat = state.stats?.[statKey] ?? 0;
const statBonus = 1 + Math.min(stat, 100) * 0.01;
const meta = state.metaExp?.[careerId] || 0;
const metaBonus = 1 + Math.log(1 + meta / 100);
return base * statBonus * metaBonus;
}
export function checkLevelUp(config, state) {
if (!state.careers) return;
for (const career of config.careers) {
const careerState = state.careers[career.id];
if (!careerState) continue;
const { base, factor } = career.expCurve;
let need = base * Math.pow(factor, careerState.level);
while (careerState.exp >= need) {
careerState.exp -= need;
careerState.level++;
need = base * Math.pow(factor, careerState.level);
}
}
}
export { STAT_MAP };