From f2da9bce65b1320f113de651e2be6205bcba0814 Mon Sep 17 00:00:00 2001 From: Art_Sakura <1754798088@qq.com> Date: Wed, 30 Sep 2026 10:23:58 +0800 Subject: [PATCH] =?UTF-8?q?fixed=20#=20=E9=87=8D=E6=9E=84=E6=A2=A6?= =?UTF-8?q?=E4=B8=89=E9=A1=B9=E7=9B=AE=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mengsan/README.md | 17 ++ .../{ => portraits}/flyconid-portrait.png | Bin .../raider-assassin-portrait.png | Bin .../{ => portraits}/raider-axe-portrait.png | Bin .../{ => portraits}/raider-brute-portrait.png | Bin .../raider-crossbow-portrait.png | Bin .../raider-tracker-portrait.png | Bin .../{ => portraits}/sushuang-zhangshiping.png | Bin .../{runtime => battle}/battle-director.js | 0 mengsan/{runtime => battle}/battle-flow.js | 0 mengsan/{runtime => battle}/battle-loop.js | 0 .../{runtime => battle}/battle-resources.js | 0 mengsan/{runtime => battle}/battle-session.js | 0 .../{runtime => battle}/battle-settlement.js | 0 mengsan/{runtime => battle}/combat-rules.js | 0 mengsan/{runtime => battle}/death-blow.js | 0 mengsan/{runtime => battle}/engine-session.js | 0 mengsan/{runtime => battle}/intent-damage.js | 0 mengsan/{runtime => battle}/skill-teardown.js | 0 mengsan/{runtime => battle}/stun-intent.js | 0 mengsan/{content => cards}/affixes.js | 0 mengsan/{runtime => cards}/card-data.js | 2 +- .../{content => cards}/card-definitions.js | 0 mengsan/{content => cards}/mode-cards.js | 2 +- mengsan/{runtime => cards}/personal-piles.js | 4 +- .../starting-deck.js} | 2 +- mengsan/config.js | 2 +- mengsan/content/registry.js | 2 +- mengsan/content/scenario-characters.js | 14 +- mengsan/docs/combat-rules.md | 12 +- mengsan/docs/liubei-opening.md | 2 +- mengsan/mode.js | 169 +++++------------- mengsan/monsters/actions.js | 131 ++++++++++++++ .../{runtime => monsters}/flyconid-intent.js | 0 mengsan/{runtime => monsters}/monster.js | 2 +- .../{runtime => monsters}/raider-intent.js | 0 .../dialogue-choice.js | 0 .../{runtime => progression}/mode-storage.js | 0 mengsan/{runtime => progression}/reward.js | 4 +- mengsan/{ => progression}/state.js | 6 +- mengsan/{runtime => progression}/story.js | 0 mengsan/{runtime => ui}/card-library.js | 2 +- mengsan/{runtime => ui}/dialogue.js | 2 +- mengsan/ui/energy-display.js | 13 ++ mengsan/{runtime => ui}/flow-ui.js | 0 mengsan/{runtime => ui}/gm-manager.js | 2 +- mengsan/{runtime => ui}/intent-display.js | 39 ++-- mengsan/{ => ui}/map/index.js | 0 mengsan/{ => ui}/map/renderer.js | 10 +- mengsan/{runtime => ui}/navigation.js | 0 mengsan/{runtime => ui}/reward-ui.js | 2 +- mengsan/{ => ui}/style.css | 0 tools/generate_manifest.py | 1 + 53 files changed, 267 insertions(+), 175 deletions(-) create mode 100644 mengsan/README.md rename mengsan/assets/{ => portraits}/flyconid-portrait.png (100%) rename mengsan/assets/{ => portraits}/raider-assassin-portrait.png (100%) rename mengsan/assets/{ => portraits}/raider-axe-portrait.png (100%) rename mengsan/assets/{ => portraits}/raider-brute-portrait.png (100%) rename mengsan/assets/{ => portraits}/raider-crossbow-portrait.png (100%) rename mengsan/assets/{ => portraits}/raider-tracker-portrait.png (100%) rename mengsan/assets/{ => portraits}/sushuang-zhangshiping.png (100%) rename mengsan/{runtime => battle}/battle-director.js (100%) rename mengsan/{runtime => battle}/battle-flow.js (100%) rename mengsan/{runtime => battle}/battle-loop.js (100%) rename mengsan/{runtime => battle}/battle-resources.js (100%) rename mengsan/{runtime => battle}/battle-session.js (100%) rename mengsan/{runtime => battle}/battle-settlement.js (100%) rename mengsan/{runtime => battle}/combat-rules.js (100%) rename mengsan/{runtime => battle}/death-blow.js (100%) rename mengsan/{runtime => battle}/engine-session.js (100%) rename mengsan/{runtime => battle}/intent-damage.js (100%) rename mengsan/{runtime => battle}/skill-teardown.js (100%) rename mengsan/{runtime => battle}/stun-intent.js (100%) rename mengsan/{content => cards}/affixes.js (100%) rename mengsan/{runtime => cards}/card-data.js (96%) rename mengsan/{content => cards}/card-definitions.js (100%) rename mengsan/{content => cards}/mode-cards.js (99%) rename mengsan/{runtime => cards}/personal-piles.js (99%) rename mengsan/{content/card-library.js => cards/starting-deck.js} (96%) create mode 100644 mengsan/monsters/actions.js rename mengsan/{runtime => monsters}/flyconid-intent.js (100%) rename mengsan/{runtime => monsters}/monster.js (98%) rename mengsan/{runtime => monsters}/raider-intent.js (100%) rename mengsan/{runtime => progression}/dialogue-choice.js (100%) rename mengsan/{runtime => progression}/mode-storage.js (100%) rename mengsan/{runtime => progression}/reward.js (89%) rename mengsan/{ => progression}/state.js (98%) rename mengsan/{runtime => progression}/story.js (100%) rename mengsan/{runtime => ui}/card-library.js (99%) rename mengsan/{runtime => ui}/dialogue.js (99%) create mode 100644 mengsan/ui/energy-display.js rename mengsan/{runtime => ui}/flow-ui.js (100%) rename mengsan/{runtime => ui}/gm-manager.js (98%) rename mengsan/{runtime => ui}/intent-display.js (86%) rename mengsan/{ => ui}/map/index.js (100%) rename mengsan/{ => ui}/map/renderer.js (98%) rename mengsan/{runtime => ui}/navigation.js (100%) rename mengsan/{runtime => ui}/reward-ui.js (99%) rename mengsan/{ => ui}/style.css (100%) diff --git a/mengsan/README.md b/mengsan/README.md new file mode 100644 index 0000000..405bfce --- /dev/null +++ b/mengsan/README.md @@ -0,0 +1,17 @@ +# 梦三模式目录 + +`register.js` 是扩展加载入口,`mode.js` 负责组装模式、注册技能并连接各领域模块,`config.js` 提供模式配置。发布器和外部扩展应继续从 `register.js` 加载,避免依赖内部文件路径。 + +| 目录 | 职责 | +| --- | --- | +| `battle/` | 战斗会话、回合、结算、费用与通用战斗效果 | +| `monsters/` | 怪物阶级、牌堆默认值和各怪物意图选择 | +| `cards/` | 卡牌定义、词缀、生成和个人牌堆 | +| `progression/` | 征程状态、存档、剧情选择和奖励逻辑 | +| `content/` | 章节、遭遇、剧本、角色与内容注册表 | +| `ui/` | 地图、对话、意图、奖励、图鉴及样式 | +| `assets/intent/` | 意图图标 | +| `assets/portraits/` | 角色立绘 | +| `docs/` | 战斗和关卡配置说明 | + +新增功能优先放入所属领域;界面代码放在 `ui/`,怪物行动逻辑放在 `monsters/`。跨领域引用使用相对路径,避免新增一个涵盖所有功能的扁平 `runtime/` 目录。模块资源文件随 `tools/generate_manifest.py` 自动收录,发布入口仍为 `mengsan/register.js`。 diff --git a/mengsan/assets/flyconid-portrait.png b/mengsan/assets/portraits/flyconid-portrait.png similarity index 100% rename from mengsan/assets/flyconid-portrait.png rename to mengsan/assets/portraits/flyconid-portrait.png diff --git a/mengsan/assets/raider-assassin-portrait.png b/mengsan/assets/portraits/raider-assassin-portrait.png similarity index 100% rename from mengsan/assets/raider-assassin-portrait.png rename to mengsan/assets/portraits/raider-assassin-portrait.png diff --git a/mengsan/assets/raider-axe-portrait.png b/mengsan/assets/portraits/raider-axe-portrait.png similarity index 100% rename from mengsan/assets/raider-axe-portrait.png rename to mengsan/assets/portraits/raider-axe-portrait.png diff --git a/mengsan/assets/raider-brute-portrait.png b/mengsan/assets/portraits/raider-brute-portrait.png similarity index 100% rename from mengsan/assets/raider-brute-portrait.png rename to mengsan/assets/portraits/raider-brute-portrait.png diff --git a/mengsan/assets/raider-crossbow-portrait.png b/mengsan/assets/portraits/raider-crossbow-portrait.png similarity index 100% rename from mengsan/assets/raider-crossbow-portrait.png rename to mengsan/assets/portraits/raider-crossbow-portrait.png diff --git a/mengsan/assets/raider-tracker-portrait.png b/mengsan/assets/portraits/raider-tracker-portrait.png similarity index 100% rename from mengsan/assets/raider-tracker-portrait.png rename to mengsan/assets/portraits/raider-tracker-portrait.png diff --git a/mengsan/assets/sushuang-zhangshiping.png b/mengsan/assets/portraits/sushuang-zhangshiping.png similarity index 100% rename from mengsan/assets/sushuang-zhangshiping.png rename to mengsan/assets/portraits/sushuang-zhangshiping.png diff --git a/mengsan/runtime/battle-director.js b/mengsan/battle/battle-director.js similarity index 100% rename from mengsan/runtime/battle-director.js rename to mengsan/battle/battle-director.js diff --git a/mengsan/runtime/battle-flow.js b/mengsan/battle/battle-flow.js similarity index 100% rename from mengsan/runtime/battle-flow.js rename to mengsan/battle/battle-flow.js diff --git a/mengsan/runtime/battle-loop.js b/mengsan/battle/battle-loop.js similarity index 100% rename from mengsan/runtime/battle-loop.js rename to mengsan/battle/battle-loop.js diff --git a/mengsan/runtime/battle-resources.js b/mengsan/battle/battle-resources.js similarity index 100% rename from mengsan/runtime/battle-resources.js rename to mengsan/battle/battle-resources.js diff --git a/mengsan/runtime/battle-session.js b/mengsan/battle/battle-session.js similarity index 100% rename from mengsan/runtime/battle-session.js rename to mengsan/battle/battle-session.js diff --git a/mengsan/runtime/battle-settlement.js b/mengsan/battle/battle-settlement.js similarity index 100% rename from mengsan/runtime/battle-settlement.js rename to mengsan/battle/battle-settlement.js diff --git a/mengsan/runtime/combat-rules.js b/mengsan/battle/combat-rules.js similarity index 100% rename from mengsan/runtime/combat-rules.js rename to mengsan/battle/combat-rules.js diff --git a/mengsan/runtime/death-blow.js b/mengsan/battle/death-blow.js similarity index 100% rename from mengsan/runtime/death-blow.js rename to mengsan/battle/death-blow.js diff --git a/mengsan/runtime/engine-session.js b/mengsan/battle/engine-session.js similarity index 100% rename from mengsan/runtime/engine-session.js rename to mengsan/battle/engine-session.js diff --git a/mengsan/runtime/intent-damage.js b/mengsan/battle/intent-damage.js similarity index 100% rename from mengsan/runtime/intent-damage.js rename to mengsan/battle/intent-damage.js diff --git a/mengsan/runtime/skill-teardown.js b/mengsan/battle/skill-teardown.js similarity index 100% rename from mengsan/runtime/skill-teardown.js rename to mengsan/battle/skill-teardown.js diff --git a/mengsan/runtime/stun-intent.js b/mengsan/battle/stun-intent.js similarity index 100% rename from mengsan/runtime/stun-intent.js rename to mengsan/battle/stun-intent.js diff --git a/mengsan/content/affixes.js b/mengsan/cards/affixes.js similarity index 100% rename from mengsan/content/affixes.js rename to mengsan/cards/affixes.js diff --git a/mengsan/runtime/card-data.js b/mengsan/cards/card-data.js similarity index 96% rename from mengsan/runtime/card-data.js rename to mengsan/cards/card-data.js index 8aa29fa..7ba963f 100644 --- a/mengsan/runtime/card-data.js +++ b/mengsan/cards/card-data.js @@ -1,4 +1,4 @@ -import { cardDefinitions, canAcquireCard } from "../content/card-definitions.js"; +import { cardDefinitions, canAcquireCard } from "./card-definitions.js"; const suits = ["spade", "heart", "club", "diamond"]; diff --git a/mengsan/content/card-definitions.js b/mengsan/cards/card-definitions.js similarity index 100% rename from mengsan/content/card-definitions.js rename to mengsan/cards/card-definitions.js diff --git a/mengsan/content/mode-cards.js b/mengsan/cards/mode-cards.js similarity index 99% rename from mengsan/content/mode-cards.js rename to mengsan/cards/mode-cards.js index 2923711..c134c0b 100644 --- a/mengsan/content/mode-cards.js +++ b/mengsan/cards/mode-cards.js @@ -3,7 +3,7 @@ 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"; +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])))]; diff --git a/mengsan/runtime/personal-piles.js b/mengsan/cards/personal-piles.js similarity index 99% rename from mengsan/runtime/personal-piles.js rename to mengsan/cards/personal-piles.js index 13ad60c..7333ae8 100644 --- a/mengsan/runtime/personal-piles.js +++ b/mengsan/cards/personal-piles.js @@ -1,7 +1,7 @@ // 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 "./combat-rules.js"; -import { AFFIX_INFO } from "../content/affixes.js"; +import { cardCost } from "../battle/combat-rules.js"; +import { AFFIX_INFO } from "./affixes.js"; export function installPersonalPiles(session, owner, battle, run, resources, env) { diff --git a/mengsan/content/card-library.js b/mengsan/cards/starting-deck.js similarity index 96% rename from mengsan/content/card-library.js rename to mengsan/cards/starting-deck.js index 60ce97a..cbf23fb 100644 --- a/mengsan/content/card-library.js +++ b/mengsan/cards/starting-deck.js @@ -1,4 +1,4 @@ -import { createCardData } from "../runtime/card-data.js"; +import { createCardData } from "./card-data.js"; // Mode-owned starting decks. Existing saves always retain their actual player.deck. export const defaultDeck = Object.freeze([ diff --git a/mengsan/config.js b/mengsan/config.js index 8c2bdb8..dda6082 100644 --- a/mengsan/config.js +++ b/mengsan/config.js @@ -1,4 +1,4 @@ -import { defaultDeck } from "./content/card-library.js"; +import { defaultDeck } from "./cards/starting-deck.js"; import contentRegistry from "./content/registry.js"; const mengsanConfig = { diff --git a/mengsan/content/registry.js b/mengsan/content/registry.js index aaa817a..8718c18 100644 --- a/mengsan/content/registry.js +++ b/mengsan/content/registry.js @@ -1,4 +1,4 @@ -import { validateBattlePlan } from "../runtime/battle-director.js"; +import { validateBattlePlan } from "../battle/battle-director.js"; import act1 from "./acts/act1/index.js"; import act2 from "./acts/act2/index.js"; import act3 from "./acts/act3/index.js"; diff --git a/mengsan/content/scenario-characters.js b/mengsan/content/scenario-characters.js index f914be3..647788c 100644 --- a/mengsan/content/scenario-characters.js +++ b/mengsan/content/scenario-characters.js @@ -7,19 +7,19 @@ export function createScenarioCharacters(Character) { mengsan_soldier_shuying: new Character({ sex: "male", group: "qun", hp: 4, maxHp: 4, skills: [], img: "image/character/shibing1.jpg" }), mengsan_flyconid_shuying: new Character({ sex: "none", group: "qun", hp: 48, maxHp: 48, - skills: [], img: "extension/术樱包/mengsan/assets/flyconid-portrait.png" }), + skills: [], img: "extension/术樱包/mengsan/assets/portraits/flyconid-portrait.png" }), mengsan_raider_brute_shuying: new Character({ sex: "none", group: "qun", hp: 32, maxHp: 32, - skills: [], img: "extension/术樱包/mengsan/assets/raider-brute-portrait.png" }), + skills: [], img: "extension/术樱包/mengsan/assets/portraits/raider-brute-portrait.png" }), mengsan_raider_assassin_shuying: new Character({ sex: "none", group: "qun", hp: 21, maxHp: 21, - skills: [], img: "extension/术樱包/mengsan/assets/raider-assassin-portrait.png" }), + skills: [], img: "extension/术樱包/mengsan/assets/portraits/raider-assassin-portrait.png" }), mengsan_raider_axe_shuying: new Character({ sex: "none", group: "qun", hp: 21, maxHp: 21, - skills: [], img: "extension/术樱包/mengsan/assets/raider-axe-portrait.png" }), + skills: [], img: "extension/术樱包/mengsan/assets/portraits/raider-axe-portrait.png" }), mengsan_raider_crossbow_shuying: new Character({ sex: "none", group: "qun", hp: 20, maxHp: 20, - skills: [], img: "extension/术樱包/mengsan/assets/raider-crossbow-portrait.png" }), + skills: [], img: "extension/术樱包/mengsan/assets/portraits/raider-crossbow-portrait.png" }), mengsan_raider_tracker_shuying: new Character({ sex: "none", group: "qun", hp: 23, maxHp: 23, - skills: [], img: "extension/术樱包/mengsan/assets/raider-tracker-portrait.png" }), + skills: [], img: "extension/术樱包/mengsan/assets/portraits/raider-tracker-portrait.png" }), mengsan_sushuang_zhangshiping_shuying: new Character({ sex: "male", group: "shu", hp: 4, maxHp: 4, - skills: [], img: "extension/术樱包/mengsan/assets/sushuang-zhangshiping.png", names: "苏|双-张|世平" }), + skills: [], img: "extension/术樱包/mengsan/assets/portraits/sushuang-zhangshiping.png", names: "苏|双-张|世平" }), }; } export const scenarioTranslations = { diff --git a/mengsan/docs/combat-rules.md b/mengsan/docs/combat-rules.md index b7b6725..8533b1f 100644 --- a/mengsan/docs/combat-rules.md +++ b/mengsan/docs/combat-rules.md @@ -1,20 +1,20 @@ # 梦三战斗规则(测试阶段) -只影响梦三模式,不修改 noname 的标准/军争原始牌包。`content/mode-cards.js` 从标准、军争实体牌列表逐一复制模式专属定义,沿用原牌名以保留响应、装备、转化兼容性;图鉴说明在梦三模式覆盖。新征程的界刘备为 50/50 生命,默认每回合摸 2 张、手牌上限 3、回合费用恢复至 3。主动使用牌消耗费用,转化牌按转化后的牌名计费;响应【闪】、救援【桃】等不消耗费用。默认主动牌 1 费,`runtime/combat-rules.js` 中列出例外。【杀】1 费、可多次使用,牌定义的基础伤害为 4;【桃】及【桃园结义】回复 8 点生命;【南蛮入侵】、【万箭齐发】、【决斗】、【火攻】基础伤害 4,【闪电】命中伤害 12。其余卡牌保留原有效果,并加上梦三费用与图鉴说明。 +只影响梦三模式,不修改 noname 的标准/军争原始牌包。`cards/mode-cards.js` 从标准、军争实体牌列表逐一复制模式专属定义,沿用原牌名以保留响应、装备、转化兼容性;图鉴说明在梦三模式覆盖。新征程的界刘备为 50/50 生命,默认每回合摸 2 张、手牌上限 3、回合费用恢复至 3。主动使用牌消耗费用,转化牌按转化后的牌名计费;响应【闪】、救援【桃】等不消耗费用。默认主动牌 1 费,`battle/combat-rules.js` 中列出例外。【杀】1 费、可多次使用,牌定义的基础伤害为 4;【桃】及【桃园结义】回复 8 点生命;【南蛮入侵】、【万箭齐发】、【决斗】、【火攻】基础伤害 4,【闪电】命中伤害 12。其余卡牌保留原有效果,并加上梦三费用与图鉴说明。 ## 怪物 -`runtime/monster.js` 的 `Monster` 类统一提供阶级默认值。普通/精英/Boss 分别每回合摸 1/2/3 张、手牌上限 3/5/7、回合费用 2/3/4。每名敌人拥有自己的抽牌堆、弃牌堆与手牌;怪物默认使用阶级牌堆,不再从公共牌堆摸牌。友方支援仍用公共牌堆。 +`monsters/monster.js` 的 `Monster` 类统一提供阶级默认值。普通/精英/Boss 分别每回合摸 1/2/3 张、手牌上限 3/5/7、回合费用 2/3/4。每名敌人拥有自己的抽牌堆、弃牌堆与手牌;怪物默认使用阶级牌堆,不再从公共牌堆摸牌。友方支援仍用公共牌堆。 `HEALTH` 为目前各敌人的独立生命值;新怪物未配置时使用阶级兜底生命值。`OpeningSoldier extends Monster` 是继承示例;新子类在 `MONSTER_TYPES` 注册。关卡单位可以写 `tier`、`hp/maxHp`、`draw`、`handLimit`、`energy`、`deck` 覆盖默认值;`deck` 可为牌名数组(如 `deck:["sha","shan","tao"]`),也可用 `{name,suit,number}` 指定单张牌。牌名必须在 noname 中存在。 ## 配置位置 -- 玩家费用、手牌上限、卡牌费用与【杀】伤害:`runtime/combat-rules.js` -- 梦三模式卡牌复制、内容与图鉴文案:`content/mode-cards.js` -- 怪物阶级、默认牌堆、生命与继承示例:`runtime/monster.js` +- 玩家费用、手牌上限、卡牌费用与【杀】伤害:`battle/combat-rules.js` +- 梦三模式卡牌复制、内容与图鉴文案:`cards/mode-cards.js` +- 怪物阶级、默认牌堆、生命与继承示例:`monsters/monster.js` - 刘备角色初始生命:`content/scenario-characters.js` -- 刘备专属初始牌组:`content/card-library.js` +- 刘备专属初始牌组:`cards/starting-deck.js` - 首关小兵覆盖数值:`content/acts/act1/stories/liubei-opening.js` 旧存档不作迁移。源码检查不等于游戏内、十周年 UI 或 AI 行为验收。 diff --git a/mengsan/docs/liubei-opening.md b/mengsan/docs/liubei-opening.md index 8a0fe4a..b5dfe5b 100644 --- a/mengsan/docs/liubei-opening.md +++ b/mengsan/docs/liubei-opening.md @@ -20,7 +20,7 @@ - 提前击败敌人允许正常获胜,此时未到第四次回合的支援不会触发。不强行等待、不添加免死。 ## 新角色立绘 -`assets/sushuang-zhangshiping.png` 为本次 image_gen 生成的原创双人立绘,不是官方素材。 +`assets/portraits/sushuang-zhangshiping.png` 为本次 image_gen 生成的原创双人立绘,不是官方素材。 ## 验证边界 隔离脚本检查配置、回合计数、效果顺序及奖励幂等,不等于游戏/十周年UI验收。请开始新征程检查真实引擎的装备、座次、AI和剧情显示。 diff --git a/mengsan/mode.js b/mengsan/mode.js index 574497a..d50f87b 100644 --- a/mengsan/mode.js +++ b/mengsan/mode.js @@ -1,16 +1,19 @@ import { createScenarioCharacters, scenarioTranslations } from "./content/scenario-characters.js"; -import { createMengsanCards } from "./content/mode-cards.js"; -import { buildBattlePlan, consumeSupports, createBattleDirector, grantSupport } from "./runtime/battle-director.js"; -import { installPersonalPiles } from "./runtime/personal-piles.js"; -import { createMonster } from "./runtime/monster.js"; -import { mountEnemyIntent } from "./runtime/intent-display.js"; -import { attackHitCount, outgoingAttackDamage } from "./runtime/intent-damage.js"; +import { createMengsanCards } from "./cards/mode-cards.js"; +import { buildBattlePlan, consumeSupports, createBattleDirector, grantSupport } from "./battle/battle-director.js"; +import { installPersonalPiles } from "./cards/personal-piles.js"; +import { createMonster } from "./monsters/monster.js"; +import { + createMonsterIntentActions, + isFlyconid, + isRaider, +} from "./monsters/actions.js"; +import { mountEnemyIntent } from "./ui/intent-display.js"; +import { mountEnergy } from "./ui/energy-display.js"; import { - beginDeathBlow, finishDeathBlow, hasPendingDeathBlow, - isDeathBlowIntent, -} from "./runtime/death-blow.js"; +} from "./battle/death-blow.js"; import { STUN_INTENT, applyStun, @@ -18,31 +21,31 @@ import { finishStunnedTurn, isStunned, skipStunnedAction, -} from "./runtime/stun-intent.js"; -import { selectFlyconidMove, recordFlyconidAction } from "./runtime/flyconid-intent.js"; -import { isRaiderCharacter, selectRaiderMove, recordRaiderAction } from "./runtime/raider-intent.js"; -import { PLAYER_ENERGY, PLAYER_HAND_LIMIT, canPayCard, payCard, isActiveCardUse } from "./runtime/combat-rules.js"; -import { mountBattlePiles } from "./runtime/card-library.js"; -import { mountGMManager } from "./runtime/gm-manager.js"; -import { createPlayerTeardown } from "./runtime/skill-teardown.js"; -import { createBattleResources } from "./runtime/battle-resources.js"; -import { createModeStorage } from "./runtime/mode-storage.js"; -import { createBattleSession } from "./runtime/battle-session.js"; -import { observeFreshEvent } from "./runtime/engine-session.js"; -import { runPhaseLoop, stopBattleTurn } from "./runtime/battle-loop.js"; -import { createBattleSettlement } from "./runtime/battle-settlement.js"; -import { createBattleFlow, quiesceEngine } from "./runtime/battle-flow.js"; +} from "./battle/stun-intent.js"; +import { recordFlyconidAction } from "./monsters/flyconid-intent.js"; +import { recordRaiderAction } from "./monsters/raider-intent.js"; +import { PLAYER_ENERGY, PLAYER_HAND_LIMIT, canPayCard, payCard, isActiveCardUse } from "./battle/combat-rules.js"; +import { mountBattlePiles } from "./ui/card-library.js"; +import { mountGMManager } from "./ui/gm-manager.js"; +import { createPlayerTeardown } from "./battle/skill-teardown.js"; +import { createBattleResources } from "./battle/battle-resources.js"; +import { createModeStorage } from "./progression/mode-storage.js"; +import { createBattleSession } from "./battle/battle-session.js"; +import { observeFreshEvent } from "./battle/engine-session.js"; +import { runPhaseLoop, stopBattleTurn } from "./battle/battle-loop.js"; +import { createBattleSettlement } from "./battle/battle-settlement.js"; +import { createBattleFlow, quiesceEngine } from "./battle/battle-flow.js"; import { lib, game, ui, get, _status } from "../../../noname.js"; import config from "./config.js"; -import { showMap } from "./map/index.js"; -import { getRandomRewardChoices } from "./runtime/reward.js"; -import { canAcquireCard } from "./content/card-definitions.js"; -import { addCardToDeck, createRandomCardData } from "./runtime/card-data.js"; -import { chooseBattleReward } from "./runtime/reward-ui.js"; -import { applyStoryOutcome, getAvailableStoryChoices } from "./runtime/story.js"; -import { playDialogue } from "./runtime/dialogue.js"; -import { chooseButtons } from "./runtime/flow-ui.js"; -import { openModeSelection } from "./runtime/navigation.js"; +import { showMap } from "./ui/map/index.js"; +import { getRandomRewardChoices } from "./progression/reward.js"; +import { canAcquireCard } from "./cards/card-definitions.js"; +import { addCardToDeck, createRandomCardData } from "./cards/card-data.js"; +import { chooseBattleReward } from "./ui/reward-ui.js"; +import { applyStoryOutcome, getAvailableStoryChoices } from "./progression/story.js"; +import { playDialogue } from "./ui/dialogue.js"; +import { chooseButtons } from "./ui/flow-ui.js"; +import { openModeSelection } from "./ui/navigation.js"; import { completeNode, createRun, @@ -51,7 +54,7 @@ import { getNodeEncounter, insertStoryNode, nextRandom, -} from "./state.js"; +} from "./progression/state.js"; const MODE_ID = config.modeId; const STYLE_PATH = `${lib.assetURL}extension/术樱包/mengsan`; @@ -278,94 +281,11 @@ const installMonsterPile = (player, monster, current) => { })); }; -const mountEnergy = (player, session) => { - const badge = document.createElement("div"); - badge.className = "mengsan-energy-shuying"; - const refresh = () => { badge.textContent = `费用 ${player.storage.mengsanEnergy_shuying}/${player.storage.mengsanMaxEnergy_shuying}`; }; - player.appendChild(badge); - session.ownResource(badge, () => badge.remove()); - refresh(); - return refresh; -}; -const isFlyconid = player => player?.name === "mengsan_flyconid_shuying" && player.storage?.mengsanCamp_shuying === "enemy"; -const isRaider = player => player?.storage?.mengsanCamp_shuying === "enemy" && isRaiderCharacter(player.name); -const planEnemyIntent = (player, run) => { - if (!player?.isAlive() || isStunned(player)) return; - if (isFlyconid(player)) { - if (player.storage.mengsanFlyconidIntent_shuying) return; - const move = selectFlyconidMove(player.storage.mengsanFlyconidState_shuying || {}, () => nextRandom(run)); - player.storage.mengsanFlyconidIntent_shuying = move; - game.mengsanSetEnemyIntent_shuying(player, move); - } else if (isRaider(player)) { - if (player.storage.mengsanRaiderIntent_shuying) return; - const move = selectRaiderMove(player.name, player.storage.mengsanRaiderState_shuying || {}); - player.storage.mengsanRaiderIntent_shuying = move; - game.mengsanSetEnemyIntent_shuying(player, move); - } -}; -const applyIntentDebuff = (target, move) => { - if (!move.debuff) return; - const frail = move.debuff === "脆弱"; - const key = frail ? "mengsanFrail_shuying" : "mengsanVulnerable_shuying"; - target.storage[key] = (target.storage[key] || 0) + move.stacks; - target.addSkill(frail ? "mengsan_frail_shuying" : "mengsan_vulnerable_shuying"); - target.markSkill(frail ? "mengsan_frail_shuying" : "mengsan_vulnerable_shuying"); -}; -const dealIntentDamage = async (source, target, move) => { - if (move.damage == null) return; - for (let index = 0; index < attackHitCount(move); index++) { - if (!target.isAlive() || !source.isAlive()) break; - const hit = target.damage(outgoingAttackDamage(move, source), source); - hit.mengsanAttack_shuying = true; - hit.mengsanScriptedSkill_shuying = true; - await hit; - } -}; -const executeFlyconidIntent = async player => { - const move = player.storage.mengsanFlyconidIntent_shuying; - if (!move) return; - player.storage.mengsanFlyconidIntent_shuying = null; - game.mengsanSetEnemyIntent_shuying(player, null); - const paid = player.storage.mengsanEnergy_shuying >= 1; - player.storage.mengsanFlyconidState_shuying = recordFlyconidAction(player.storage.mengsanFlyconidState_shuying || {}, move, paid); - if (!paid) { game.log(player, "费用不足,未发动", move.name); return; } - player.storage.mengsanEnergy_shuying--; - activeBattle?.energyUI.get(player)?.(); - game.log(player, "消耗1费用发动", move.name); - const target = game.me; - if (!target?.isAlive() || !player.isAlive()) return; - if (isDeathBlowIntent(move)) { - await beginDeathBlow(player, target, move); - return; - } - await dealIntentDamage(player, target, move); - if (target.isAlive() && player.isAlive()) applyIntentDebuff(target, move); -}; -const executeRaiderIntent = async player => { - const move = player.storage.mengsanRaiderIntent_shuying; - if (!move) return; - player.storage.mengsanRaiderIntent_shuying = null; - game.mengsanSetEnemyIntent_shuying(player, null); - player.storage.mengsanRaiderState_shuying = recordRaiderAction(player.storage.mengsanRaiderState_shuying || {}); - if (player.storage.mengsanEnergy_shuying < 1) { - game.log(player, "费用不足,未发动", move.name); - return; - } - player.storage.mengsanEnergy_shuying--; - activeBattle?.energyUI.get(player)?.(); - game.log(player, "消耗1费用发动", move.name); - const target = game.me; - if (!target?.isAlive() || !player.isAlive()) return; - await dealIntentDamage(player, target, move); - if (target.isAlive() && player.isAlive()) applyIntentDebuff(target, move); - if (!player.isAlive() || !activeBattle?.session.active) return; - if (move.block) await player.changeHujia(move.block); - if (move.strength) { - player.storage.mengsanStrength_shuying = (player.storage.mengsanStrength_shuying || 0) + move.strength; - player.addSkill("mengsan_raider_strength_shuying"); - player.markSkill("mengsan_raider_strength_shuying"); - } -}; +const { + planEnemyIntent, + executeFlyconidIntent, + executeRaiderIntent, +} = createMonsterIntentActions(game, () => activeBattle); const equipBattleUnit = async (player, spec, resources) => { for (const info of spec.equipment || []) { const card = resources.card(game.createCard(info.name, info.suit, info.number, info.nature)); @@ -388,7 +308,8 @@ const createScenario = (plan, current) => { const monster = initBattleUnit(player, spec); installMonsterPile(player, monster, current); if (monster) { - current.energyUI.set(player, mountEnergy(player, current.session)); + current.energyUI.set(player, + mountEnergy(player, current.session, document)); current.intentUI.set(player, mountEnemyIntent(player, current.session, STYLE_PATH, document, () => game.me)); } @@ -484,7 +405,7 @@ const prepareBattle = async (run, node, encounter, session, resources) => { me.storage.mengsanPlayer_shuying = true; me.storage.mengsanMaxEnergy_shuying = PLAYER_ENERGY; me.storage.mengsanEnergy_shuying = PLAYER_ENERGY; - currentBattle.energyUI.set(me, mountEnergy(me, session)); + currentBattle.energyUI.set(me, mountEnergy(me, session, document)); game.zhu = me; if (run.player.maxHp == null) { run.player.maxHp = me.maxHp; @@ -1085,7 +1006,7 @@ export async function createMengsanMode() { ] }); } await new Promise(resolve => { - const style = lib.init.css(STYLE_PATH, "style", resolve); + const style = lib.init.css(`${STYLE_PATH}/ui`, "style", resolve); style.addEventListener("error", resolve, { once: true }); }); const identityMode = await game.loadModeAsync("identity"); diff --git a/mengsan/monsters/actions.js b/mengsan/monsters/actions.js new file mode 100644 index 0000000..8403094 --- /dev/null +++ b/mengsan/monsters/actions.js @@ -0,0 +1,131 @@ +import { + attackHitCount, + outgoingAttackDamage, +} from "../battle/intent-damage.js"; +import { beginDeathBlow, isDeathBlowIntent } from "../battle/death-blow.js"; +import { isStunned } from "../battle/stun-intent.js"; +import { nextRandom } from "../progression/state.js"; +import { selectFlyconidMove, recordFlyconidAction } from "./flyconid-intent.js"; +import { + isRaiderCharacter, + selectRaiderMove, + recordRaiderAction, +} from "./raider-intent.js"; + +export const isFlyconid = player => + player?.name === "mengsan_flyconid_shuying" && + player.storage?.mengsanCamp_shuying === "enemy"; + +export const isRaider = player => + player?.storage?.mengsanCamp_shuying === "enemy" && + isRaiderCharacter(player.name); + +export function createMonsterIntentActions(game, getActiveBattle) { + const planEnemyIntent = (player, run) => { + if (!player?.isAlive() || isStunned(player)) return; + if (isFlyconid(player)) { + if (player.storage.mengsanFlyconidIntent_shuying) return; + const move = selectFlyconidMove( + player.storage.mengsanFlyconidState_shuying || {}, + () => nextRandom(run)); + player.storage.mengsanFlyconidIntent_shuying = move; + game.mengsanSetEnemyIntent_shuying(player, move); + } else if (isRaider(player)) { + if (player.storage.mengsanRaiderIntent_shuying) return; + const move = selectRaiderMove(player.name, + player.storage.mengsanRaiderState_shuying || {}); + player.storage.mengsanRaiderIntent_shuying = move; + game.mengsanSetEnemyIntent_shuying(player, move); + } + }; + + const applyIntentDebuff = (target, move) => { + if (!move.debuff) return; + const frail = move.debuff === "脆弱"; + const key = frail ? "mengsanFrail_shuying" : + "mengsanVulnerable_shuying"; + target.storage[key] = (target.storage[key] || 0) + move.stacks; + target.addSkill(frail ? "mengsan_frail_shuying" : + "mengsan_vulnerable_shuying"); + target.markSkill(frail ? "mengsan_frail_shuying" : + "mengsan_vulnerable_shuying"); + }; + + const dealIntentDamage = async (source, target, move) => { + if (move.damage == null) return; + for (let index = 0; index < attackHitCount(move); index++) { + if (!target.isAlive() || !source.isAlive()) break; + const hit = target.damage( + outgoingAttackDamage(move, source), source); + hit.mengsanAttack_shuying = true; + hit.mengsanScriptedSkill_shuying = true; + await hit; + } + }; + + const executeFlyconidIntent = async player => { + const move = player.storage.mengsanFlyconidIntent_shuying; + if (!move) return; + player.storage.mengsanFlyconidIntent_shuying = null; + game.mengsanSetEnemyIntent_shuying(player, null); + const paid = player.storage.mengsanEnergy_shuying >= 1; + player.storage.mengsanFlyconidState_shuying = + recordFlyconidAction( + player.storage.mengsanFlyconidState_shuying || {}, + move, paid); + if (!paid) { + game.log(player, "费用不足,未发动", move.name); + return; + } + player.storage.mengsanEnergy_shuying--; + getActiveBattle()?.energyUI.get(player)?.(); + game.log(player, "消耗1费用发动", move.name); + const target = game.me; + if (!target?.isAlive() || !player.isAlive()) return; + if (isDeathBlowIntent(move)) { + await beginDeathBlow(player, target, move); + return; + } + await dealIntentDamage(player, target, move); + if (target.isAlive() && player.isAlive()) { + applyIntentDebuff(target, move); + } + }; + + const executeRaiderIntent = async player => { + const move = player.storage.mengsanRaiderIntent_shuying; + if (!move) return; + player.storage.mengsanRaiderIntent_shuying = null; + game.mengsanSetEnemyIntent_shuying(player, null); + player.storage.mengsanRaiderState_shuying = recordRaiderAction( + player.storage.mengsanRaiderState_shuying || {}); + if (player.storage.mengsanEnergy_shuying < 1) { + game.log(player, "费用不足,未发动", move.name); + return; + } + player.storage.mengsanEnergy_shuying--; + getActiveBattle()?.energyUI.get(player)?.(); + game.log(player, "消耗1费用发动", move.name); + const target = game.me; + if (!target?.isAlive() || !player.isAlive()) return; + await dealIntentDamage(player, target, move); + if (target.isAlive() && player.isAlive()) { + applyIntentDebuff(target, move); + } + if (!player.isAlive() || !getActiveBattle()?.session.active) return; + if (move.block) await player.changeHujia(move.block); + if (move.strength) { + player.storage.mengsanStrength_shuying = + (player.storage.mengsanStrength_shuying || 0) + + move.strength; + player.addSkill("mengsan_raider_strength_shuying"); + player.markSkill("mengsan_raider_strength_shuying"); + } + }; + + return { + planEnemyIntent, + executeFlyconidIntent, + executeRaiderIntent, + }; +} diff --git a/mengsan/runtime/flyconid-intent.js b/mengsan/monsters/flyconid-intent.js similarity index 100% rename from mengsan/runtime/flyconid-intent.js rename to mengsan/monsters/flyconid-intent.js diff --git a/mengsan/runtime/monster.js b/mengsan/monsters/monster.js similarity index 98% rename from mengsan/runtime/monster.js rename to mengsan/monsters/monster.js index 6e090df..068854f 100644 --- a/mengsan/runtime/monster.js +++ b/mengsan/monsters/monster.js @@ -1,4 +1,4 @@ -import { createCardData } from "./card-data.js"; +import { createCardData } from "../cards/card-data.js"; // 梦三怪物规则只作用于敌方单位;子类可覆盖构造参数或 createDeck()。 const TIER_RULES = Object.freeze({ diff --git a/mengsan/runtime/raider-intent.js b/mengsan/monsters/raider-intent.js similarity index 100% rename from mengsan/runtime/raider-intent.js rename to mengsan/monsters/raider-intent.js diff --git a/mengsan/runtime/dialogue-choice.js b/mengsan/progression/dialogue-choice.js similarity index 100% rename from mengsan/runtime/dialogue-choice.js rename to mengsan/progression/dialogue-choice.js diff --git a/mengsan/runtime/mode-storage.js b/mengsan/progression/mode-storage.js similarity index 100% rename from mengsan/runtime/mode-storage.js rename to mengsan/progression/mode-storage.js diff --git a/mengsan/runtime/reward.js b/mengsan/progression/reward.js similarity index 89% rename from mengsan/runtime/reward.js rename to mengsan/progression/reward.js index f52e202..b37dd02 100644 --- a/mengsan/runtime/reward.js +++ b/mengsan/progression/reward.js @@ -1,6 +1,6 @@ import config from "../config.js"; -import { nextRandom } from "../state.js"; -import { canAcquireCard } from "../content/card-definitions.js"; +import { nextRandom } from "./state.js"; +import { canAcquireCard } from "../cards/card-definitions.js"; export const rewardCardName = reward => reward?.card?.name || ({ card_sha: "sha", card_tao: "tao" })[reward?.effectId] || null; export const canAcquireReward = (run, reward) => { diff --git a/mengsan/state.js b/mengsan/progression/state.js similarity index 98% rename from mengsan/state.js rename to mengsan/progression/state.js index 1f6a21a..c650566 100644 --- a/mengsan/state.js +++ b/mengsan/progression/state.js @@ -1,6 +1,6 @@ -import { createStartingDeck } from "./content/card-library.js"; -import config from "./config.js"; -import { RAIDER_TRIO_ENCOUNTER, createRaiderTrioBattlePlan } from "./content/acts/act1/raider-trio.js"; +import { createStartingDeck } from "../cards/starting-deck.js"; +import config from "../config.js"; +import { RAIDER_TRIO_ENCOUNTER, createRaiderTrioBattlePlan } from "../content/acts/act1/raider-trio.js"; const hashText = text => { let value = 2166136261; diff --git a/mengsan/runtime/story.js b/mengsan/progression/story.js similarity index 100% rename from mengsan/runtime/story.js rename to mengsan/progression/story.js diff --git a/mengsan/runtime/card-library.js b/mengsan/ui/card-library.js similarity index 99% rename from mengsan/runtime/card-library.js rename to mengsan/ui/card-library.js index c3d442c..a88f57a 100644 --- a/mengsan/runtime/card-library.js +++ b/mengsan/ui/card-library.js @@ -1,5 +1,5 @@ import { lib, get, ui } from "../../../../noname.js"; -import { AFFIX_INFO } from "../content/affixes.js"; +import { AFFIX_INFO } from "../cards/affixes.js"; const suits = { spade: "♠ 黑桃", heart: "♥ 红桃", club: "♣ 梅花", diamond: "♦ 方块" }; const types = { basic: "基本牌", trick: "锦囊牌", delay: "延时锦囊", equip: "装备牌" }; diff --git a/mengsan/runtime/dialogue.js b/mengsan/ui/dialogue.js similarity index 99% rename from mengsan/runtime/dialogue.js rename to mengsan/ui/dialogue.js index 1937daf..2cc5ba6 100644 --- a/mengsan/runtime/dialogue.js +++ b/mengsan/ui/dialogue.js @@ -1,6 +1,6 @@ import { mountMenu } from "./navigation.js"; import { ui, get } from "../../../../noname.js"; -import { isDialogueLineVisible, applyDialogueChoice, applySkippedDialogueChoices } from "./dialogue-choice.js"; +import { isDialogueLineVisible, applyDialogueChoice, applySkippedDialogueChoices } from "../progression/dialogue-choice.js"; const TYPE_NAMES = { character: "角色", diff --git a/mengsan/ui/energy-display.js b/mengsan/ui/energy-display.js new file mode 100644 index 0000000..c28393d --- /dev/null +++ b/mengsan/ui/energy-display.js @@ -0,0 +1,13 @@ +export function mountEnergy(player, session, document) { + const badge = document.createElement("div"); + badge.className = "mengsan-energy-shuying"; + const refresh = () => { + const current = player.storage.mengsanEnergy_shuying; + const maximum = player.storage.mengsanMaxEnergy_shuying; + badge.textContent = `费用 ${current}/${maximum}`; + }; + player.appendChild(badge); + session.ownResource(badge, () => badge.remove()); + refresh(); + return refresh; +} diff --git a/mengsan/runtime/flow-ui.js b/mengsan/ui/flow-ui.js similarity index 100% rename from mengsan/runtime/flow-ui.js rename to mengsan/ui/flow-ui.js diff --git a/mengsan/runtime/gm-manager.js b/mengsan/ui/gm-manager.js similarity index 98% rename from mengsan/runtime/gm-manager.js rename to mengsan/ui/gm-manager.js index c0af686..e52286d 100644 --- a/mengsan/runtime/gm-manager.js +++ b/mengsan/ui/gm-manager.js @@ -1,4 +1,4 @@ -import { AFFIX_INFO } from "../content/affixes.js"; +import { AFFIX_INFO } from "../cards/affixes.js"; import { describeLibraryCard } from "./card-library.js"; const node = (tag, className, label, parent) => { diff --git a/mengsan/runtime/intent-display.js b/mengsan/ui/intent-display.js similarity index 86% rename from mengsan/runtime/intent-display.js rename to mengsan/ui/intent-display.js index e2193e6..7c65693 100644 --- a/mengsan/runtime/intent-display.js +++ b/mengsan/ui/intent-display.js @@ -1,7 +1,7 @@ // 只呈现已经选定的预测技能;不从手牌或原生技能猜测意图。 -import { attackHitCount, previewAttackDamage } from "./intent-damage.js"; -import { isDeathBlowIntent } from "./death-blow.js"; -import { isStunIntent } from "./stun-intent.js"; +import { attackHitCount, previewAttackDamage } from "../battle/intent-damage.js"; +import { isDeathBlowIntent } from "../battle/death-blow.js"; +import { isStunIntent } from "../battle/stun-intent.js"; export function attackIconForDamage(damage) { if (!Number.isInteger(damage) || damage < 0) { @@ -27,14 +27,14 @@ export function mountEnemyIntent(player, session, assetBase, document, amount.className = "mengsan-intent-amount-shuying"; const icon = document.createElement("img"); icon.className = "mengsan-intent-icon-shuying"; - icon.alt = "Attack"; + icon.alt = ""; icon.decoding = "async"; damageLine.append(icon, amount); const debuffLine = document.createElement("span"); debuffLine.className = "mengsan-intent-debuff-shuying"; const debuffIcon = document.createElement("img"); debuffIcon.className = "mengsan-intent-icon-shuying"; - debuffIcon.alt = "Debuff"; + debuffIcon.alt = ""; debuffIcon.decoding = "async"; debuffIcon.src = `${assetBase}/assets/intent/Intent_debuff.png`; const debuffAmount = document.createElement("span"); @@ -43,7 +43,7 @@ export function mountEnemyIntent(player, session, assetBase, document, blockLine.className = "mengsan-intent-block-shuying"; const blockIcon = document.createElement("img"); blockIcon.className = "mengsan-intent-icon-shuying"; - blockIcon.alt = "Block"; + blockIcon.alt = ""; blockIcon.decoding = "async"; blockIcon.src = `${assetBase}/assets/intent/Intent_defend.png`; const blockAmount = document.createElement("span"); @@ -52,7 +52,7 @@ export function mountEnemyIntent(player, session, assetBase, document, buffLine.className = "mengsan-intent-buff-shuying"; const buffIcon = document.createElement("img"); buffIcon.className = "mengsan-intent-icon-shuying"; - buffIcon.alt = "Buff"; + buffIcon.alt = ""; buffIcon.decoding = "async"; buffIcon.src = `${assetBase}/assets/intent/Intent_buff.png`; const buffAmount = document.createElement("span"); @@ -79,7 +79,6 @@ export function mountEnemyIntent(player, session, assetBase, document, amount.textContent = ""; const src = `${assetBase}/assets/intent/Intent_stun.png`; if (icon.getAttribute("src") !== src) icon.src = src; - icon.alt = "Stunned"; if (badge.getAttribute("aria-label") !== "Stunned") { badge.setAttribute("aria-label", "Stunned"); } @@ -96,9 +95,7 @@ export function mountEnemyIntent(player, session, assetBase, document, Number.isInteger(intent.stacks) && intent.stacks > 0; debuffLine.hidden = !hasDebuff; if (hasDebuff) { - const label = intent.debuff === "脆弱" ? "Frail" : - intent.debuff === "易伤" ? "Vulnerable" : "Debuff"; - const debuffText = `${label} ×${intent.stacks}`; + const debuffText = `×${intent.stacks}`; if (debuffAmount.textContent !== debuffText) { debuffAmount.textContent = debuffText; } @@ -108,7 +105,9 @@ export function mountEnemyIntent(player, session, assetBase, document, blockLine.hidden = !hasBlock; buffLine.hidden = !hasStrength; if (hasBlock) blockAmount.textContent = String(intent.block); - if (hasStrength) buffAmount.textContent = `力量 ×${intent.strength}`; + if (hasStrength) { + buffAmount.textContent = `+${intent.strength}`; + } const effects = []; if (hasDamage) { const hits = attackHitCount(intent); @@ -127,13 +126,23 @@ export function mountEnemyIntent(player, session, assetBase, document, `${assetBase}/assets/intent/Intent_death_blow.png` : `${assetBase}/assets/intent/Intent_attack_${tier}.png`; if (icon.getAttribute("src") !== src) icon.src = src; - icon.alt = deathBlow ? "Death Blow" : "Attack"; effects.push(`${deathBlow ? "Death Blow" : "Attack"}: ${damage} damage${hits > 1 ? ` × ${hits} hits` : ""}`); } - if (hasDebuff) effects.push(debuffAmount.textContent); + if (hasDebuff) { + const label = intent.debuff === "脆弱" ? "Frail" : + intent.debuff === "易伤" ? "Vulnerable" : "Debuff"; + effects.push(`${label} ×${intent.stacks}`); + } if (hasBlock) effects.push(`Block ${intent.block}`); if (hasStrength) effects.push(`Strength +${intent.strength}`); - const label = effects.join(", ") || intent.name || "Intent"; + if (!effects.length) { + damageLine.hidden = false; + amount.textContent = ""; + const src = `${assetBase}/assets/intent/Intent_unknown.png`; + if (icon.getAttribute("src") !== src) icon.src = src; + effects.push("Unknown"); + } + const label = effects.join(", "); if (badge.getAttribute("aria-label") !== label) badge.setAttribute("aria-label", label); badge.hidden = false; }; diff --git a/mengsan/map/index.js b/mengsan/ui/map/index.js similarity index 100% rename from mengsan/map/index.js rename to mengsan/ui/map/index.js diff --git a/mengsan/map/renderer.js b/mengsan/ui/map/renderer.js similarity index 98% rename from mengsan/map/renderer.js rename to mengsan/ui/map/renderer.js index b6cf5db..bd4a203 100644 --- a/mengsan/map/renderer.js +++ b/mengsan/ui/map/renderer.js @@ -1,8 +1,8 @@ -import { openCardLibrary } from "../runtime/card-library.js"; -import { askMenu, openModeSelection } from "../runtime/navigation.js"; -import { ui, get } from "../../../../noname.js"; -import config from "../config.js"; -import { getSelectableNodes } from "../state.js"; +import { openCardLibrary } from "../card-library.js"; +import { askMenu, openModeSelection } from "../navigation.js"; +import { ui, get } from "../../../../../noname.js"; +import config from "../../config.js"; +import { getSelectableNodes } from "../../progression/state.js"; // Original vector pictograms; no external image/font requests. const iconPaths = { diff --git a/mengsan/runtime/navigation.js b/mengsan/ui/navigation.js similarity index 100% rename from mengsan/runtime/navigation.js rename to mengsan/ui/navigation.js diff --git a/mengsan/runtime/reward-ui.js b/mengsan/ui/reward-ui.js similarity index 99% rename from mengsan/runtime/reward-ui.js rename to mengsan/ui/reward-ui.js index a3a9561..8665a3c 100644 --- a/mengsan/runtime/reward-ui.js +++ b/mengsan/ui/reward-ui.js @@ -1,5 +1,5 @@ import { lib, get } from "../../../../noname.js"; -import { cardCost } from "./combat-rules.js"; +import { cardCost } from "../battle/combat-rules.js"; import { chooseButtons } from "./flow-ui.js"; const element = (tag, className, text, parent) => { diff --git a/mengsan/style.css b/mengsan/ui/style.css similarity index 100% rename from mengsan/style.css rename to mengsan/ui/style.css diff --git a/tools/generate_manifest.py b/tools/generate_manifest.py index 26a4525..4bc7e21 100644 --- a/tools/generate_manifest.py +++ b/tools/generate_manifest.py @@ -12,6 +12,7 @@ EXCLUDE_DIRS = { ".gitea", ".github", ".update_tmp", + ".planning", "dist", "tools", "__pycache__",