Files
noname-shuying/tools/test_mengsan_cards.mjs
T
Diuse 82d80397b6 fixed # 新增羁绊系统
fixed # 新增遗物系统
fixed # 梦三模式下移除护甲上限限制
2026-09-30 14:33:17 +08:00

231 lines
10 KiB
JavaScript

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { test } from "node:test";
import { canAcquireCard } from "../mengsan/cards/card-definitions.js";
import { createCardData } from "../mengsan/cards/card-data.js";
import { rewards, rewardPools } from "../mengsan/content/shared/rewards.js";
import * as upgrades from "../mengsan/cards/upgrades.js";
import { canAcquireReward } from "../mengsan/progression/reward.js";
const load = source => import(`data:text/javascript;base64,${
Buffer.from(source).toString("base64")}`);
const rulesSource = await readFile(new URL(
"../mengsan/battle/combat-rules.js", import.meta.url), "utf8");
const rules = await load(rulesSource.replace(
/^import .*;\r?\n/gm, "const _status = {};\n"));
const cardsSource = await readFile(new URL(
"../mengsan/cards/mode-cards.js", import.meta.url), "utf8");
const nativeCard = {
sha: { type: "basic", enable: true, range: () => true,
filterTarget: (card, player, target) => player !== target,
content: async event => { event.observedDamage = event.baseDamage; } },
tao: {}, taoyuan: {}, nanman: {}, wanjian: {}, juedou: {},
huogong: {}, shandian: {},
};
for (const name of ["nanman", "wanjian", "juedou"]) {
nativeCard[name].content = nativeCard.sha.content;
}
globalThis.mengsanCardTest = { rules, nativeCard, upgrades, logs: [] };
const { createMengsanCards } = await load(`
const { rules, nativeCard, upgrades, logs } = globalThis.mengsanCardTest;
const { cardUpgradeLevel, cardUpgradeRule } = upgrades;
const { cardCost, canPayCard, isActiveCardUse, SHA_DAMAGE,
TRICK_DAMAGE } = rules;
const _status = {};
const game = { log: (...args) => logs.push(args) };
const standard = { card: nativeCard, translate: {},
list: Object.keys(nativeCard).map(name => [null, null, name]) };
const extra = { card: {}, translate: {}, list: [] };
${cardsSource.replace(/^import[\s\S]*?;\r?\n/gm, "")}
`);
const { card, translate, names } = createMengsanCards();
globalThis.mengsanCardTest.card = card;
test("sha damage changes without changing trick damage", async () => {
const event = {};
await card.sha.content(event);
assert.equal(event.observedDamage, 6);
assert.equal(card.sha.baseDamage, 6);
assert.match(translate.sha_info, /6点伤害/);
for (const name of ["nanman", "wanjian", "juedou"]) {
const trickEvent = {};
await card[name].content(trickEvent);
assert.equal(trickEvent.observedDamage, 4);
}
assert.equal(card.huogong.baseDamage, 4);
});
test("heavy strike costs two energy and cannot target its user", () => {
const used = { name: "mengsan_zhongsha" };
const player = { storage: { mengsanEnergy_shuying: 1 } };
const event = { name: "phaseUse", player };
assert.equal(rules.cardCost(used), 2);
assert.equal(card.mengsan_zhongsha.enable(used, player, event), false);
player.storage.mengsanEnergy_shuying = 2;
assert.equal(card.mengsan_zhongsha.enable(used, player, event), true);
assert.equal(rules.payCard(player, used), true);
assert.equal(player.storage.mengsanEnergy_shuying, 0);
assert.equal(card.mengsan_zhongsha.filterTarget(used, player, player), false);
});
test("damage resolves before vulnerability; stacks add and dead targets skip", async () => {
for (const [alive, stacks] of [[true, 0], [true, 3], [false, 3]]) {
let settled = false;
const source = {};
const target = {
storage: { mengsanVulnerable_shuying: stacks },
damage(num, player) {
assert.equal(num, 8);
assert.equal(player, source);
assert.equal(this.storage.mengsanVulnerable_shuying, stacks);
const result = Promise.resolve().then(() => {
assert.equal(result.mengsanAttack_shuying, true);
settled = true;
});
return result;
},
isAlive: () => alive,
addSkill(skill) {
assert.equal(settled, true);
assert.equal(skill, "mengsan_vulnerable_shuying");
},
markSkill() {},
};
await card.mengsan_zhongsha.content({ target }, null, source);
assert.equal(target.storage.mengsanVulnerable_shuying,
alive ? stacks + 2 : stacks);
}
});
test("card library and rewards use the extension artwork", async () => {
for (const [file, helper] of [["card-library", "preview"],
["reward-ui", "viewCard"]]) {
const source = await readFile(new URL(
`../mengsan/ui/${file}.js`, import.meta.url), "utf8");
const module = await load(`
const lib = { assetURL: "/game/", translate: {},
card: { mengsan_zhongsha: {
image: "${card.mengsan_zhongsha.image}"
} } };
const get = { translation: name => name };
lib.card.sha = globalThis.mengsanCardTest.card.sha;
lib.card.mengsan_zhongsha.cardPrompt =
globalThis.mengsanCardTest.card.mengsan_zhongsha.cardPrompt;
const ui = {}, AFFIX_INFO = {}, cardCost = () => 2;
const { cardUpgradeLevel, cardUpgradeRule } =
globalThis.mengsanCardTest.upgrades;
${source.replace(/^import .*;\r?\n/gm, "")}
export { ${helper} };
`);
const expected = "/game/extension/术樱包/mengsan/assets/cards/" +
"mengsan_zhongsha.png";
const previous = globalThis.document;
globalThis.document = { createElement: () => ({
children: [], appendChild(child) { this.children.push(child); },
addEventListener() {},
get content() { return { textContent: this.innerHTML || "" }; },
}) };
try {
if (helper === "viewCard") {
assert.equal(module.viewCard({ card: {
name: "mengsan_zhongsha" } }).image, expected);
continue;
}
const face = module.preview({ name: "mengsan_zhongsha" },
{ name: "重杀", suit: "黑桃", number: 7,
affixes: [] }, null);
const art = face.children[1];
assert.equal(art.children[1].src, expected);
const details = module.describeLibraryCard({
name: "mengsan_zhongsha", upgrade: 1 });
assert.match(details.description, /10点伤害.*3层易伤/);
assert.equal(details.upgrade, 1);
assert.equal(details.upgradeLimit, 1);
} finally {
globalThis.document = previous;
}
}
});
test("heavy strike is registered, serializable and obtainable in rewards", () => {
assert.equal(canAcquireCard("mengsan_liubei_shuying", "mengsan_zhongsha"), true);
const data = createCardData({ id: "heavy", name: "mengsan_zhongsha",
suit: "spade", number: 7 }, { character: "mengsan_liubei_shuying" });
assert.equal(data.name, "mengsan_zhongsha");
assert.ok(names.includes(data.name));
assert.ok(rewardPools["shared.pool.battle.normal"].includes(
"shared.reward.card.zhongsha"));
assert.equal(rewards["shared.reward.card.zhongsha"].card.name, data.name);
});
test("only configured cards can upgrade once, excluding eternal cards", () => {
const deck = [
{ name: "sha", upgrade: 0 },
{ name: "mengsan_zhongsha", upgrade: 0 },
{ name: "tao", upgrade: 0 },
{ name: "sha", upgrade: 1 },
{ name: "sha", upgrade: 0, affixes: ["eternal"] },
];
assert.deepEqual(deck.map(upgrades.canUpgradeCard),
[true, true, false, false, false]);
assert.equal(upgrades.upgradeRandomCard(deck, () => 0.9), deck[1]);
assert.equal(deck[1].upgrade, 1);
assert.equal(upgrades.upgradeRandomCard(deck, () => 0), deck[0]);
assert.equal(deck[0].upgrade, 1);
assert.equal(upgrades.hasUpgradeableCard(deck), false);
assert.equal(upgrades.upgradeRandomCard(deck,
() => { throw new Error("empty candidates must not consume RNG"); }), null);
assert.equal(canAcquireReward({ player: { deck } },
{ effectId: "upgrade" }), false);
deck.push({ name: "sha", upgrade: 0 });
assert.equal(canAcquireReward({ player: { deck } },
{ effectId: "upgrade" }), true);
});
test("new card data rejects upgrades on unsupported or full-level cards", () => {
const spec = { id: "upgrade", suit: "spade", number: 7,
name: "sha", upgrade: 1 };
assert.equal(createCardData(spec).upgrade, 1);
assert.throws(() => createCardData({ ...spec, upgrade: 2 }));
assert.throws(() => createCardData({ ...spec, name: "tao" }));
assert.throws(() => createCardData({ ...spec, upgrade: -1 }));
assert.equal(upgrades.cardUpgradeLevel({ name: "sha", upgrade: 5 }), 1);
assert.equal(upgrades.cardUpgradeLevel({ name: "tao", upgrade: 5 }), 0);
});
test("upgraded sha applies nine damage through serialized and runtime data", async () => {
const stored = { name: "sha", upgrade: 1 };
for (const used of [stored,
{ name: "sha", storage: { mengsanCard_shuying: stored } }]) {
const event = { card: used, baseDamage: 6 };
await card.sha.content(event);
assert.equal(event.observedDamage, 9);
assert.match(card.sha.cardPrompt(used), /9点伤害/);
}
const changed = { card: { name: "sha", storage: {
mengsanCard_shuying: { name: "tao", upgrade: 1 } } }, baseDamage: 6 };
await card.sha.content(changed);
assert.equal(changed.observedDamage, 6);
const modified = { card: stored, baseDamage: 7 };
await card.sha.content(modified);
assert.equal(modified.observedDamage, 10);
});
test("upgraded heavy strike deals ten before adding three vulnerability", async () => {
const used = { name: "mengsan_zhongsha", storage: {
mengsanCard_shuying: { name: "mengsan_zhongsha", upgrade: 1 } } };
const target = {
storage: { mengsanVulnerable_shuying: 2 },
damage(num) {
assert.equal(num, 10);
assert.equal(this.storage.mengsanVulnerable_shuying, 2);
return Promise.resolve();
},
isAlive: () => true, addSkill() {}, markSkill() {},
};
await card.mengsan_zhongsha.content({ card: used, target }, null, {});
assert.equal(target.storage.mengsanVulnerable_shuying, 5);
assert.match(card.mengsan_zhongsha.cardPrompt(used), /10点伤害.*3层易伤/);
assert.equal(rules.cardCost(used), 2);
});