From 623f53a1d2ccec7853b650303b5a7d328f287b1a Mon Sep 17 00:00:00 2001 From: Shu-Ying <1754798088@qq.com> Date: Mon, 5 Oct 2026 21:45:35 +0800 Subject: [PATCH] =?UTF-8?q?fixed=20#=20=E7=A7=BB=E9=99=A4=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E8=BA=AB=E4=BB=BD=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- extension.js | 2 +- mengsan/README.md | 16 +++++++ mengsan/battle/camps.js | 54 ++++++++++++++++++++++ mengsan/battle/intent-effects.js | 5 +- mengsan/mode.js | 79 +++++++++++--------------------- 5 files changed, 101 insertions(+), 55 deletions(-) create mode 100644 mengsan/battle/camps.js diff --git a/extension.js b/extension.js index ddff93a..112160d 100644 --- a/extension.js +++ b/extension.js @@ -5,7 +5,7 @@ import initShuYingMenu from "./menu.js"; export const type = "extension"; export let shuYing = new Object(); -const shuYingLocalVersion = "2.0.2.3-rc.1"; +const shuYingLocalVersion = "2.0.2.3-rc.2"; shuYing.name = "术樱包"; shuYing.editable = false; diff --git a/mengsan/README.md b/mengsan/README.md index e00a9ea..b6065b1 100644 --- a/mengsan/README.md +++ b/mengsan/README.md @@ -2,6 +2,22 @@ `register.js` 是扩展加载入口,`mode.js` 负责组装模式、注册技能并连接各领域模块,`config.js` 提供模式配置。发布器和外部扩展应继续从 `register.js` 加载,避免依赖内部文件路径。 +梦三独立组装模式,不加载或继承身份模式。敌我关系统一由 +`battle/camps.js` 读取角色的 `storage.mengsanCamp_shuying`:主角、支援和 +羁绊助战为 `ally`,怪物及敌方援军为 `enemy`。`side` 与梦三专属的 +`identity` 标记只用于核心及界面兼容,不赋予主公、忠臣或反贼规则。 + +技能应使用 `isFriendOf` / `isEnemyOf`、`getFriends` / `getEnemies` 或 +`get.attitude` 判断敌我。基础态度为同阵营及自身 `+8`、对立阵营 `-8`, +未设置有效阵营的其他对象为 `0`;剧情切换阵营后按新阵营判断。 +`get.effect`、`get.effect_use`、`get.damageEffect`、`get.recoverEffect` 和 +牌价值、技能效果修正继续使用核心算法,并通过上述态度计算行动收益。 +有益效果不一律视为正收益,伤害也不一律视为负收益,仍遵循技能自己的 AI 配置。 + +梦三不启用主公技,初始技能及动态授予技能均按此限制处理。阵营始终公开, +出牌或技能的 `logAi` / `addExpose` 调用不推断隐藏身份;击杀不附加身份奖惩, +胜负与奖励沿用梦三关卡和结算流程。 + | 目录 | 职责 | | --- | --- | | `battle/` | 战斗会话、回合、结算、费用与通用战斗效果 | diff --git a/mengsan/battle/camps.js b/mengsan/battle/camps.js new file mode 100644 index 0000000..1299a40 --- /dev/null +++ b/mengsan/battle/camps.js @@ -0,0 +1,54 @@ +// 阵营是梦三敌我关系的唯一来源;side/identity 仅供核心及界面兼容。 +export const battleCamp = player => { + const camp = player?.storage?.mengsanCamp_shuying; + return camp === "ally" || camp === "enemy" ? camp : null; +}; + +export const campFriend = (from, to) => Boolean(from && to && + (from === to || (battleCamp(from) !== null && battleCamp(from) === battleCamp(to)))); + +export const campEnemy = (from, to) => Boolean(from && to && from !== to && + battleCamp(from) !== null && battleCamp(to) !== null && + battleCamp(from) !== battleCamp(to)); + +// get.attitude 会继续处理混乱、技能态度修正;get.effect 等沿用核心收益计算。 +export const campAttitude = (from, to) => + campFriend(from, to) ? 8 : campEnemy(from, to) ? -8 : 0; + +export function setBattleCamp(player, camp) { + if (camp !== "ally" && camp !== "enemy") throw new Error("梦三战斗阵营无效:" + camp); + player.storage.mengsanCamp_shuying = camp; + player.side = camp === "enemy"; + player.identity = `mengsan_${camp}_shuying`; + player.isZhu = false; + player.setIdentity(player.identity); + player.identityShown = true; + player.ai.shown = 1; + player.classList.add("mengsan-hide-identity-shuying"); +} + +export function createCampPlayerMethods(game) { + return { + isFriendOf(player) { return campFriend(this, player); }, + isEnemyOf(player) { return campEnemy(this, player); }, + getEnemies(filter, includeDie) { + return game[includeDie ? "filterPlayer2" : "filterPlayer"](player => + campEnemy(this, player) && (typeof filter !== "function" || filter(player))); + }, + getFriends(filter, includeDie) { + const includeSelf = filter === true; + return game[includeDie ? "filterPlayer2" : "filterPlayer"](player => + (player !== this || includeSelf) && campFriend(this, player) && + (typeof filter !== "function" || filter(player))); + }, + // 阵营始终公开,不根据行动收益推断隐藏身份或积累暴露值。 + logAi() { this.ai.shown = 1; }, + addExpose() { return this; }, + showIdentity() { + const camp = battleCamp(this); + if (camp) setBattleCamp(this, camp); + }, + isZhu2() { return false; }, + hasZhuSkill() { return false; }, + }; +} diff --git a/mengsan/battle/intent-effects.js b/mengsan/battle/intent-effects.js index 53d7a0c..091e45a 100644 --- a/mengsan/battle/intent-effects.js +++ b/mengsan/battle/intent-effects.js @@ -2,11 +2,10 @@ import { applyMengsanDebuff } from "../monsters/artifact-status.js"; import { applyTangled } from "../monsters/vine-tangled.js"; import { attackHitCount, outgoingAttackDamage } from "./intent-damage.js"; import { beginDeathBlow, isDeathBlowIntent } from "./death-blow.js"; +import { campEnemy } from "./camps.js"; export const getIntentHostiles = (game, source) => - game.players.filter(target => target !== source && target.isAlive() && - target.storage?.mengsanCamp_shuying !== - source.storage?.mengsanCamp_shuying); + game.players.filter(target => target.isAlive() && campEnemy(source, target)); export function createIntentExecutor(game, getActiveBattle) { const active = source => source.isAlive() && diff --git a/mengsan/mode.js b/mengsan/mode.js index 98cbbf2..6d8f122 100644 --- a/mengsan/mode.js +++ b/mengsan/mode.js @@ -15,6 +15,7 @@ import { ironcladBlocksDraw, ironcladPreservesBlock, afterIroncladDraw, beforeIr import { afterSharedDraw, beforeSharedBlock, sharedStrengthPenalty } from "./cards/shared-hooks.js"; import { hasCardAffix } from "./cards/intrinsic-affixes.js"; import { createBattleEnergy } from "./cards/battle-energy.js"; +import { setBattleCamp, createCampPlayerMethods, campAttitude } from "./battle/camps.js"; import { createRandomHandExhauster } from "./cards/random-hand-exhaust.js"; import { createBattleCardCopier } from "./cards/battle-card-copy.js"; import { createBattleCardReclaimer } from "./cards/battle-card-reclaim.js"; @@ -335,15 +336,6 @@ const shuffleBattlePile = (run, cards) => { } }; -const setBattleCamp = (player, camp) => { - player.storage.mengsanCamp_shuying = camp; - player.side = camp === "enemy"; - player.identity = player === game.me ? "zhu" : camp === "ally" ? "zhong" : "fan"; - player.setIdentity(player.identity); - player.identityShown = true; - player.ai.shown = 1; - player.classList.add("mengsan-hide-identity-shuying"); -}; const initBattleUnit = (player, spec) => { // 只在实际入场且未指定生命时,用征程随机源抽取各怪物的普通生命范围;资料列表不消耗随机数。 const monsterSpec = spec.camp === "enemy" && spec.hp == null ? @@ -612,7 +604,6 @@ const prepareBattle = async (run, node, encounter, session, resources) => { log: message => game.log(message), }); if (!session.active) return; - game.zhu = me; if (run.player.maxHp == null) { run.player.maxHp = me.maxHp; run.player.hp = me.hp; @@ -723,7 +714,6 @@ const registerPlayers = (session, players) => { const index = list.indexOf(player); if (index >= 0) list.splice(index, 1); } if (game.me === player) game.me = null; - if (game.zhu === player) game.zhu = null; player.remove(); }); } @@ -893,11 +883,10 @@ const startJourney = async () => { await routeRun(run); }; -const createMode = identityMode => { - const identityElement = identityMode.element || {}; - const identityPlayer = identityElement.player || {}; - const nativeChangeHujia = identityPlayer.changeHujia || lib.element.Player.prototype.changeHujia; - const nativeChangeHp = identityPlayer.changeHp || lib.element.Player.prototype.changeHp; +const createMode = () => { + const nativeChangeHujia = lib.element.Player.prototype.changeHujia; + const nativeChangeHp = lib.element.Player.prototype.changeHp; + const nativeAddSkill = lib.element.Player.prototype.addSkill; const cardContext = { game, get, lib, random:nextRandom, status:_status, getBattle:()=>activeBattle, copyToDiscard: createBattleCardCopier(game, () => activeBattle), @@ -921,10 +910,9 @@ const createMode = identityMode => { const modeCards = createMengsanCards({cardPacks,isBattleActive:cardContext.isBattleActive}); return { - ...identityMode, name: MODE_ID, - character: { ...(identityMode.character || {}), ...createScenarioCharacters(lib.element.Character) }, - card: { ...(identityMode.card || {}), ...modeCards.card }, + character: createScenarioCharacters(lib.element.Character), + card: modeCards.card, connect: false, start: [ async () => { @@ -933,9 +921,14 @@ const createMode = identityMode => { }, ], element: { - ...identityElement, player: { - ...identityPlayer, + ...createCampPlayerMethods(game), + addSkill(skill, ...args) { + // 初始武将技能由核心过滤;同时阻止奖励、剧情等动态授予主公技。 + if (Array.isArray(skill)) skill = skill.filter(name => !lib.skill[name]?.zhuSkill); + else if (lib.skill[skill]?.zhuSkill) return; + return nativeAddSkill.call(this, skill, ...args); + }, async dieAfter() { // 原生死亡已成立;在战斗结算关闭会话前落实斩杀的永久收益。 if(this.isDead())await cardPacks.onDeath(_status.event); @@ -948,11 +941,7 @@ const createMode = identityMode => { message => game.log(message)); game.checkResult(); }, - dieAfter2() {}, // Identity-mode kill rewards/loyalist penalties do not apply here. - isFriendOf(player) { return this === player || (this.storage.mengsanCamp_shuying === player?.storage?.mengsanCamp_shuying); }, - isEnemyOf(player) { return Boolean(player && this.storage.mengsanCamp_shuying !== player.storage?.mengsanCamp_shuying); }, - getEnemies(filter, includeDie) { return game[includeDie ? "filterPlayer2" : "filterPlayer"](p => this.isEnemyOf(p) && (!filter || filter(p))); }, - getFriends(filter, includeDie) { const self = filter === true; return game[includeDie ? "filterPlayer2" : "filterPlayer"](p => (p !== this || self) && this.isFriendOf(p) && (typeof filter !== "function" || filter(p))); }, + dieAfter2() {}, // 击杀不额外摸牌或弃牌,奖励由梦三结算流程处理。 changeHp(num, popup) { const next = nativeChangeHp.call(this, num, popup); if (activeBattle?.session.active) capInkletHpLoss(this, next); @@ -985,7 +974,12 @@ const createMode = identityMode => { }, }, game: { - ...(identityMode.game || {}), + getVideoName() { + const me = game.me; + const names = [me?.name1 || me?.name, me?.name2].filter(Boolean); + return [names.map(name => get.translation(name)).join("/") || "梦三", + "梦三" + (_status.mengsanEncounter_shuying?.name ? " · " + _status.mengsanEncounter_shuying.name : "")]; + }, mengsanSetEnemyIntent_shuying(player, intent) { const update = activeBattle?.intentUI.get(player); if (!update) return false; @@ -1239,11 +1233,10 @@ const createMode = identityMode => { }, }, get: { - ...(identityMode.get || {}), - rawAttitude(from, to) { return from === to || from.storage.mengsanCamp_shuying === to.storage.mengsanCamp_shuying ? 8 : -8; }, + rawAttitude: campAttitude, + realAttitude: campAttitude, }, skill: { - ...(identityMode.skill || {}), ...cardPacks.skills, ...createRelicSkills(() => activeBattle, () => game.me), ...createRelicCombatSkills(() => activeBattle, () => game.me), @@ -1829,7 +1822,10 @@ const createMode = identityMode => { }, }, translate: { - ...(identityMode.translate || {}), + mengsan_ally_shuying: "友", + mengsan_ally_shuying2: "友方", + mengsan_enemy_shuying: "敌", + mengsan_enemy_shuying2: "敌方", ...modeCards.translate, ...scenarioTranslations, mengsan_draw_shuying: "梦三牌组", @@ -1912,26 +1908,7 @@ export async function createMengsanMode() { const style = lib.init.css(`${STYLE_PATH}/ui`, "style", resolve); style.addEventListener("error", resolve, { once: true }); }); - const identityMode = await new Promise((resolve, reject) => { - let received = false; - const receiveMode = mode => { - if (!mode || typeof mode !== "object" || mode.name !== "identity") { - reject(new Error("梦三启动失败:未取得有效的身份模式,请检查游戏核心与身份模式文件。")); - return; - } - received = true; - resolve(mode); - }; - // 部分核心仅通过回调交付模式;新版也可能返回 Promise。 - const loading = game.loadModeAsync("identity", receiveMode, reject); - if (loading && typeof loading.then === "function") { - Promise.resolve(loading).then(mode => { - // 回调版的 Promise 可能返回 undefined,此时模式已由回调交付。 - if (!received) receiveMode(mode); - }, reject); - } - }); - const mode = createMode(identityMode); + const mode = createMode(); mode.splash = "ext:术樱包/pve/images/tianshu.jpg"; return mode; }