fixed # 更新刘备配置和奖励

This commit is contained in:
2026-09-30 15:43:07 +08:00
parent 82d80397b6
commit 0ccb14cecd
22 changed files with 681 additions and 30 deletions
+8
View File
@@ -12,6 +12,14 @@
| 千年积木 | `mengsan_millennium_puzzle_shuying` | `millennium_puzzle.png` | 待提供 |
| 摆动球 | `mengsan_pendulum_shuying` | `pendulum.png` | 待提供 |
| 束带(既有道具) | `mengsan_hand_charm_shuying` | `hand_charm.png` | 待提供 |
| 餐券 | `mengsan_meal_ticket_shuying` | `meal_ticket.png` | 待提供 |
| 草莓 | `mengsan_strawberry_shuying` | `strawberry.png` | 待提供 |
| 弹珠袋 | `mengsan_bag_of_marbles_shuying` | `bag_of_marbles.png` | 待提供 |
| 灯笼 | `mengsan_lantern_shuying` | `lantern.png` | 待提供 |
| 红面具 | `mengsan_red_mask_shuying` | `red_mask.png` | 待提供 |
| 皇家枕头 | `mengsan_regal_pillow_shuying` | `regal_pillow.png` | 待提供 |
| 节日拉炮 | `mengsan_festive_poppers_shuying` | `festive_poppers.png` | 待提供 |
| 金刚杵 | `mengsan_vajra_shuying` | `vajra.png` | 待提供 |
建议使用统一尺寸的透明背景 PNG,实际尺寸和视觉风格在资源制作时确定。
图片仅负责外观,不承载名称、效果或触发计数。
+18 -2
View File
@@ -1,7 +1,7 @@
# 梦三遗物(道具)
本目录负责遗物定义、持有规则与战斗效果管理。
已实现五件新遗物,并接入既有“束带”。图片暂缺,清单见 [ASSETS.md](ASSETS.md)。
已实现十三件新遗物,并接入既有“束带”。图片暂缺,清单见 [ASSETS.md](ASSETS.md)。
## 已实现的遗物
@@ -13,6 +13,22 @@
| 千年积木 | 每场战斗首次实际生命减少且仍存活时摸 3 张;纯护甲损失不触发 |
| 摆动球 | 每场战斗第 3、6、9……个自身回合开始额外摸 1 张 |
| 束带 | 原有手牌上限 +1,重复获得不叠加 |
| 餐券 | 进入商店时回复 15 点生命,每个商店节点一次 |
| 草莓 | 获得时生命上限 +7,当前生命不变,重复获得不叠加 |
| 弹珠袋 | 开战时给予当时所有存活敌人 1 层易伤 |
| 灯笼 | 首个自身回合可用费用 +1,费用上限不变,下回合恢复正常 |
| 红面具 | 开战时给予当时所有存活敌人 1 层虚弱 |
| 皇家枕头 | 选择休息回血时额外恢复 15;现有基础 8,总计 23 |
| 节日拉炮 | 开战时对当时所有存活敌人造成 9 点伤害 |
| 金刚杵 | 开战时获得 1 点力量,只在当前战斗有效 |
虚弱使攻击伤害减少 25%(向下取整),自身回合结束减少 1 层;
力量使玩家攻击牌伤害增加对应点数,意图攻击沿用原有力量/虚弱计算。
节日拉炮是非卡牌伤害,正常消耗目标护甲,不享受力量、易伤、虚弱的攻击修正。
开场效果先摸牌和施加状态,再结算节日拉炮;仅影响开战时的敌人,不影响后续援军。
餐券在打开商店前保存回血和节点记录,读档不会重复触发;
皇家枕头不影响磨砺、商店治疗或其他回复效果。所有回复均不超过生命上限。
草莓若在角色生命尚未初始化时获得,会保存待应用加成,初始化后只应用一次。
遗物互相独立:同时持有两种戒指时,开场和前三回合效果分别触发。
所有回合计数按玩家自身实际开始的回合计算,不计敌方和支援回合,
@@ -21,7 +37,7 @@
### 获得、保存和查看
- 五件新遗物加入共享 Boss 奖励池及第一章宝箱奖励池;
- 新遗物通过自动生成的遗物奖励列表加入共享 Boss 和第一章宝箱奖励池;
后续章节使用共享 Boss 奖励池的宝箱也可出现它们。
- 角色默认不携带新遗物;未新增商店价格或精英掉落规则。
- 持有记录沿用 `run.player.items`,已持有遗物会从随机奖励中排除。
+19 -2
View File
@@ -1,6 +1,6 @@
import { heldRelics } from "./definitions.js";
export function createRelicBattle(run, { active, draw, log }) {
export function createRelicBattle(run, { active, draw, log, effect }) {
const relics = heldRelics(run);
let started = false, turns = 0, firstLoss = false;
let openingHandBonus = 0, pendingHpDraw = null;
@@ -24,6 +24,15 @@ export function createRelicBattle(run, { active, draw, log }) {
for (const relic of relics) {
if (relic.effect === "openingDraw") await drawEffect(relic);
}
for (const relic of relics.filter(item =>
["openingVulnerable", "openingWeak", "openingStrength",
"openingDamage"].includes(item.effect))
.sort((a, b) => Number(a.effect === "openingDamage") -
Number(b.effect === "openingDamage"))) {
if (!usable()) break;
log(relic, relic.description);
await effect(relic);
}
if (pendingHpDraw) {
const relic = pendingHpDraw;
pendingHpDraw = null;
@@ -35,6 +44,11 @@ export function createRelicBattle(run, { active, draw, log }) {
seenTurns.add(event);
turns++;
for (const relic of relics) {
if (relic.effect === "firstTurnEnergy" && turns === 1 &&
usable()) {
log(relic, `获得${relic.amount}点临时费用`);
await effect(relic);
}
if ((relic.effect === "earlyTurnDraw" &&
turns <= relic.turns) ||
(relic.effect === "periodicTurnDraw" &&
@@ -63,9 +77,12 @@ export function createRelicBattle(run, { active, draw, log }) {
if (relic.effect === "periodicTurnDraw") {
return `回合计数:${turns % relic.turns}/${relic.turns}`;
}
if (relic.effect === "openingDraw") {
if (relic.effect.startsWith("opening")) {
return started ? "本场已触发" : "等待战斗开始";
}
if (relic.effect === "firstTurnEnergy") {
return turns ? "本场已触发" : "等待首个自身回合";
}
return "持续持有";
},
};
+78
View File
@@ -0,0 +1,78 @@
export function createOpeningEffects(game, owner, refreshEnergy) {
const enemies = () => game.players.filter(player =>
player !== owner && player.isAlive() &&
player.storage?.mengsanCamp_shuying === "enemy");
const addStatus = (player, key, skill, amount) => {
player.storage[key] = (player.storage[key] || 0) + amount;
player.addSkill(skill);
player.markSkill(skill);
};
return async relic => {
if (relic.effect === "firstTurnEnergy") {
owner.storage.mengsanEnergy_shuying += relic.amount;
refreshEnergy();
} else if (relic.effect === "openingStrength") {
addStatus(owner, "mengsanStrength_shuying",
"mengsan_raider_strength_shuying", relic.amount);
} else {
for (const target of enemies()) {
if (!owner.isAlive()) break;
if (!target.isAlive()) continue;
if (relic.effect === "openingVulnerable") {
addStatus(target, "mengsanVulnerable_shuying",
"mengsan_vulnerable_shuying", relic.amount);
} else if (relic.effect === "openingWeak") {
addStatus(target, "mengsanWeak_shuying",
"mengsan_weak_shuying", relic.amount);
} else if (relic.effect === "openingDamage") {
const damage = target.damage(relic.amount, owner, "nocard");
damage.mengsanScriptedSkill_shuying = true;
await damage;
}
}
}
};
}
export function createRelicCombatSkills(getBattle, getOwner) {
return {
mengsan_relic_attack_shuying: {
trigger: { global: "damageBegin1" },
forced: true, silent: true, popup: false, priority: 80,
filter(event) {
const source = event.source;
return Boolean(getBattle()?.session.active && event.card &&
!event.mengsanScriptedSkill_shuying &&
((source === getOwner() &&
source?.storage?.mengsanStrength_shuying > 0) ||
source?.storage?.mengsanWeak_shuying > 0));
},
async content(event, trigger) {
const source = trigger.source;
if (source === getOwner()) {
trigger.num += source.storage.mengsanStrength_shuying || 0;
}
if (source.storage.mengsanWeak_shuying > 0) {
trigger.num = Math.floor(trigger.num * 0.75);
}
},
},
mengsan_weak_shuying: {
mark: true, marktext: "弱",
onremove(player) { delete player.storage.mengsanWeak_shuying; },
intro: { content(storage, player) {
return `剩余${player.storage.mengsanWeak_shuying || 0}回合:` +
"攻击伤害减少25%(向下取整)";
} },
trigger: { player: "phaseAfter" },
forced: true, silent: true, popup: false,
async content(event, trigger, player) {
player.storage.mengsanWeak_shuying = Math.max(0,
(player.storage.mengsanWeak_shuying || 0) - 1);
if (player.storage.mengsanWeak_shuying) {
player.markSkill("mengsan_weak_shuying");
} else player.removeSkill("mengsan_weak_shuying");
},
},
};
}
+40 -1
View File
@@ -17,6 +17,30 @@ const entries = [
{ 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 },
];
export const relicDefinitions = Object.freeze(Object.fromEntries(
@@ -39,16 +63,31 @@ export function canAcquireRelic(run, id) {
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);
const relic = getRelic(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;
}
}
return true;
}
export function initializeRelicMaxHp(run) {
const amount = run.player.pendingRelicMaxHp || 0;
if (!amount || !Number.isFinite(run.player.maxHp)) return;
run.player.maxHp += amount;
delete run.player.pendingRelicMaxHp;
}
export const relicRewardIds = entries.filter(entry =>
entry.effect !== "handLimit").map(entry => `shared.reward.relic.${entry.id}`);
+22
View File
@@ -0,0 +1,22 @@
import { heldRelics } from "./definitions.js";
export const restRecovery = run => 8 + heldRelics(run)
.filter(relic => relic.effect === "restHeal")
.reduce((sum, relic) => sum + relic.amount, 0);
export function applyRoomRelics(run, node, action) {
const resting = node.type === "rest" && action === "heal";
if (node.type !== "shop" && !resting) return null;
const receipt = `${node.type}:${node.id}`;
const receipts = run.player.relicRoomReceipts || [];
if (receipts.includes(receipt)) return null;
const effect = resting ? "restHeal" : "shopHeal";
const relics = heldRelics(run).filter(relic => relic.effect === effect);
const amount = (resting ? 8 : 0) + relics.reduce((sum, relic) =>
sum + relic.amount, 0);
if (!amount) return null;
const before = run.player.hp;
run.player.hp = Math.min(run.player.maxHp, before + amount);
run.player.relicRoomReceipts = [...receipts, receipt];
return { recovered: run.player.hp - before, relics };
}