mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-21 14:49:59 +08:00
fixed # 重构梦三项目结构
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
// Shared mode-owned affix registry; adding a new entry also exposes it in GM.
|
||||
export const AFFIX_INFO = Object.freeze({
|
||||
void: Object.freeze({ name: "虚无", description: "弃牌阶段开始时移除此牌,退出此次战斗;即使同时具有保留也会移除。" }),
|
||||
retain: Object.freeze({ name: "保留", description: "弃牌阶段开始时,此牌不计入手牌上限;若同时具有虚无,仍会被移除。" }),
|
||||
unplayable: Object.freeze({ name: "不能被打出", description: "此牌不能使用或打出,但可以被弃置。" }),
|
||||
ingenious: Object.freeze({ name: "奇巧", description: "不因弃牌阶段进入弃牌堆时,若可以使用,则使用此牌。" }),
|
||||
eternal: Object.freeze({ name: "永恒", description: "此牌不能从你的牌组移除或变化。" }),
|
||||
innate: Object.freeze({ name: "固有", description: "战斗开始时移至手牌;若已在手牌中则不移动。" }),
|
||||
exhaust: Object.freeze({ name: "消耗", description: "使用或打出后移出此次战斗,并可在消耗牌堆查看;消耗牌堆不参与洗牌。" }),
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { cardDefinitions, canAcquireCard } from "./card-definitions.js";
|
||||
|
||||
const suits = ["spade", "heart", "club", "diamond"];
|
||||
|
||||
// 仅创建可序列化的牌组数据;战斗中的实体牌仍由 game.createCard 创建。
|
||||
export function createCardData(spec, { character, enemy = false, registry = cardDefinitions } = {}) {
|
||||
if (!spec || typeof spec !== "object" || !Object.hasOwn(registry, spec.name)) {
|
||||
throw new Error(`梦三牌未注册:${spec?.name}`);
|
||||
}
|
||||
if (!enemy && !canAcquireCard(character, spec.name, registry)) {
|
||||
throw new Error(`武将 ${character || "未知"} 无法获得牌:${spec.name}`);
|
||||
}
|
||||
if (typeof spec.id !== "string" || !spec.id || !suits.includes(spec.suit) ||
|
||||
!Number.isInteger(spec.number) || spec.number < 1 || spec.number > 13 ||
|
||||
(spec.nature != null && typeof spec.nature !== "string") ||
|
||||
!Number.isInteger(spec.upgrade ?? 0) || (spec.upgrade ?? 0) < 0 ||
|
||||
!Array.isArray(spec.affixes ?? []) || !(spec.affixes ?? []).every(value => typeof value === "string")) {
|
||||
throw new Error(`梦三牌数据无效:${spec.name}`);
|
||||
}
|
||||
return {
|
||||
id: spec.id, suit: spec.suit, number: spec.number, name: spec.name,
|
||||
nature: spec.nature ?? null, affixes: (spec.affixes ?? []).slice(), upgrade: spec.upgrade ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function createRandomCardData(run, name, { random, now = Date.now, upgrade = 0, affixes = [] } = {}) {
|
||||
if (!run?.player || !Array.isArray(run.player.deck) || typeof random !== "function") throw new Error("梦三随机牌参数无效");
|
||||
// 先检查资格,再消耗随机数;失败的获得操作不改变后续奖励序列。
|
||||
if (!canAcquireCard(run.player.character, name)) throw new Error(`武将 ${run.player.character} 无法获得牌:${name}`);
|
||||
const stamp = now();
|
||||
let id;
|
||||
do { id = `mengsan_card_${stamp}_${Math.floor(random() * 1e6)}`; }
|
||||
while (run.player.deck.some(card => card.id === id));
|
||||
return createCardData({
|
||||
id, suit: suits[Math.floor(random() * suits.length)], number: 1 + Math.floor(random() * 13),
|
||||
name, nature: null, upgrade, affixes,
|
||||
}, { character: run.player.character });
|
||||
}
|
||||
|
||||
export function addCardToDeck(run, spec) {
|
||||
const card = createCardData(spec, { character: run?.player?.character });
|
||||
if (!Array.isArray(run?.player?.deck)) throw new Error("梦三牌组无效");
|
||||
if (run.player.deck.some(existing => existing.id === card.id)) throw new Error(`梦三牌 ID 重复:${card.id}`);
|
||||
run.player.deck.push(card);
|
||||
return card;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// 梦三永久牌组的可获得牌注册表。这里登记牌名与归属,不复制引擎中的效果定义。
|
||||
// 新增武将专属牌时填写 owner 为该武将 ID;通用牌填写 owner: null。
|
||||
const definitions = [
|
||||
{ name: "sha", category: "damage", owner: null },
|
||||
{ name: "shan", category: "utility", owner: null },
|
||||
{ name: "tao", category: "recovery", owner: null },
|
||||
{ name: "jiu", category: "recovery", owner: null },
|
||||
{ name: "guohe", category: "utility", owner: null },
|
||||
{ name: "wuzhong", category: "utility", owner: null },
|
||||
{ name: "juedou", category: "damage", owner: null },
|
||||
{ name: "bagua", category: "utility", owner: null },
|
||||
// 当前仅刘备专属首关的固定奖励会永久获得【的卢】。
|
||||
{ name: "dilu", category: "utility", owner: "mengsan_liubei_shuying" },
|
||||
];
|
||||
|
||||
export function createCardRegistry(entries) {
|
||||
const registry = Object.create(null);
|
||||
for (const entry of entries) {
|
||||
if (!entry || typeof entry.name !== "string" || !/^[a-z][a-z0-9_]*$/.test(entry.name) ||
|
||||
!["damage", "recovery", "utility"].includes(entry.category) ||
|
||||
!(entry.owner === null || typeof entry.owner === "string" && entry.owner.length > 0) ||
|
||||
Object.hasOwn(registry, entry.name)) {
|
||||
throw new Error(`梦三牌定义无效或重复:${entry?.name}`);
|
||||
}
|
||||
registry[entry.name] = Object.freeze({ name: entry.name, category: entry.category, owner: entry.owner });
|
||||
}
|
||||
return Object.freeze(registry);
|
||||
}
|
||||
|
||||
export const cardDefinitions = createCardRegistry(definitions);
|
||||
|
||||
export function canAcquireCard(character, name, registry = cardDefinitions) {
|
||||
const definition = Object.hasOwn(registry, name) ? registry[name] : null;
|
||||
return !!definition && (definition.owner === null || definition.owner === character);
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// 梦三的模式牌定义:复制标准/军争的实体牌定义,不改动原牌包。
|
||||
// 保留原牌名,以兼容原有牌的响应、装备技能及转化牌引用。
|
||||
import standard from "../../../../card/standard.js";
|
||||
import extra from "../../../../card/extra.js";
|
||||
import { _status } from "../../../../noname.js";
|
||||
import { cardCost, canPayCard, isActiveCardUse, SHA_DAMAGE } from "../battle/combat-rules.js";
|
||||
|
||||
const packs = [standard, extra];
|
||||
const names = [...new Set(packs.flatMap(pack => pack.list.map(entry => entry[2])))];
|
||||
const sourceCard = name => standard.card[name] || extra.card[name];
|
||||
const sourceText = name => extra.translate[`${name}_info`] || standard.translate[`${name}_info`];
|
||||
|
||||
export function createMengsanCards() {
|
||||
const card = {};
|
||||
const translate = {};
|
||||
for (const name of names) {
|
||||
const source = sourceCard(name);
|
||||
if (!source) throw new Error(`梦三牌定义缺失:${name}`);
|
||||
const copy = { ...source, mengsanCost_shuying: cardCost({ name }) };
|
||||
// 卡牌本身校验费用;响应和濒死救援不属于主动出牌。
|
||||
if (source.enable || source.type === "equip" || source.type === "delay") {
|
||||
copy.enable = function (used, player, event) {
|
||||
if (isActiveCardUse(event || _status.event, player) && !canPayCard(player, used)) return false;
|
||||
return typeof source.enable === "function" ? source.enable.call(this, used, player, event) : source.enable ?? true;
|
||||
};
|
||||
}
|
||||
const prompt = source.cardPrompt;
|
||||
const costLabel = name === "shan" || name === "wuxie"
|
||||
? "响应不消耗费用" : `主动使用消耗${copy.mengsanCost_shuying}费用`;
|
||||
copy.cardPrompt = function (used) {
|
||||
const detail = typeof prompt === "function" ? prompt.call(this, used) : prompt;
|
||||
return `梦三:${costLabel}。${detail || sourceText(name) || ""}`;
|
||||
};
|
||||
const nativeText = sourceText(name);
|
||||
if (nativeText) translate[`${name}_info`] = `梦三:${costLabel}。${nativeText}`;
|
||||
card[name] = copy;
|
||||
}
|
||||
// 【杀】的基础伤害由牌内容决定,而不是事后追加伤害的角色技能。
|
||||
const nativeSha = sourceCard("sha");
|
||||
card.sha.baseDamage = SHA_DAMAGE;
|
||||
card.sha.content = async function (event, trigger, player) {
|
||||
if (typeof event.baseDamage !== "number") event.baseDamage = SHA_DAMAGE;
|
||||
return nativeSha.content.call(this, event, trigger, player);
|
||||
};
|
||||
card.sha.usable = Infinity;
|
||||
card.sha.cardPrompt = () => `梦三:主动使用消耗1费用;目标须使用【闪】,否则受到${SHA_DAMAGE}点伤害。`;
|
||||
translate.sha_info = `梦三:主动使用消耗1费用。出牌阶段,对攻击范围内的一名角色使用;其须使用【闪】,否则受到${SHA_DAMAGE}点伤害。每回合使用次数不限。`;
|
||||
// 【桃】的回复量也由梦三牌本体提供;救援仍不消耗费用。
|
||||
card.tao.content = async function (event) { await event.target.recover(8); };
|
||||
translate.tao_info = "梦三:主动使用消耗1费用,回复8点生命;濒死救援不消耗费用,同样回复8点生命。";
|
||||
card.taoyuan.content = async function (event) { await event.target.recover(8); };
|
||||
translate.taoyuan_info = "梦三:主动使用消耗2费用。所有角色各回复8点生命。";
|
||||
card.taoyuan.cardPrompt = () => translate.taoyuan_info;
|
||||
for (const name of ["nanman", "wanjian", "juedou"]) {
|
||||
const nativeContent = sourceCard(name).content;
|
||||
card[name].baseDamage = SHA_DAMAGE;
|
||||
card[name].content = async function (event, trigger, player) {
|
||||
if (typeof event.baseDamage !== "number") event.baseDamage = SHA_DAMAGE;
|
||||
return nativeContent.call(this, event, trigger, player);
|
||||
};
|
||||
const detail = {
|
||||
nanman: "所有其他角色须打出【杀】,否则受到4点伤害。",
|
||||
wanjian: "所有其他角色须打出【闪】,否则受到4点伤害。",
|
||||
juedou: "目标与你轮流打出【杀】;未能打出的一方受到4点伤害。",
|
||||
}[name];
|
||||
translate[`${name}_info`] = `梦三:主动使用消耗${cardCost({ name })}费用。${detail}`;
|
||||
card[name].cardPrompt = () => translate[`${name}_info`];
|
||||
}
|
||||
// 火攻通过当前牌事件的 baseDamage 传入 damage("fire")。
|
||||
card.huogong.baseDamage = SHA_DAMAGE;
|
||||
translate.huogong_info = "梦三:主动使用消耗1费用。令一名有手牌的角色展示一张手牌;若你弃置同花色牌,对其造成4点火焰伤害。";
|
||||
card.huogong.cardPrompt = () => translate.huogong_info;
|
||||
// 闪电属于延时锦囊,其命中伤害在 effect 中显式写死为3,需要独立覆写。
|
||||
card.shandian.effect = async function (event, trigger, player, result) {
|
||||
if (result.bool === false) await player.damage(12, "thunder", "nosource");
|
||||
else player.addJudgeNext(event.card);
|
||||
};
|
||||
translate.shandian_info = "梦三:主动使用消耗1费用。若判定为黑桃2~9,目标受到12点雷电伤害;否则移至下家的判定区。";
|
||||
card.shandian.cardPrompt = () => translate.shandian_info;
|
||||
return { card, translate, names };
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// Mode-owned piles use real Card nodes so native searches/gain/insert operations agree.
|
||||
// DIY must access ui piles inside the acting player's engine event, never cache global nodes.
|
||||
import { cardCost } from "../battle/combat-rules.js";
|
||||
import { AFFIX_INFO } from "./affixes.js";
|
||||
|
||||
|
||||
export function installPersonalPiles(session, owner, battle, run, resources, env) {
|
||||
const { game, ui, get, _status, document, MutationObserver, shuffle } = env;
|
||||
for (const key of ["cardPile", "discardPile"]) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(ui, key);
|
||||
if (!descriptor || !descriptor.configurable || (!Object.hasOwn(descriptor, "value") && typeof descriptor.get !== "function")) throw new Error("Unsupported pile property: " + key);
|
||||
}
|
||||
const draw = document.createElement("div"), discard = document.createElement("div");
|
||||
// Detached nodes: engine get.position recognises these IDs; no duplicate document IDs.
|
||||
draw.id = "cardPile"; discard.id = "discardPile";
|
||||
const owned = new Set(), restores = [], costBadges = new Map();
|
||||
const strict = env.strict !== false;
|
||||
let released = false, forcedOwner = false;
|
||||
const personal = () => {
|
||||
if (released) return false;
|
||||
if (forcedOwner) return true;
|
||||
const seen = new Set();
|
||||
for (let event = _status.event; event && !seen.has(event); event = event.parent) {
|
||||
seen.add(event);
|
||||
if (event.player) return event.player === owner;
|
||||
}
|
||||
return false; // Never infer ownership from whose turn it is.
|
||||
};
|
||||
const refresh = () => { if (!released) env.refresh(); };
|
||||
function showCost(card) {
|
||||
if (!env.showCosts || !card?.name) return;
|
||||
const cost = String(cardCost(card));
|
||||
card.dataset.mengsanCost = cost;
|
||||
let badge = costBadges.get(card);
|
||||
if (!badge) {
|
||||
badge = Array.from(card.children || []).find(child => child.classList?.contains("mengsan-hand-cost-shuying"));
|
||||
if (!badge) {
|
||||
badge = document.createElement("span");
|
||||
badge.className = "mengsan-hand-cost-shuying";
|
||||
badge.setAttribute("aria-hidden", "true");
|
||||
card.appendChild(badge);
|
||||
}
|
||||
costBadges.set(card, badge);
|
||||
}
|
||||
// 十周年 UI scans thrown-card children via innerText when adding “刘备使用”.
|
||||
// Keep this child text-free so its numeric cost cannot match that scan.
|
||||
badge.textContent = "";
|
||||
badge.dataset.mengsanCost = cost;
|
||||
}
|
||||
function adopt(card, data) {
|
||||
if (owned.has(card)) return;
|
||||
owned.add(card); resources.card(card);
|
||||
card.storage ||= {};
|
||||
card.storage.mengsanOwnerId_shuying = owner.playerid;
|
||||
if (data) {
|
||||
card.storage.mengsanCard_shuying = JSON.parse(JSON.stringify(data));
|
||||
// Retired affixes from old saves must not acquire new battle behaviour.
|
||||
card.storage.mengsanCard_shuying.affixes = (card.storage.mengsanCard_shuying.affixes || []).filter(key => key !== "annihilate");
|
||||
}
|
||||
if (data) showCost(card);
|
||||
// Existing foreign/generated cards retain their original destruction semantics.
|
||||
if (!data) return;
|
||||
card.destroyLog = false;
|
||||
card.destroyed = (current, position) => {
|
||||
if (released || !["discardPile", "equip", "judge"].includes(position)) return false;
|
||||
if (current.storage.mengsanCard_shuying.affixes.includes("exhaust") && current.storage.mengsanConsumed_shuying) {
|
||||
if (current.storage.mengsanExhausted_shuying) return true;
|
||||
current.storage.mengsanExhausted_shuying = true;
|
||||
battle.exhaustPile.push(snapshot(current));
|
||||
refresh();
|
||||
return true;
|
||||
}
|
||||
return false; // Actual discarded cards remain available to native skills.
|
||||
};
|
||||
for (const key of card.storage.mengsanCard_shuying.affixes) card.addGaintag(AFFIX_INFO[key]?.name || key);
|
||||
if (data.upgrade) card.addGaintag(`强化+${data.upgrade}`);
|
||||
}
|
||||
function snapshot(card) {
|
||||
const data = card.storage?.mengsanCard_shuying || {};
|
||||
return { ...data, name: card.name, suit: card.suit, number: card.number,
|
||||
nature: card.nature || null, affixes: data.affixes?.slice() || [], upgrade: data.upgrade || 0 };
|
||||
}
|
||||
for (const data of battle.drawPile) {
|
||||
const card = game.createCard(data.name, data.suit, data.number, data.nature);
|
||||
adopt(card, data); draw.appendChild(card);
|
||||
}
|
||||
// Native and 十周年 UI both move real card nodes into these hand containers.
|
||||
// Decorate newly gained cards without patching either UI's card factory or layout.
|
||||
const handZones = [owner.node?.handcards1, owner.node?.handcards2].filter(Boolean);
|
||||
const handObserver = new MutationObserver(mutations => {
|
||||
if (released) return;
|
||||
for (const mutation of mutations) for (const card of mutation.addedNodes) {
|
||||
if (card.classList?.contains("card")) showCost(card);
|
||||
}
|
||||
});
|
||||
for (const zone of handZones) {
|
||||
for (const card of zone.childNodes) if (card.classList?.contains("card")) showCost(card);
|
||||
handObserver.observe(zone, { childList: true });
|
||||
}
|
||||
session.ownResource(handObserver, () => {
|
||||
handObserver.disconnect();
|
||||
for (const [card, badge] of costBadges) {
|
||||
if (badge.parentNode === card) badge.remove();
|
||||
delete card.dataset.mengsanCost;
|
||||
}
|
||||
costBadges.clear();
|
||||
});
|
||||
const publicDiscard = ui.discardPile;
|
||||
for (const [key, node] of [["cardPile", draw], ["discardPile", discard]]) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(ui, key);
|
||||
let publicNode = descriptor.value;
|
||||
const read = () => personal() ? node : (descriptor.get ? descriptor.get.call(ui) : publicNode);
|
||||
Object.defineProperty(ui, key, { configurable: true, enumerable: descriptor.enumerable,
|
||||
get: read, set(value) { if (descriptor.set) descriptor.set.call(ui, value); else publicNode = value; } });
|
||||
restores.push(() => {
|
||||
if (Object.getOwnPropertyDescriptor(ui, key)?.get === read) Object.defineProperty(ui, key, descriptor.get ? descriptor : { ...descriptor, value: publicNode });
|
||||
});
|
||||
}
|
||||
for (const [key, node] of [["drawPile", draw], ["discardPile", discard]]) {
|
||||
Object.defineProperty(battle, key, { configurable: true, enumerable: true,
|
||||
get: () => Array.from(node.childNodes, snapshot) });
|
||||
}
|
||||
const observer = new MutationObserver(() => {
|
||||
if (released) return;
|
||||
// A card from our deck can be discarded during an enemy-owned event.
|
||||
for (const card of Array.from(publicDiscard.childNodes)) if (owned.has(card)) discard.appendChild(card);
|
||||
for (const node of [draw, discard]) for (const card of node.childNodes) adopt(card);
|
||||
refresh();
|
||||
});
|
||||
for (const node of [draw, discard, publicDiscard]) observer.observe(node, { childList: true });
|
||||
function wash() {
|
||||
const cards = [...draw.childNodes, ...discard.childNodes];
|
||||
if (!cards.length) return false;
|
||||
shuffle(run, cards);
|
||||
for (const card of cards) { card.fix?.(); draw.appendChild(card); }
|
||||
refresh();
|
||||
return true;
|
||||
}
|
||||
function take(number = 1, putBack = false, bottom = false) {
|
||||
if (!Number.isFinite(number) || number <= 0) return [];
|
||||
number = Math.ceil(number);
|
||||
if (draw.childNodes.length + discard.childNodes.length < number) {
|
||||
if (strict) { game.mengsanFailRun_shuying("个人牌堆与弃牌堆不足,挑战失败"); return []; }
|
||||
number = draw.childNodes.length + discard.childNodes.length;
|
||||
}
|
||||
const cards = [];
|
||||
while (cards.length < number) {
|
||||
if (!draw.childNodes.length) wash();
|
||||
const card = draw.removeChild(bottom ? draw.lastChild : draw.firstChild); card.original = "c"; cards.push(card);
|
||||
}
|
||||
if (putBack) for (let i = cards.length - 1; i >= 0; i--) {
|
||||
if (bottom) draw.appendChild(cards[i]); else draw.insertBefore(cards[i], draw.firstChild);
|
||||
}
|
||||
refresh(); return cards;
|
||||
}
|
||||
function wrap(object, key, replacement) {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(object, key), original = object[key];
|
||||
const wrapped = function (...args) { return personal() ? replacement(...args) : original.apply(this, args); };
|
||||
object[key] = wrapped;
|
||||
restores.push(() => { if (object[key] === wrapped) { if (descriptor) Object.defineProperty(object, key, descriptor); else delete object[key]; } });
|
||||
}
|
||||
wrap(get, "cards", (number, putBack) => take(typeof number === "number" ? number : 1, putBack));
|
||||
wrap(get, "bottomCards", (number, putBack) => take(typeof number === "number" ? number : 1, putBack, true));
|
||||
wrap(game, "washCard", wash);
|
||||
session.ownResource(draw, () => {
|
||||
if (released) return;
|
||||
observer.disconnect(); released = true;
|
||||
for (const restore of restores.reverse()) restore();
|
||||
for (const card of owned) card.remove();
|
||||
owned.clear(); draw.remove(); discard.remove();
|
||||
});
|
||||
return {
|
||||
take,
|
||||
addAffix(card, key) {
|
||||
if (released || !Object.hasOwn(AFFIX_INFO, key) || !owned.has(card) || !owner.getCards("h").includes(card)) return false;
|
||||
const affixes = card.storage?.mengsanCard_shuying?.affixes;
|
||||
if (!Array.isArray(affixes) || affixes.includes(key)) return false;
|
||||
affixes.push(key);
|
||||
card.addGaintag(AFFIX_INFO[key].name);
|
||||
refresh();
|
||||
return true;
|
||||
},
|
||||
// Engine/mod opening draws must not stack with the mode's four-card opening hand.
|
||||
resetOpeningHand() {
|
||||
for (const card of owner.getCards("h")) {
|
||||
if (owned.has(card)) draw.insertBefore(card, draw.firstChild);
|
||||
else card.remove();
|
||||
}
|
||||
refresh();
|
||||
},
|
||||
trimOpeningHand(limit = 4) {
|
||||
for (const card of owner.getCards("h").filter(card => !card.storage?.mengsanCard_shuying?.affixes?.includes("innate")).slice(limit)) {
|
||||
if (owned.has(card)) draw.insertBefore(card, draw.firstChild);
|
||||
else card.remove();
|
||||
}
|
||||
refresh();
|
||||
},
|
||||
drawInnate() {
|
||||
const cards = Array.from(draw.childNodes).filter(card => card.storage?.mengsanCard_shuying?.affixes?.includes("innate") && !owner.getCards("h").includes(card));
|
||||
if (cards.length) owner.directgain(cards);
|
||||
refresh();
|
||||
return cards;
|
||||
},
|
||||
withOwner(callback) { const before = forcedOwner; forcedOwner = true; try { return callback(); } finally { forcedOwner = before; } },
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createCardData } from "./card-data.js";
|
||||
|
||||
// Mode-owned starting decks. Existing saves always retain their actual player.deck.
|
||||
export const defaultDeck = Object.freeze([
|
||||
["spade", 7, "sha"], ["heart", 10, "sha"],
|
||||
["club", 4, "sha"], ["diamond", 6, "sha"],
|
||||
["heart", 2, "shan"], ["diamond", 7, "shan"], ["club", 2, "shan"],
|
||||
["heart", 3, "tao"], ["spade", 3, "guohe"], ["heart", 7, "wuzhong"],
|
||||
["spade", 2, "bagua"], ["club", 6, "sha"],
|
||||
].map(Object.freeze));
|
||||
|
||||
// Character ID -> [suit, number, card name, optional nature, optional affixes].
|
||||
// Example: edit these twelve entries to change only this private Liu Bei's new-run deck.
|
||||
export const characterDecks = {
|
||||
mengsan_liubei_shuying: [
|
||||
["spade", 7, "sha"], ["heart", 10, "sha"], ["club", 4, "sha"], ["diamond", 6, "sha"],
|
||||
["heart", 2, "shan"], ["diamond", 7, "shan"], ["club", 2, "shan"],
|
||||
["heart", 3, "tao"], ["heart", 7, "wuzhong"], ["spade", 1, "juedou"],
|
||||
["spade", 2, "bagua"], ["club", 6, "sha"],
|
||||
],
|
||||
};
|
||||
|
||||
export function getStartingDeck(character) {
|
||||
const override = Object.hasOwn(characterDecks, character) ? characterDecks[character] : null;
|
||||
return Array.isArray(override) && override.length ? override : defaultDeck;
|
||||
}
|
||||
|
||||
export function createStartingDeck(character, prefix = `mengsan_card_${Date.now()}`) {
|
||||
return getStartingDeck(character).map((entry, index) => createCardData({
|
||||
id: `${prefix}_${index}`, suit: entry[0], number: entry[1], name: entry[2],
|
||||
nature: entry[3] || null, affixes: entry[4] ? entry[4].slice() : [], upgrade: 0,
|
||||
}, { character }));
|
||||
}
|
||||
Reference in New Issue
Block a user