feat: add shop engine with 3-tab support and artifact upgrades

This commit is contained in:
2026-05-13 03:39:51 +00:00
parent cbed651937
commit 86bd7ba376
3 changed files with 484 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
{
"items": [
{
"id": "taoist_license",
"name": "道碟",
"tab": "item",
"cost": 500,
"unlockConditions": [
{ "type": "age", "op": ">=", "value": 10 },
{ "type": "or", "conditions": [
{ "type": "identity", "value": "taoist_orphan" },
{ "type": "worldFlag", "flag": "met_taoist_priest", "value": true }
]}
],
"effects": [
{ "type": "unlockCareer", "careerId": "taoist_priest" },
{ "type": "setFlag", "flag": "has_taoist_license", "value": true }
],
"resetOnRebirth": true
},
{
"id": "power_pill",
"name": "大力丸",
"tab": "item",
"cost": 50,
"unlockConditions": [],
"effects": [
{ "type": "addStat", "stat": "body", "value": 5 }
],
"resetOnRebirth": true
}
],
"buffs": [
{
"id": "clever_brush",
"name": "妙笔生花",
"tab": "buff",
"cost": 200,
"unlockConditions": [],
"effects": [
{ "type": "addStat", "stat": "wisdom", "value": 10 }
],
"resetOnRebirth": true
}
],
"artifacts": [
{
"id": "immortal_sword",
"name": "仙剑",
"tab": "artifact",
"baseCost": 1000,
"costGrowth": 1.5,
"unlockConditions": [
{ "type": "careerLevel", "careerId": "taoist_priest", "op": ">=", "value": 50 }
],
"effects": [
{ "type": "addStat", "stat": "body", "value": 10 }
],
"resetOnRebirth": false,
"maxLevel": 10
}
]
}
+135
View File
@@ -0,0 +1,135 @@
import { evaluateCondition } from './conditionEvaluator.js';
import { applyEffect } from './effectApplier.js';
import { canAfford, spendMoney } from './moneySystem.js';
let _shopConfig = null;
export function setShopConfig(config) {
_shopConfig = config;
}
export function getShopConfig() {
return _shopConfig;
}
export function findEntry(config, id) {
if (!config || !id) return null;
const sections = ['items', 'buffs', 'artifacts'];
for (const section of sections) {
const list = config[section];
if (!Array.isArray(list)) continue;
const entry = list.find(e => e.id === id);
if (entry) {
return { ...entry, section };
}
}
return null;
}
export function getArtifactUpgradeCost(config, artifactId, currentLevel) {
if (!config || !config.artifacts || !artifactId) return null;
const entry = config.artifacts.find(a => a.id === artifactId);
if (!entry) return null;
return Math.floor(entry.baseCost * Math.pow(entry.costGrowth, currentLevel));
}
export function canBuyItem(config, state, itemId) {
const entry = findEntry(config, itemId);
if (!entry) return false;
// Check unlock conditions
if (Array.isArray(entry.unlockConditions) && entry.unlockConditions.length > 0) {
const allMet = entry.unlockConditions.every(cond => evaluateCondition(cond, state));
if (!allMet) return false;
}
// Check max level for artifacts
if (entry.section === 'artifacts') {
const currentLevel = state.artifacts?.[itemId] || 0;
if (entry.maxLevel !== undefined && currentLevel >= entry.maxLevel) {
return false;
}
}
// Check cost
let cost;
if (entry.section === 'artifacts') {
const currentLevel = state.artifacts?.[itemId] || 0;
cost = getArtifactUpgradeCost(config, itemId, currentLevel);
} else {
cost = entry.cost;
}
if (cost === null || cost === undefined) return false;
return canAfford(state, cost);
}
export function buyItem(config, state, itemId) {
if (!canBuyItem(config, state, itemId)) {
return false;
}
const entry = findEntry(config, itemId);
if (!entry) return false;
// Calculate cost
let cost;
if (entry.section === 'artifacts') {
const currentLevel = state.artifacts?.[itemId] || 0;
cost = getArtifactUpgradeCost(config, itemId, currentLevel);
} else {
cost = entry.cost;
}
// Spend money
if (!spendMoney(state, cost)) {
return false;
}
// Apply effects
if (Array.isArray(entry.effects)) {
for (const effect of entry.effects) {
applyEffect(effect, state);
}
}
// Record purchase
if (entry.section === 'items') {
if (!state.shopItems) state.shopItems = {};
state.shopItems[itemId] = (state.shopItems[itemId] || 0) + 1;
} else if (entry.section === 'buffs') {
if (!state.activeBuffs) state.activeBuffs = {};
state.activeBuffs[itemId] = { expiresDay: -1 };
} else if (entry.section === 'artifacts') {
if (!state.artifacts) state.artifacts = {};
state.artifacts[itemId] = (state.artifacts[itemId] || 0) + 1;
}
return true;
}
export function resetShopItems(state) {
state.shopItems = {};
state.activeBuffs = {};
// Artifacts are preserved
}
export function applyArtifactEffects(config, state) {
if (!config || !state.artifacts) return;
for (const [artifactId, level] of Object.entries(state.artifacts)) {
if (level <= 0) continue;
const entry = config.artifacts?.find(a => a.id === artifactId);
if (!entry || !Array.isArray(entry.effects)) continue;
for (const effect of entry.effects) {
const scaledEffect = {
...effect,
value: effect.value * level
};
applyEffect(scaledEffect, state);
}
}
}