feat # 继续补全遗物系统

This commit is contained in:
2026-10-08 23:46:57 +08:00
parent 623f53a1d2
commit 1c93c77f4f
335 changed files with 11528 additions and 1797 deletions
+62 -88
View File
@@ -1,99 +1,73 @@
const entries = [
{ id: "mengsan_burning_blood_shuying", name: "燃烧之血",
description: "战斗结束后回复6点生命,不能超过生命上限。",
effect: "battleHeal", amount: 6 },
{ id: "mengsan_snake_ring_shuying", name: "蛇之戒指",
description: "每场战斗开始时,额外摸2张牌。",
effect: "openingDraw", amount: 2 },
{ id: "mengsan_long_snake_ring_shuying", name: "长蛇戒指",
description: "每场战斗的前3个自身回合开始时,额外摸2张牌。",
effect: "earlyTurnDraw", amount: 2, turns: 3 },
{ id: "mengsan_millennium_puzzle_shuying", name: "千年积木",
description: "每场战斗首次实际损失生命且仍存活时,摸3张牌。",
effect: "firstHpLossDraw", amount: 3 },
{ id: "mengsan_pendulum_shuying", name: "摆动球",
description: "每场战斗每到第3个自身回合开始时,额外摸1张牌。",
effect: "periodicTurnDraw", amount: 1, turns: 3 },
{ id: "mengsan_hand_charm_shuying", name: "束带",
description: "基础手牌上限额外+1;重复获得不叠加。",
effect: "handLimit", amount: 1 },
{ id: "mengsan_meal_ticket_shuying", name: "餐券",
description: "进入商店房间时回复15点生命。",
effect: "shopHeal", amount: 15 },
{ id: "mengsan_strawberry_shuying", name: "草莓",
description: "获得时最大生命值提升7;不额外回复生命。",
effect: "gainMaxHp", amount: 7 },
{ id: "mengsan_bag_of_marbles_shuying", name: "弹珠袋",
description: "每场战斗开始时给予所有敌人1层易伤。",
effect: "openingVulnerable", amount: 1 },
{ id: "mengsan_lantern_shuying", name: "灯笼",
description: "每场战斗首个自身回合额外获得1点临时费用。",
effect: "firstTurnEnergy", amount: 1 },
{ id: "mengsan_red_mask_shuying", name: "红面具",
description: "每场战斗开始时给予所有敌人1层虚弱。",
effect: "openingWeak", amount: 1 },
{ id: "mengsan_regal_pillow_shuying", name: "皇家枕头",
description: "选择休息回血时额外回复15点生命。",
effect: "restHeal", amount: 15 },
{ id: "mengsan_festive_poppers_shuying", name: "节日拉炮",
description: "每场战斗开始时对所有敌人造成9点伤害。",
effect: "openingDamage", amount: 9 },
{ id: "mengsan_vajra_shuying", name: "金刚杵",
description: "每场战斗开始时获得1点力量。",
effect: "openingStrength", amount: 1 },
];
import { relicCatalog } from "./catalog.js";
import { relicRules, missingMechanisms, unhandledRelicRule } from "./rules.js";
import { applyRelicPickup, relicCardRequirements } from "./progression.js";
export const relicDefinitions = Object.freeze(Object.fromEntries(
entries.map(entry => [entry.id, Object.freeze({ ...entry,
owner: null, image: null })])));
export const getRelic = id => Object.hasOwn(relicDefinitions, id)
? relicDefinitions[id] : null;
export const heldRelics = run => [...new Set(run?.player?.items || [])]
.map(getRelic).filter(Boolean);
export function canAcquireRelic(run, id) {
export const relicAdaptations = Object.freeze({
the_courier:"梦三商店出售【杀】、回复生命及三件随机遗物;购买后补货,折扣适用现有商品。药水尚无接口。遗物基价120金币,为梦三配置值。",
miniature_tent:"每个休息选项可各选择一次,直至选择离开;不允许无限重复同一选项。",
meat_cleaver:"网页没有烹饪细则;暂定每次烹饪最大生命+5,待用户确认。",
big_mushroom:"沿用梦三初始手牌4张,扣减2张;未改成STS2的基础手牌数量。",
ring_of_the_drake:"刘备仅映射铁甲战士池;其他角色池需配置player.relicPool才可获取。效果按前三个自身回合各摸2张接入。",
runic_pyramid:"梦三原本按手牌上限弃牌;这里让全部手牌不计入弃牌上限。虚无牌照常消耗。",
precarious_shears:"选牌后扣除16点生命;当前地图流程保留至少1点生命,待补充非战斗死亡结算。",
fragrant_mushroom:"随机强化2张并扣除15点生命;当前地图流程保留至少1点生命,待补充非战斗死亡结算。",
dusty_tome:"从已登记的先古事件牌池随机获得一张;网页未列出完整候选排除规则。",
sword_of_stone:"按5次精英战胜利计数,变化为玉之剑;尚无精英单位的独立击杀标签。",
paper_phrog:"依梦三易伤口径向上取整;作用于玩家发起的攻击,保留既有卡牌的额外易伤增幅。",
paper_krane:"依梦三虚弱口径向下取整,敌人攻击倍率0.6。",
maw_bank:"将进入一个新的梦三节点视为攀爬一层,先发12金币,再进行商店购买或战斗;原作楼层与额外剧情节点未单独区分。",
});
const entries = relicCatalog.map(item => {
const rule = relicRules[item.wikiId];
const missingCards = rule ? relicCardRequirements(rule) : [];
const unhandled = rule ? unhandledRelicRule(rule) : [];
const reason = missingMechanisms[item.wikiId] || (missingCards.length ? `尚缺网页对应卡牌:${missingCards.join("、")}` : !rule ? "尚未接入此效果" : unhandled.length ? `尚缺效果接口:${unhandled.join("、")}` : null);
return Object.freeze({ ...item, owner: null, rule: rule || Object.freeze({}),
implementation: reason ? "pending" : relicAdaptations[item.wikiId] ? "adapted" : "implemented",
implementationNote: reason || relicAdaptations[item.wikiId] || "效果已接入,实机验收待完成。", effect: "sts2" });
});
entries.push(Object.freeze({ id: "mengsan_hand_charm_shuying", wikiId: "legacy_hand_charm",
name: "束带", description: "基础手牌上限额外+1;重复获得不叠加。", pool: "通用", tier: "梦三道具",
ancient: null, owner: null, image: null, effect: "handLimit", amount: 1,
rule: Object.freeze({ handLimit: 1 }), implementation: "implemented", implementationNote: "保留原梦三道具。" }));
export const relicDefinitions = Object.freeze(Object.fromEntries(entries.map(entry => [entry.id, entry])));
const aliases = Object.freeze(Object.fromEntries(entries.flatMap(entry => [[entry.id, entry.id], [entry.wikiId, entry.id], [`mengsan_${entry.wikiId}_shuying`, entry.id]])));
export const getRelic = id => Object.hasOwn(aliases, id) ? relicDefinitions[aliases[id]] : null;
export const heldRelics = run => [...new Set((run?.player?.items || []).map(id => getRelic(id)?.id).filter(Boolean))].map(getRelic);
export const relicPoolFor = run => run?.player?.relicPool || (run?.player?.character === "mengsan_liubei_shuying" ? "铁甲战士" : null);
export function canAcquireRelic(run, id, { allowPending = false } = {}) {
const relic = getRelic(id);
return Boolean(run?.player && relic &&
(relic.owner === null || relic.owner === run.player.character) &&
!run.player.items?.includes(id));
if (!run?.player || !relic || !allowPending && relic.implementation === "pending") return false;
if (relic.pool !== "通用" && relic.pool !== relicPoolFor(run)) return false;
return !heldRelics(run).some(item => item.id === relic.id || item.rule.replaces === relic.wikiId);
}
export function grantRelic(run, id) {
if (!getRelic(id)) throw new Error(`梦三遗物未注册:${id}`);
if (!canAcquireRelic(run, id)) return false;
const relic = getRelic(id);
run.player.items ||= [];
run.player.items.push(id);
if (relic.effect === "handLimit") {
run.player.handLimitBonus = (run.player.handLimitBonus || 0) +
relic.amount;
}
if (relic.effect === "gainMaxHp") {
if (Number.isFinite(run.player.maxHp)) {
run.player.maxHp += relic.amount;
} else {
run.player.pendingRelicMaxHp =
(run.player.pendingRelicMaxHp || 0) + relic.amount;
}
}
if (!relic) throw new Error(`梦三遗物未注册:${id}`);
if (!canAcquireRelic(run, id)) return false;
const draft = JSON.parse(JSON.stringify(run));
draft.player.items ||= [];
if (relic.rule.replaces) draft.player.items = draft.player.items.filter(value => getRelic(value)?.wikiId !== relic.rule.replaces);
draft.player.items.push(relic.id);
applyRelicPickup(draft, relic, { entries, canAcquire: canAcquireRelic, grant: grantRelic });
Object.assign(run, draft);
return true;
}
export function initializeRelicMaxHp(run) {
const amount = run.player.pendingRelicMaxHp || 0;
if (!amount || !Number.isFinite(run.player.maxHp)) return;
run.player.maxHp += amount;
if (!Number.isFinite(run.player.maxHp)) return;
run.player.maxHp = Math.max(1, run.player.maxHp + amount);
delete run.player.pendingRelicMaxHp;
run.player.hp = Math.min(run.player.hp, run.player.maxHp);
if (run.player.pendingRelicFullHeal) { run.player.hp = run.player.maxHp; delete run.player.pendingRelicFullHeal; }
}
export const relicRewardIds = entries.filter(entry =>
entry.effect !== "handLimit").map(entry => `shared.reward.relic.${entry.id}`);
export const relicRewards = Object.fromEntries(relicRewardIds.map(id => {
const relicId = id.slice("shared.reward.relic.".length);
const relic = getRelic(relicId);
return [id, { relic: relicId, name: `遗物·${relic.name}`,
description: relic.description }];
}));
export const relicRewardIds = entries.filter(entry => entry.implementation !== "pending" && ["普通", "罕见", "稀有"].includes(entry.tier)).map(entry => `shared.reward.relic.${entry.id}`);
export const relicShopRewardIds = entries.filter(entry => entry.implementation !== "pending" && ["普通", "罕见", "稀有", "商店"].includes(entry.tier)).map(entry => `shared.reward.relic.${entry.id}`);
export const relicRewards = Object.fromEntries(entries.map(relic => [`shared.reward.relic.${relic.id}`, { relic: relic.id, name: `遗物·${relic.name}`, description: relic.description, image: relic.image }]));
const ancientKeys={"涅奥":"neow","欧洛巴斯":"orobas","佩尔":"pael","特兹卡塔拉":"tezcatara","诺奴佩普":"nonupep","坦克斯":"tanx","瓦库":"vakuu","达弗":"darv"};
export const relicRewardPools={
"shared.pool.relic.shop":[...relicShopRewardIds],
"shared.pool.relic.event":entries.filter(r=>r.implementation!=="pending" && r.tier==="事件").map(r=>`shared.reward.relic.${r.id}`),
"shared.pool.relic.initial":entries.filter(r=>r.implementation!=="pending" && r.tier==="初始").map(r=>`shared.reward.relic.${r.id}`),
...Object.fromEntries(Object.entries(ancientKeys).map(([ancient,key])=>[`shared.pool.relic.ancient.${key}`,entries.filter(r=>r.implementation!=="pending" && r.ancient===ancient).map(r=>`shared.reward.relic.${r.id}`)])),
};