mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-22 15:19:59 +08:00
整合
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import liubeiOpening, { openingRewards } from "./stories/liubei-opening.js";
|
||||
import actMap from "./map.js";
|
||||
import { rewardPools } from "./rewards.js";
|
||||
import forestAmbush from "./stories/forest-ambush.js";
|
||||
|
||||
export default {
|
||||
map: actMap,
|
||||
nodeContents: {
|
||||
[forestAmbush.id]: forestAmbush,
|
||||
[liubeiOpening.id]: liubeiOpening,
|
||||
},
|
||||
rewardPools,
|
||||
rewards: openingRewards,
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
const act1Map = {
|
||||
id: "act1",
|
||||
name: "第一关·初梦",
|
||||
floorNodes: [1, 4, 4, 4, 1],
|
||||
minFloorNodes: [1, 3, 3, 3, 1],
|
||||
nodeWeights: { battle: 65, event: 15, rest: 10, shop: 10 },
|
||||
fixedNodes: [
|
||||
{
|
||||
floor: 0,
|
||||
position: "start",
|
||||
type: "battle",
|
||||
contentId: "act1.battle.liubeiOpening",
|
||||
},
|
||||
],
|
||||
enemies: ["re_xiahoudun", "re_zhangliao", "re_xuzhu"],
|
||||
boss: "re_lvbu",
|
||||
baseGold: 20,
|
||||
};
|
||||
|
||||
export default act1Map;
|
||||
@@ -0,0 +1,16 @@
|
||||
export const rewardPools = {
|
||||
"act1.pool.story.scoutGift": [
|
||||
"shared.reward.support.scout",
|
||||
"shared.reward.card.sha",
|
||||
"shared.reward.card.tao",
|
||||
"shared.reward.card.upgrade",
|
||||
"shared.reward.item.handCharm",
|
||||
],
|
||||
"act1.pool.story.ambushLoot": [
|
||||
"shared.reward.card.sha",
|
||||
"shared.reward.card.tao",
|
||||
"shared.reward.card.upgrade",
|
||||
"shared.reward.heal",
|
||||
"shared.reward.item.handCharm",
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,112 @@
|
||||
const forestAmbush = {
|
||||
id: "act1.story.forestAmbush",
|
||||
kind: "dialogue",
|
||||
name: "林中伏兵",
|
||||
mapName: "林中伏兵",
|
||||
description: "你在密林中遇到一名负伤的斥候。他声称前方有伏兵,并请求你决定下一步行动。",
|
||||
dialogue: [
|
||||
{
|
||||
type: "narrator",
|
||||
text: "暮色压进密林,归巢的鸟雀忽然一齐噤声。",
|
||||
},
|
||||
{
|
||||
type: "sound",
|
||||
speaker: "林间异响",
|
||||
text: "沙沙……咔嚓!",
|
||||
speed: 45,
|
||||
},
|
||||
{
|
||||
type: "character",
|
||||
character: "re_zhangliao",
|
||||
speaker: "负伤斥候",
|
||||
text: "且慢!前方埋伏着一队精兵。我侥幸逃出,已无力再探。",
|
||||
},
|
||||
{
|
||||
type: "character",
|
||||
character: "$player",
|
||||
speaker: "$player",
|
||||
text: "先把你知道的情形说清楚,我再决定如何行动。",
|
||||
},
|
||||
],
|
||||
choices: [
|
||||
{
|
||||
id: "listen_and_leave",
|
||||
name: "听完警告后离开",
|
||||
description: "结束对话,不发生战斗,也不获得奖励。",
|
||||
outcome: {
|
||||
result: "你记下了斥候的警告,绕开了危险区域。",
|
||||
dialogue: [
|
||||
{ type: "narrator", text: "你记下警告,沿着一条隐蔽的小径绕开了危险区域。" },
|
||||
],
|
||||
flags: {
|
||||
"story.act1.heardAmbushWarning": true,
|
||||
"story.act1.ambushResult": "left",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "help_scout",
|
||||
name: "帮助斥候处理伤势",
|
||||
description: "不进入战斗,从随机出现的三个谢礼中选择一个。",
|
||||
outcome: {
|
||||
result: "斥候恢复了行动能力,并拿出随身物资作为谢礼。",
|
||||
dialogue: [
|
||||
{
|
||||
type: "character",
|
||||
character: "re_zhangliao",
|
||||
speaker: "负伤斥候",
|
||||
text: "多谢相助。这些随身物资,请务必收下。",
|
||||
},
|
||||
],
|
||||
rewardPool: "act1.pool.story.scoutGift",
|
||||
rewardTitle: "斥候的谢礼(三选一)",
|
||||
flags: {
|
||||
"story.act1.helpedScout": true,
|
||||
"story.act1.ambushResult": "helped",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "hunt_ambushers",
|
||||
name: "主动追击伏兵",
|
||||
description: "迎战两名伏兵,第 3 轮还有敌军增援;胜利后获得金币并选择战利品。",
|
||||
outcome: {
|
||||
result: "你循着林间留下的痕迹追了上去。",
|
||||
dialogue: [
|
||||
{ type: "character", character: "$player", speaker: "$player", text: "与其绕路,不如趁他们尚未察觉,先发制人。" },
|
||||
{ type: "sound", text: "刀剑出鞘之声划破寂静。" },
|
||||
],
|
||||
battle: {
|
||||
enemies: ["re_xiahoudun", "re_zhangliao", "re_xuzhu"],
|
||||
gold: 30,
|
||||
// Two initial enemies; a third-round reinforcement demonstrates the rule system.
|
||||
battlePlan: {
|
||||
units: [
|
||||
{ id: "captain", character: "re_xiahoudun", camp: "enemy", hand: 4 },
|
||||
{ id: "guard", character: "re_xuzhu", camp: "enemy", hand: 2 },
|
||||
],
|
||||
rules: [
|
||||
{ id: "enemy_reinforcement", name: "伏兵增援", blocksVictory: true, when: { event: "roundStart", round: 3 },
|
||||
effects: [{ type: "spawn", unit: { id: "reinforcement", character: "re_zhangliao", camp: "enemy", hand: 3 } }] },
|
||||
{ id: "captain_rally", name: "困兽犹斗", when: { event: "state", all: [
|
||||
{ unit: "captain", field: "alive", value: true }, { unit: "captain", field: "hp", op: "lte", value: 2 },
|
||||
] }, effects: [{ type: "draw", target: "captain", amount: 2 }] },
|
||||
{ id: "player_supply", name: "临阵补给", when: { event: "state", all: [
|
||||
{ unit: "player", field: "hand", op: "lte", value: 1 },
|
||||
] }, effects: [{ type: "draw", target: "player", amount: 2 }] },
|
||||
],
|
||||
},
|
||||
rewardPool: "act1.pool.story.ambushLoot",
|
||||
rewardTitle: "伏兵战利品(三选一)",
|
||||
description: "击败伏兵后,从随机出现的三个奖励中选择一个。",
|
||||
},
|
||||
flags: {
|
||||
"story.act1.huntedAmbushers": true,
|
||||
"story.act1.ambushResult": "fought",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default forestAmbush;
|
||||
@@ -0,0 +1,72 @@
|
||||
export const openingDialogue = [
|
||||
{ type: "narrator", text: "你是21世纪的一名普通牛马。" },
|
||||
{ type: "narrator", text: "某天你晚上9点照常下班,刚出公司大门,就撞大运了。" },
|
||||
{ type: "narrator", text: "等你睁开眼发现自己竟已身在三国,成为桃园结义后的刘备了。" },
|
||||
{ type: "character", character: "$player", text: "(内心)我叫刘备。眼前这两位,便是与我结为兄弟的关羽和张飞。此刻,三弟正与二弟谈论黄巾之乱。" },
|
||||
{ type: "character", character: "zhangfei", text: "大哥!如今黄巾作乱,官府正在招募义兵。俺们既已结为兄弟,何不召集乡勇,起兵讨伐黄巾贼?" },
|
||||
{ type: "character", character: "guanyu", text: "三弟所言有理。只是起兵讨贼,须备足钱粮、兵器,方能成事。此事如何安排,还请大哥定夺。" },
|
||||
{
|
||||
type: "choice", text: "还好我历史满分!但是我不想干活啊。", flag: "story.act1.liubeiOpening.reply", defaultChoice: "gather", choices: [
|
||||
{ id: "gather", text: "眼下先召集乡亲,整备人手,再从长计议。待我上山巡视一番,说不定有一张SSR呢。" },
|
||||
]
|
||||
},
|
||||
{ type: "character", character: "zhangfei", text: "SSR为何物?" },
|
||||
{ type: "narrator", text: "你不等张飞吐槽,转身拂袖而去。" },
|
||||
{ type: "narrator", text: "乡间小道之上,忽听前方有人高声喝道:“黄巾军押运辎重,闲人速速避让,莫要挡路!”" },
|
||||
{ type: "narrator", text: "只见一人一马疾驰而来。" },
|
||||
{ type: "character", character: "$player", text: "(内心)这就开始刷怪了?白送的物资来了。" },
|
||||
{ type: "narrator", text: "只见你伸腿一拦,只见对方勒马拔剑,进入战斗。" },
|
||||
|
||||
];
|
||||
export const reinforcementDialogue = [
|
||||
{ type: "character", character: "$player", text: "(内心)我靠,对面有马我没马啊。" },
|
||||
{ type: "character", character: "mengsan_sushuang_zhangshiping_shuying", text: "将军莫急,我等前来助战。" },
|
||||
];
|
||||
export const victoryDialogue = [
|
||||
{ type: "character", character: "$player", text: "方才多谢二位出手相助,尚未请教二位尊姓大名?" },
|
||||
{ type: "character", character: "mengsan_sushuang_zhangshiping_shuying", text: "在下苏双,这位是张世平。我二人皆是中山商贾。" },
|
||||
{ type: "character", character: "$player", text: "原来是苏兄、张兄。不知二位为何来到此处?" },
|
||||
{ type: "character", character: "mengsan_sushuang_zhangshiping_shuying", text: "我二人贩马经商,途经涿郡。久闻玄德素有豪侠之名,特来拜会。" },
|
||||
{ type: "character", character: "mengsan_sushuang_zhangshiping_shuying", text: "入城打听之时,恰遇张壮士。听闻玄德正在此处,我二人便一路寻来。" },
|
||||
{ type: "character", character: "mengsan_sushuang_zhangshiping_shuying", text: "不料刚到此地,便见玄德与黄巾贼交战。今日一见,果然名不虚传。" },
|
||||
{ type: "character", character: "$player", text: "二位过誉了。既然远道而来,不如随我回寒舍一叙?" },
|
||||
{ type: "character", character: "mengsan_sushuang_zhangshiping_shuying", text: "玄德盛情,我二人心领了。只是商队不日便要启程,实不便久留。" },
|
||||
{ type: "character", character: "mengsan_sushuang_zhangshiping_shuying", text: "听闻玄德正欲招募乡勇、讨伐黄巾。我二人虽是商贾,也愿略尽绵薄之力。" },
|
||||
{ type: "character", character: "mengsan_sushuang_zhangshiping_shuying", text: "我二人常年贩马,别的或许帮不上忙,良马倒有一些,愿赠与玄德,助义兵一臂之力。" },
|
||||
{ type: "character", character: "mengsan_sushuang_zhangshiping_shuying", text: "至于甲胄兵器,我二人并不经营。听闻蜀郡有一年轻匠人,名唤蒲元,颇善锻造,如今在刘焉麾下。玄德若欲寻他,不妨前往投靠刘焉。" }
|
||||
];
|
||||
|
||||
export const openingRewards = {
|
||||
"act1.reward.dilu": {
|
||||
id: "act1.reward.dilu", name: "的卢", description: "+1坐骑,加入本次征程的个人牌库。",
|
||||
card: { name: "dilu", suit: "club", number: 5, nature: null },
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
id: "act1.battle.liubeiOpening", kind: "battle", name: "刘备初始剧情", mapName: "初识的卢",
|
||||
requiredCharacter: "mengsan_liubei_shuying",
|
||||
enemies: ["mengsan_soldier_shuying"], gold: 20,
|
||||
skipRandomReward: true, fixedRewards: ["act1.reward.dilu"], openingDialogue, victoryDialogue,
|
||||
battlePlan: {
|
||||
units: [{
|
||||
id: "soldier", character: "mengsan_soldier_shuying", camp: "enemy",
|
||||
inheritSkills: false, skills: [], hand: 4, hp: 24, maxHp: 24,
|
||||
equipment: [{ name: "dilu", suit: "club", number: 5 }],
|
||||
}],
|
||||
rules: [{
|
||||
id: "merchants_arrive", name: "苏双与张世平支援",
|
||||
when: { event: "turnEnd", actor: "player", actorTurns: 4 },
|
||||
effects: [
|
||||
{ type: "dialogue", lines: reinforcementDialogue },
|
||||
{
|
||||
type: "spawn", unit: {
|
||||
id: "merchants", character: "mengsan_sushuang_zhangshiping_shuying", camp: "ally",
|
||||
after: "player", inheritSkills: false, skills: [], hand: 4, hp: 4, maxHp: 4,
|
||||
}
|
||||
},
|
||||
{ type: "discardEquipment", target: "soldier", name: "dilu" },
|
||||
],
|
||||
}],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
import actMap from "./map.js";
|
||||
|
||||
export default {
|
||||
map: actMap,
|
||||
nodeContents: {},
|
||||
rewardPools: {},
|
||||
rewards: {},
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
const act2Map = {
|
||||
id: "act2",
|
||||
name: "第二关·迷梦",
|
||||
floorNodes: [1, 4, 4, 4, 4, 1],
|
||||
minFloorNodes: [1, 3, 2, 3, 3, 1],
|
||||
nodeWeights: { battle: 55, elite: 15, event: 12, rest: 9, shop: 9 },
|
||||
enemies: ["re_ganning", "re_zhangfei", "re_huangzhong", "re_weiyan"],
|
||||
eliteEnemies: ["re_guanyu", "dianwei"],
|
||||
boss: "shen_guanyu",
|
||||
baseGold: 30,
|
||||
};
|
||||
|
||||
export default act2Map;
|
||||
@@ -0,0 +1,8 @@
|
||||
import actMap from "./map.js";
|
||||
|
||||
export default {
|
||||
map: actMap,
|
||||
nodeContents: {},
|
||||
rewardPools: {},
|
||||
rewards: {},
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
const act3Map = {
|
||||
id: "act3",
|
||||
name: "第三关·终梦",
|
||||
floorNodes: [1, 4, 4, 4, 4, 4, 1],
|
||||
minFloorNodes: [1, 3, 2, 3, 2, 3, 1],
|
||||
nodeWeights: { battle: 45, elite: 20, event: 15, rest: 10, shop: 10 },
|
||||
enemies: ["re_simayi", "re_sunben", "dongzhuo", "re_yuanshao"],
|
||||
eliteEnemies: ["shen_zhaoyun", "shen_simayi"],
|
||||
boss: "shen_caocao",
|
||||
baseGold: 40,
|
||||
};
|
||||
|
||||
export default act3Map;
|
||||
@@ -0,0 +1,31 @@
|
||||
// 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", null, Object.freeze(["annihilate"])],
|
||||
].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", null, ["annihilate"]],
|
||||
],
|
||||
};
|
||||
|
||||
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) => ({
|
||||
id: `${prefix}_${index}`, suit: entry[0], number: entry[1], name: entry[2],
|
||||
nature: entry[3] || null, affixes: entry[4] ? entry[4].slice() : [], upgrade: 0,
|
||||
}));
|
||||
}
|
||||
@@ -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 "../runtime/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,99 @@
|
||||
import { validateBattlePlan } from "../runtime/battle-director.js";
|
||||
import act1 from "./acts/act1/index.js";
|
||||
import act2 from "./acts/act2/index.js";
|
||||
import act3 from "./acts/act3/index.js";
|
||||
import shared from "./shared/index.js";
|
||||
|
||||
const addEntries = (target, source, category) => {
|
||||
Object.entries(source || {}).forEach(([id, value]) => {
|
||||
if (Object.prototype.hasOwnProperty.call(target, id)) throw new Error(`梦三${category} ID重复:${id}`);
|
||||
target[id] = value;
|
||||
});
|
||||
};
|
||||
|
||||
const packages = [shared, act1, act2, act3];
|
||||
const nodeContents = {};
|
||||
const rewardPools = {};
|
||||
const rewards = {};
|
||||
const dialogueTypes = new Set(["character", "narrator", "sound", "choice"]);
|
||||
|
||||
const validateDialogue = (dialogue, location) => {
|
||||
if (dialogue == null) return;
|
||||
if (!Array.isArray(dialogue)) throw new Error(`梦三剧情台词必须为数组:${location}`);
|
||||
dialogue.forEach((line, index) => {
|
||||
const lineLocation = `${location} #${index + 1}`;
|
||||
if (!line || !dialogueTypes.has(line.type)) throw new Error(`梦三剧情台词类型无效:${lineLocation}`);
|
||||
if (typeof line.text != "string" || !line.text.trim()) throw new Error(`梦三剧情台词内容为空:${lineLocation}`);
|
||||
if (line.type == "character" && !line.character) throw new Error(`梦三角色台词缺少武将ID:${lineLocation}`);
|
||||
if (line.when && (!/^story\.[\w.]+$/.test(line.when.flag || "") || typeof line.when.equals !== "string")) throw new Error(`梦三剧情条件无效:${lineLocation}`);
|
||||
if (line.type == "choice" && (!/^story\.[\w.]+$/.test(line.flag || "") || !Array.isArray(line.choices) || line.choices.length < 1 ||
|
||||
new Set(line.choices.map(choice => choice.id)).size !== line.choices.length ||
|
||||
line.choices.some(choice => !/^[\w.-]+$/.test(choice.id || "") || typeof choice.text !== "string" || !choice.text.trim()) ||
|
||||
(line.defaultChoice != null && !line.choices.some(choice => choice.id === line.defaultChoice)))) {
|
||||
throw new Error(`梦三发言选项无效:${lineLocation}`);
|
||||
}
|
||||
if (line.speed != null && (!Number.isFinite(line.speed) || line.speed < 0)) {
|
||||
throw new Error(`梦三剧情台词速度无效:${lineLocation}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
packages.forEach(content => {
|
||||
addEntries(nodeContents, content.nodeContents, "节点内容");
|
||||
addEntries(rewardPools, content.rewardPools, "奖励池");
|
||||
addEntries(rewards, content.rewards, "奖励");
|
||||
});
|
||||
|
||||
const acts = [act1.map, act2.map, act3.map];
|
||||
if (new Set(acts.map(act => act.id)).size != acts.length) throw new Error("梦三大关ID存在重复");
|
||||
acts.forEach(act => {
|
||||
(act.fixedNodes || []).forEach(node => {
|
||||
if (!nodeContents[node.contentId]) throw new Error(`梦三固定节点内容不存在:${node.contentId}`);
|
||||
if (!Number.isInteger(node.floor) || node.floor < 0 || node.floor >= act.floorNodes.length) {
|
||||
throw new Error(`梦三固定节点楼层越界:${node.contentId}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
Object.entries(rewardPools).forEach(([poolId, rewardIds]) => {
|
||||
if (!Array.isArray(rewardIds) || rewardIds.length < 3) throw new Error(`梦三奖励池至少需要三项:${poolId}`);
|
||||
if (new Set(rewardIds).size != rewardIds.length) throw new Error(`梦三奖励池存在重复项:${poolId}`);
|
||||
rewardIds.forEach(rewardId => {
|
||||
if (!rewards[rewardId]) throw new Error(`梦三奖励不存在:${poolId} -> ${rewardId}`);
|
||||
});
|
||||
});
|
||||
Object.entries(nodeContents).forEach(([contentId, content]) => {
|
||||
if (content.id != contentId) throw new Error(`梦三节点内容ID不一致:${contentId}`);
|
||||
validateDialogue(content.dialogue, contentId);
|
||||
validateDialogue(content.victoryDialogue, contentId + " 战后");
|
||||
validateDialogue(content.openingDialogue, contentId + " 开场");
|
||||
for (const id of content.fixedRewards || []) if (!rewards[id]) throw new Error("固定奖励不存在:" + id);
|
||||
if (content.battlePlan) validateBattlePlan(content.battlePlan);
|
||||
(content.choices || []).forEach(choice => {
|
||||
if (!choice.id) throw new Error(`梦三剧情选项缺少ID:${contentId}`);
|
||||
const outcome = choice.outcome || {};
|
||||
if (outcome.battle?.battlePlan) validateBattlePlan(outcome.battle.battlePlan);
|
||||
validateDialogue(outcome.dialogue, `${contentId} -> ${choice.id}`);
|
||||
const referencedPools = [outcome.rewardPool, outcome.battle?.rewardPool].filter(Boolean);
|
||||
referencedPools.forEach(poolId => {
|
||||
if (!rewardPools[poolId]) throw new Error(`梦三剧情奖励池不存在:${contentId} -> ${poolId}`);
|
||||
});
|
||||
if (outcome.battle && !outcome.battle.enemies?.length && !outcome.battle.battlePlan) {
|
||||
throw new Error(`梦三剧情战斗敌人池为空:${contentId} -> ${choice.id}`);
|
||||
}
|
||||
if (outcome.confirmResult != null && typeof outcome.confirmResult != "boolean") {
|
||||
throw new Error(`梦三剧情confirmResult必须为布尔值:${contentId} -> ${choice.id}`);
|
||||
}
|
||||
if (choice.requires && !Array.isArray(choice.requires)) throw new Error(`梦三剧情requires必须为数组:${contentId}`);
|
||||
if (choice.excludes && !Array.isArray(choice.excludes)) throw new Error(`梦三剧情excludes必须为数组:${contentId}`);
|
||||
});
|
||||
});
|
||||
|
||||
Object.values(rewards).forEach(reward => {
|
||||
if (!reward.support) return;
|
||||
validateBattlePlan({ units: [{ ...reward.support.unit, id: "reward_support" }], rules: [] });
|
||||
const battles = reward.support.battles ?? -1;
|
||||
if (battles !== -1 && (!Number.isInteger(battles) || battles < 1 || battles > 99)) throw new Error("梦三支援持续场数无效");
|
||||
});
|
||||
|
||||
const contentRegistry = { acts, nodeContents, rewardPools, rewards };
|
||||
|
||||
export default contentRegistry;
|
||||
@@ -0,0 +1,17 @@
|
||||
// 梦三私有武将:不改动原版界刘备,也不导入整套国战技能。
|
||||
export function createScenarioCharacters(Character) {
|
||||
return {
|
||||
mengsan_liubei_shuying: new Character({ sex: "male", group: "han", hp: 50, maxHp: 50,
|
||||
skills: [], img: "image/character/re_liubei.jpg" }),
|
||||
// 国战男性普通小兵使用原生 shibing1 立绘;实际战斗生命由怪物类或关卡配置决定。
|
||||
mengsan_soldier_shuying: new Character({ sex: "male", group: "qun", hp: 4, maxHp: 4,
|
||||
skills: [], img: "image/character/shibing1.jpg" }),
|
||||
mengsan_sushuang_zhangshiping_shuying: new Character({ sex: "male", group: "shu", hp: 4, maxHp: 4,
|
||||
skills: [], img: "extension/术樱包/mengsan/assets/sushuang-zhangshiping.png", names: "苏|双-张|世平" }),
|
||||
};
|
||||
}
|
||||
export const scenarioTranslations = {
|
||||
mengsan_liubei_shuying: "界刘备", mengsan_liubei_shuying_title: "50/50生命 · 无初始技能",
|
||||
mengsan_soldier_shuying: "士兵",
|
||||
mengsan_sushuang_zhangshiping_shuying: "苏双&张世平",
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
import { rewardPools, rewards } from "./rewards.js";
|
||||
|
||||
export default {
|
||||
nodeContents: {},
|
||||
rewardPools,
|
||||
rewards,
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
export const rewards = {
|
||||
"shared.reward.support.scout": {
|
||||
name: "道具·斥候援令", description: "本次征程后续每场战斗,友方张辽携 4 张手牌参战;重复获得不叠加人数。",
|
||||
support: { category: "item", battles: -1, unit: { character: "re_zhangliao", camp: "ally", hand: 4 } },
|
||||
},
|
||||
"shared.reward.card.sha": { effectId: "card_sha", name: "获得一张【杀】", description: "加入独立永久牌组" },
|
||||
"shared.reward.card.tao": { effectId: "card_tao", name: "获得一张【桃】", description: "加入独立永久牌组" },
|
||||
"shared.reward.card.upgrade": { effectId: "upgrade", name: "随机强化", description: "随机一张牌强化一级" },
|
||||
"shared.reward.heal": { effectId: "heal", name: "整顿伤势", description: "回复 8 点生命" },
|
||||
"shared.reward.item.handCharm": { effectId: "item_hand", name: "道具·束带", description: "基础手牌上限额外 +1" },
|
||||
"shared.reward.skill.heroic": { effectId: "skill_yingyong", name: "极品技能·英勇", description: "本次征程永久获得【英勇】" },
|
||||
"shared.reward.maxHp": { effectId: "max_hp", name: "极品强化·生机", description: "生命上限与当前生命各 +5" },
|
||||
};
|
||||
|
||||
export const rewardPools = {
|
||||
"shared.pool.battle.normal": [
|
||||
"shared.reward.card.sha",
|
||||
"shared.reward.card.tao",
|
||||
"shared.reward.card.upgrade",
|
||||
],
|
||||
"shared.pool.boss.premium": [
|
||||
"shared.reward.skill.heroic",
|
||||
"shared.reward.item.handCharm",
|
||||
"shared.reward.maxHp",
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user