mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-29 03:00:01 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
332a3b2737 | ||
|
|
3a3d547ccd | ||
|
|
036f929862 | ||
|
|
3ac3b5e52a |
Vendored
+313
-313
File diff suppressed because it is too large
Load Diff
Vendored
+1092
-1082
File diff suppressed because it is too large
Load Diff
Vendored
+2
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "2.0.1.1-rc.1",
|
||||
"version": "2.0.2.3-rc.2",
|
||||
"module_id": "mengsan",
|
||||
"snapshot": "legacy",
|
||||
"files": [
|
||||
@@ -309,6 +309,7 @@
|
||||
"mengsan/battle/battle-resources.js",
|
||||
"mengsan/battle/battle-session.js",
|
||||
"mengsan/battle/battle-settlement.js",
|
||||
"mengsan/battle/camps.js",
|
||||
"mengsan/battle/combat-rules.js",
|
||||
"mengsan/battle/death-blow.js",
|
||||
"mengsan/battle/engine-session.js",
|
||||
|
||||
+103
@@ -1096,6 +1096,109 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 更新设置:沿用选择窗口材质,两个选项与取消/确认按钮适配窄屏。 */
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] {
|
||||
--setting-muted: #d1c9bc;
|
||||
--setting-focus: #ffe2a0;
|
||||
padding: max(12px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right))
|
||||
max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left)) !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog {
|
||||
width: min(600px, 100%) !important;
|
||||
max-height: calc(100vh - 32px) !important;
|
||||
max-height: calc(100dvh - max(12px, env(safe-area-inset-top)) - max(12px, env(safe-area-inset-bottom))) !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-title,
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-intro,
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-controls {
|
||||
flex: none !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-intro,
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-tag,
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-description {
|
||||
color: var(--setting-muted) !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-options {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
|
||||
min-height: 0 !important;
|
||||
gap: 12px !important;
|
||||
padding: 18px !important;
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-card {
|
||||
min-height: 170px !important;
|
||||
padding: 38px 18px 18px !important;
|
||||
transform: none !important;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-order {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-name {
|
||||
margin-top: 0 !important;
|
||||
font-size: 25px !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-description {
|
||||
margin-top: 12px !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-controls {
|
||||
gap: 12px !important;
|
||||
padding: 0 18px 18px !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-confirm {
|
||||
flex: 1 !important;
|
||||
min-width: 0 !important;
|
||||
min-height: 48px !important;
|
||||
height: auto !important;
|
||||
padding: 10px 8px !important;
|
||||
font-size: 16px !important;
|
||||
line-height: 1.4 !important;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-cancel {
|
||||
background: rgba(255, 255, 255, 0.04) !important;
|
||||
border-color: rgba(232, 190, 99, 0.4) !important;
|
||||
color: var(--setting-muted) !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] button:focus-visible {
|
||||
outline: 2px solid var(--setting-focus) !important;
|
||||
outline-offset: 2px !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] button:active:not(:disabled) {
|
||||
opacity: 0.85 !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-confirm:disabled {
|
||||
opacity: 0.5 !important;
|
||||
cursor: default !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 520px) {
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-options {
|
||||
grid-template-columns: minmax(0, 1fr) !important;
|
||||
padding: 12px !important;
|
||||
}
|
||||
|
||||
.shuYing-option-dialog-mask[data-variant="settings"] .shuYing-option-dialog-card {
|
||||
min-height: 140px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 天书对局羁绊入口与概览:不依赖十周年 UI 节点,原版和扩展 UI 共用。 */
|
||||
.shuYing-bond-overview-button {
|
||||
all: unset !important;
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import initShuYingMenu from "./menu.js";
|
||||
|
||||
export const type = "extension";
|
||||
export let shuYing = new Object();
|
||||
const shuYingLocalVersion = "2.0.2.3";
|
||||
const shuYingLocalVersion = "2.0.2.3-rc.2";
|
||||
|
||||
shuYing.name = "术樱包";
|
||||
shuYing.editable = false;
|
||||
|
||||
+44
-4
@@ -225,7 +225,8 @@ export default function initFunction(lib, game, ui, get, ai, _status, shuYing =
|
||||
};
|
||||
|
||||
// 创建可复用的对局单选界面;单击选中卡片,确认后返回对应键值。
|
||||
shuYing.chooseSingleOptionDialog = ({ title = "请选择", intro = "", options = [], defaultKey = null } = {}) => {
|
||||
shuYing.chooseSingleOptionDialog = ({ title = "请选择", intro = "", options = [], defaultKey = null,
|
||||
currentKey = null, cancelable = false, variant = "default", confirmText = "确定" } = {}) => {
|
||||
return new Promise(resolve => {
|
||||
const items = options.map(option => typeof option == "string" ? { key: option, name: option } : option)
|
||||
.filter(option => option?.key);
|
||||
@@ -240,18 +241,26 @@ export default function initFunction(lib, game, ui, get, ai, _status, shuYing =
|
||||
if (parent) parent.appendChild(node);
|
||||
return node;
|
||||
};
|
||||
const previousFocus = document.activeElement;
|
||||
const mask = createNode("div", "shuYing-option-dialog-mask", null, document.body);
|
||||
mask.dataset.variant = variant;
|
||||
const dialog = createNode("div", "shuYing-option-dialog", null, mask);
|
||||
dialog.setAttribute("role", "dialog");
|
||||
dialog.setAttribute("aria-modal", "true");
|
||||
dialog.setAttribute("aria-label", title);
|
||||
createNode("div", "shuYing-option-dialog-title", title, dialog);
|
||||
if (intro) createNode("div", "shuYing-option-dialog-intro", intro, dialog);
|
||||
const optionArea = createNode("div", "shuYing-option-dialog-options", null, dialog);
|
||||
const controls = createNode("div", "shuYing-option-dialog-controls", null, dialog);
|
||||
let closed = false;
|
||||
let onKeydown = null;
|
||||
let selectedKey = defaultKey && items.some(item => item.key == defaultKey) ? defaultKey : items[0].key;
|
||||
const finish = key => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
if (onKeydown) document.removeEventListener("keydown", onKeydown, true);
|
||||
mask.remove();
|
||||
if (cancelable && previousFocus?.isConnected) previousFocus.focus?.({ preventScroll: true });
|
||||
resolve(key);
|
||||
};
|
||||
const refreshSelection = () => {
|
||||
@@ -260,6 +269,7 @@ export default function initFunction(lib, game, ui, get, ai, _status, shuYing =
|
||||
card.classList.toggle("selected", selected);
|
||||
card.setAttribute("aria-pressed", String(selected));
|
||||
});
|
||||
confirm.disabled = currentKey != null && selectedKey == currentKey;
|
||||
};
|
||||
items.forEach((option, index) => {
|
||||
const card = createNode("button", "shuYing-option-dialog-card", null, optionArea);
|
||||
@@ -276,14 +286,44 @@ export default function initFunction(lib, game, ui, get, ai, _status, shuYing =
|
||||
refreshSelection();
|
||||
});
|
||||
});
|
||||
const confirm = createNode("button", "shuYing-option-dialog-confirm", "确定", controls);
|
||||
if (cancelable) {
|
||||
const cancel = createNode("button", "shuYing-option-dialog-confirm shuYing-option-dialog-cancel", "取消", controls);
|
||||
cancel.type = "button";
|
||||
cancel.addEventListener("click", event => { event.stopPropagation(); finish(null); });
|
||||
}
|
||||
const confirm = createNode("button", "shuYing-option-dialog-confirm", confirmText, controls);
|
||||
confirm.type = "button";
|
||||
confirm.addEventListener("click", event => {
|
||||
event.stopPropagation();
|
||||
finish(selectedKey);
|
||||
if (!confirm.disabled) finish(selectedKey);
|
||||
});
|
||||
mask.addEventListener("click", event => {
|
||||
event.stopPropagation();
|
||||
if (cancelable && event.target == mask) finish(null);
|
||||
});
|
||||
mask.addEventListener("click", event => event.stopPropagation());
|
||||
dialog.addEventListener("click", event => event.stopPropagation());
|
||||
if (cancelable) {
|
||||
dialog.addEventListener("keydown", event => event.stopPropagation());
|
||||
onKeydown = event => {
|
||||
if (event.key == "Escape") {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
finish(null);
|
||||
}
|
||||
else if (event.key == "Tab") {
|
||||
const buttons = Array.from(dialog.querySelectorAll("button:not(:disabled)"));
|
||||
const first = buttons[0], last = buttons[buttons.length - 1];
|
||||
if (!dialog.contains(document.activeElement)
|
||||
|| event.shiftKey && document.activeElement == first
|
||||
|| !event.shiftKey && document.activeElement == last) {
|
||||
event.preventDefault();
|
||||
(event.shiftKey ? last : first)?.focus();
|
||||
}
|
||||
event.stopPropagation();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", onKeydown, true);
|
||||
}
|
||||
refreshSelection();
|
||||
const initial = Array.from(optionArea.querySelectorAll(".shuYing-option-dialog-card")).find(node => node.dataset.key == selectedKey)
|
||||
|| optionArea.querySelector(".shuYing-option-dialog-card");
|
||||
|
||||
@@ -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/` | 战斗会话、回合、结算、费用与通用战斗效果 |
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- 批量优化只处理 `mengsan_*.png` 运行时卡牌,参考原件如 `wound-original.png` 不参与。
|
||||
- 画风:泛黄宣纸、黑褐水墨、飞白与淡赭色、中国古代人物或兵器;根据卡牌效果独立安排竖幅构图。拆包图只作题材参考,不修改拆包原件。
|
||||
- 立绘不绘制名称、费用、花色、强化角标或边框。左上花色点数、左侧中段费用、右下名称由游戏原生节点和梦三样式叠加;左侧信息栏和右下名称需留空间。
|
||||
- 卡牌定义使用 `fullimage: true` 与明确的 `image` 路径,不使用横图 `fullskin`。`mengsan_ic_`、`mengsan_colorless_`、`mengsan_event_`、`mengsan_status_`、`mengsan_curse_` 已按前缀接入 `ui/hand/style.css` 的本地完整卡面选择器;其他命名的新增卡牌须单独接入,长牌名同时加入长名称窄屏规则。保持 `cover`,不使用 `100% 100%` 拉伸。
|
||||
- 卡牌定义使用 `fullimage: true` 与明确的 `image` 路径,不使用横图 `fullskin`。手牌叠层按实际注册定义的 `fullimage: true` 及 `ext:术樱包/mengsan/assets/cards/牌名.png` 自动设置 `data-mengsan-local-art="full"`;四字及以上的翻译名称自动标记为长名,常规和窄屏样式统一,不再维护逐牌 CSS 名单。保持 `cover`,不使用 `100% 100%` 拉伸。
|
||||
- 费用和强化角标继续使用现有 `data-*` 与伪元素,避免向卡牌子节点写入额外文字而触发十周年 UI 扫描。
|
||||
- 不改变卡牌费用、效果、强化、奖励与存档数据;不修改全局标准/军争牌或十周年 UI。
|
||||
- 接入后检查完整立绘、花色/费用/名称、扇形叠牌、悬停放大和窄屏。文件检查、隔离行为测试及预览不代表实机验收。
|
||||
|
||||
@@ -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; },
|
||||
};
|
||||
}
|
||||
@@ -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() &&
|
||||
|
||||
+28
-33
@@ -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,8 +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 game.loadModeAsync("identity");
|
||||
const mode = createMode(identityMode);
|
||||
const mode = createMode();
|
||||
mode.splash = "ext:术樱包/pve/images/tianshu.jpg";
|
||||
return mode;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import { createHandInput } from "./input.js";
|
||||
export async function mountHandUI(player, session, env) {
|
||||
const { ui, document, window, cardCost, styleURL } = env;
|
||||
const adapter = createHandAdapter(player, ui);
|
||||
const overlays = createHandOverlays(document, card => cardCost(card, player));
|
||||
const overlays = createHandOverlays(document, card => cardCost(card, player), {
|
||||
definitions: env.lib?.card, translations: env.lib?.translate,
|
||||
});
|
||||
const fan = createHandFan(adapter, env);
|
||||
let input = null;
|
||||
const restores = [];
|
||||
|
||||
@@ -2,13 +2,30 @@ import { cardUpgradeLevel } from "../../cards/upgrades.js";
|
||||
import { isXCostCard } from "../../battle/combat-rules.js";
|
||||
import { applyCardRarity } from "../../cards/rarity.js";
|
||||
|
||||
export function createHandOverlays(document, cardCost) {
|
||||
export function createHandOverlays(document, cardCost, { definitions, translations } = {}) {
|
||||
const entries = new Map();
|
||||
const setData = (node, key, value) => {
|
||||
if (node.dataset[key] !== value) node.dataset[key] = value;
|
||||
};
|
||||
function refresh(card) {
|
||||
if (!card.name) return;
|
||||
// Opt in only the mode's registered complete illustrations; native card skins stay native.
|
||||
// Re-evaluate after card.init/transformation, without adding child text for Decade UI to scan.
|
||||
const definition = definitions?.[card.name];
|
||||
const localArt = definition?.fullimage === true &&
|
||||
definition.image === `ext:术樱包/mengsan/assets/cards/${card.name}.png`;
|
||||
if (localArt) {
|
||||
setData(card, "mengsanLocalArt", "full");
|
||||
const title = translations?.[card.name];
|
||||
if (typeof title === "string" && Array.from(title).length >= 4) {
|
||||
setData(card, "mengsanLongName", "true");
|
||||
} else {
|
||||
delete card.dataset.mengsanLongName;
|
||||
}
|
||||
} else {
|
||||
delete card.dataset.mengsanLocalArt;
|
||||
delete card.dataset.mengsanLongName;
|
||||
}
|
||||
let entry = entries.get(card);
|
||||
if (!entry) {
|
||||
const cost = document.createElement("span");
|
||||
@@ -58,6 +75,8 @@ export function createHandOverlays(document, cardCost) {
|
||||
entry.upgrade.remove();
|
||||
entry.border?.remove();
|
||||
entry.rarity.remove();
|
||||
delete card.dataset.mengsanLocalArt;
|
||||
delete card.dataset.mengsanLongName;
|
||||
delete card.dataset.mengsanRarity;
|
||||
delete card.dataset.mengsanRarityLabel;
|
||||
for (const key of ["accent", "dark", "ink", "metal"]) card.style.removeProperty(`--ms-rarity-${key}`);
|
||||
|
||||
+55
-138
@@ -187,12 +187,12 @@ body.mengsan-hand-ui-shuying :is(
|
||||
}
|
||||
}
|
||||
|
||||
/* 梦三本地竖幅立绘:刘备及四类共享牌统一使用完整卡面;只协调梦三手牌里的原生显示节点。 */
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name^="mengsan_ic_"], [data-card-name^="mengsan_colorless_"], [data-card-name^="mengsan_event_"], [data-card-name^="mengsan_status_"], [data-card-name^="mengsan_curse_"], [data-card-name="mengsan_zhongsha"], [data-card-name="mengsan_fangyu"], [data-card-name="mengsan_fennu"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"], [data-card-name="mengsan_wuzhuang"], [data-card-name="mengsan_zhanli"], [data-card-name="mengsan_xueqiang"], [data-card-name="mengsan_qiling"], [data-card-name="mengsan_yuanhen"], [data-card-name="mengsan_baozou"], [data-card-name="mengsan_xuanfengzhan"], [data-card-name="mengsan_dazed_shuying"], [data-card-name="mengsan_slimed_shuying"], [data-card-name="mengsan_infection_shuying"], [data-card-name="mengsan_wound_shuying"]):not(.infohidden):not(.infoflip) {
|
||||
/* 按实际卡牌定义标记本地完整立绘,不再维护牌名清单;只协调梦三手牌原生节点。 */
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:not(.infohidden):not(.infoflip) {
|
||||
background-size: cover !important;
|
||||
background-position: center !important;
|
||||
}
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name^="mengsan_ic_"], [data-card-name^="mengsan_colorless_"], [data-card-name^="mengsan_event_"], [data-card-name^="mengsan_status_"], [data-card-name^="mengsan_curse_"], [data-card-name="mengsan_zhongsha"], [data-card-name="mengsan_fangyu"], [data-card-name="mengsan_fennu"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"], [data-card-name="mengsan_wuzhuang"], [data-card-name="mengsan_zhanli"], [data-card-name="mengsan_xueqiang"], [data-card-name="mengsan_qiling"], [data-card-name="mengsan_yuanhen"], [data-card-name="mengsan_baozou"], [data-card-name="mengsan_xuanfengzhan"], [data-card-name="mengsan_dazed_shuying"], [data-card-name="mengsan_slimed_shuying"], [data-card-name="mengsan_infection_shuying"], [data-card-name="mengsan_wound_shuying"]):not(.infohidden):not(.infoflip) > :is(.name, .top-name) {
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:not(.infohidden):not(.infoflip) > :is(.name, .top-name) {
|
||||
display: block !important;
|
||||
top: auto !important;
|
||||
bottom: 8px !important;
|
||||
@@ -214,14 +214,14 @@ body.mengsan-hand-ui-shuying :is(
|
||||
z-index: 7 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name^="mengsan_ic_"], [data-card-name^="mengsan_colorless_"], [data-card-name^="mengsan_event_"], [data-card-name^="mengsan_status_"], [data-card-name^="mengsan_curse_"], [data-card-name="mengsan_zhongsha"], [data-card-name="mengsan_fangyu"], [data-card-name="mengsan_fennu"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"], [data-card-name="mengsan_wuzhuang"], [data-card-name="mengsan_zhanli"], [data-card-name="mengsan_xueqiang"], [data-card-name="mengsan_qiling"], [data-card-name="mengsan_yuanhen"], [data-card-name="mengsan_baozou"], [data-card-name="mengsan_xuanfengzhan"], [data-card-name="mengsan_dazed_shuying"], [data-card-name="mengsan_slimed_shuying"], [data-card-name="mengsan_infection_shuying"], [data-card-name="mengsan_wound_shuying"]):not(.infohidden):not(.infoflip) > :is(.name, .top-name) br {
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:not(.infohidden):not(.infoflip) > :is(.name, .top-name) br {
|
||||
display: none;
|
||||
}
|
||||
/* 十周年同时保留竖排名称与 top-name 时,只显示后者。 */
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name^="mengsan_ic_"], [data-card-name^="mengsan_colorless_"], [data-card-name^="mengsan_event_"], [data-card-name^="mengsan_status_"], [data-card-name^="mengsan_curse_"], [data-card-name="mengsan_zhongsha"], [data-card-name="mengsan_fangyu"], [data-card-name="mengsan_fennu"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"], [data-card-name="mengsan_wuzhuang"], [data-card-name="mengsan_zhanli"], [data-card-name="mengsan_xueqiang"], [data-card-name="mengsan_qiling"], [data-card-name="mengsan_yuanhen"], [data-card-name="mengsan_baozou"], [data-card-name="mengsan_xuanfengzhan"], [data-card-name="mengsan_dazed_shuying"], [data-card-name="mengsan_slimed_shuying"], [data-card-name="mengsan_infection_shuying"], [data-card-name="mengsan_wound_shuying"]):not(.infohidden):not(.infoflip):has(> .top-name) > .name {
|
||||
/* 十周年名称有文字时优先使用;空节点不能遮掉原生名称。 */
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:not(.infohidden):not(.infoflip):has(> .top-name:not(:empty)) > .name {
|
||||
display: none !important;
|
||||
}
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name^="mengsan_ic_"], [data-card-name^="mengsan_colorless_"], [data-card-name^="mengsan_event_"], [data-card-name^="mengsan_status_"], [data-card-name^="mengsan_curse_"], [data-card-name="mengsan_zhongsha"], [data-card-name="mengsan_fangyu"], [data-card-name="mengsan_fennu"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"], [data-card-name="mengsan_wuzhuang"], [data-card-name="mengsan_zhanli"], [data-card-name="mengsan_xueqiang"], [data-card-name="mengsan_qiling"], [data-card-name="mengsan_yuanhen"], [data-card-name="mengsan_baozou"], [data-card-name="mengsan_xuanfengzhan"], [data-card-name="mengsan_dazed_shuying"], [data-card-name="mengsan_slimed_shuying"], [data-card-name="mengsan_infection_shuying"], [data-card-name="mengsan_wound_shuying"]):not(.infohidden):not(.infoflip) > :is(.info:not(.gaintag), .suit-num) {
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:not(.infohidden):not(.infoflip) > :is(.info:not(.gaintag), .suit-num) {
|
||||
top: 6px !important;
|
||||
left: 6px !important;
|
||||
right: auto !important;
|
||||
@@ -241,17 +241,17 @@ body.mengsan-hand-ui-shuying :is(
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name^="mengsan_ic_"], [data-card-name^="mengsan_colorless_"], [data-card-name^="mengsan_event_"], [data-card-name^="mengsan_status_"], [data-card-name^="mengsan_curse_"], [data-card-name="mengsan_zhongsha"], [data-card-name="mengsan_fangyu"], [data-card-name="mengsan_fennu"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"], [data-card-name="mengsan_wuzhuang"], [data-card-name="mengsan_zhanli"], [data-card-name="mengsan_xueqiang"], [data-card-name="mengsan_qiling"], [data-card-name="mengsan_yuanhen"], [data-card-name="mengsan_baozou"], [data-card-name="mengsan_xuanfengzhan"], [data-card-name="mengsan_dazed_shuying"], [data-card-name="mengsan_slimed_shuying"], [data-card-name="mengsan_infection_shuying"], [data-card-name="mengsan_wound_shuying"]) > .image,
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name^="mengsan_ic_"], [data-card-name^="mengsan_colorless_"], [data-card-name^="mengsan_event_"], [data-card-name^="mengsan_status_"], [data-card-name^="mengsan_curse_"], [data-card-name="mengsan_zhongsha"], [data-card-name="mengsan_fangyu"], [data-card-name="mengsan_fennu"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"], [data-card-name="mengsan_wuzhuang"], [data-card-name="mengsan_zhanli"], [data-card-name="mengsan_xueqiang"], [data-card-name="mengsan_qiling"], [data-card-name="mengsan_yuanhen"], [data-card-name="mengsan_baozou"], [data-card-name="mengsan_xuanfengzhan"], [data-card-name="mengsan_dazed_shuying"], [data-card-name="mengsan_slimed_shuying"], [data-card-name="mengsan_infection_shuying"], [data-card-name="mengsan_wound_shuying"]) > .info:empty {
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"] > .image,
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"] > .info:empty {
|
||||
display: none !important;
|
||||
}
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name^="mengsan_ic_"], [data-card-name^="mengsan_colorless_"], [data-card-name^="mengsan_event_"], [data-card-name^="mengsan_status_"], [data-card-name^="mengsan_curse_"], [data-card-name="mengsan_zhongsha"], [data-card-name="mengsan_fangyu"], [data-card-name="mengsan_fennu"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"], [data-card-name="mengsan_wuzhuang"], [data-card-name="mengsan_zhanli"], [data-card-name="mengsan_xueqiang"], [data-card-name="mengsan_qiling"], [data-card-name="mengsan_yuanhen"], [data-card-name="mengsan_baozou"], [data-card-name="mengsan_xuanfengzhan"], [data-card-name="mengsan_dazed_shuying"], [data-card-name="mengsan_slimed_shuying"], [data-card-name="mengsan_infection_shuying"], [data-card-name="mengsan_wound_shuying"]):is([data-suit="heart"], [data-suit="diamond"]):not(.infohidden):not(.infoflip) > :is(.info:not(.gaintag), .suit-num) {
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:is([data-suit="heart"], [data-suit="diamond"]):not(.infohidden):not(.infoflip) > :is(.info:not(.gaintag), .suit-num) {
|
||||
color: #a52219 !important;
|
||||
}
|
||||
|
||||
/* 强化卡牌的完整卡面样式优先级更高,窄屏同样为角标留出空隙。 */
|
||||
@media (max-width: 640px), (max-height: 500px) {
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name^="mengsan_ic_"], [data-card-name^="mengsan_colorless_"], [data-card-name^="mengsan_event_"], [data-card-name^="mengsan_status_"], [data-card-name^="mengsan_curse_"], [data-card-name="mengsan_zhongsha"], [data-card-name="mengsan_fangyu"], [data-card-name="mengsan_fennu"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"], [data-card-name="mengsan_wuzhuang"], [data-card-name="mengsan_zhanli"], [data-card-name="mengsan_xueqiang"], [data-card-name="mengsan_qiling"], [data-card-name="mengsan_yuanhen"], [data-card-name="mengsan_baozou"], [data-card-name="mengsan_xuanfengzhan"], [data-card-name="mengsan_dazed_shuying"], [data-card-name="mengsan_slimed_shuying"], [data-card-name="mengsan_infection_shuying"], [data-card-name="mengsan_wound_shuying"]):not(.infohidden):not(.infoflip):has(> .mengsan-hand-upgrade-shuying) > .suit-num {
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:not(.infohidden):not(.infoflip):has(> .mengsan-hand-upgrade-shuying) > .suit-num {
|
||||
top: 4px !important;
|
||||
padding: 0 2px !important;
|
||||
font-size: 18px !important;
|
||||
@@ -259,17 +259,58 @@ body.mengsan-hand-ui-shuying :is(
|
||||
}
|
||||
}
|
||||
|
||||
/* 长牌名完整显示,不挤出卡面;只作用于梦三手牌。 */
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name="mengsan_ic_ashen_strike"], [data-card-name="mengsan_ic_fight_me"], [data-card-name="mengsan_ic_demonic_shield"], [data-card-name="mengsan_ic_forgotten_ritual"], [data-card-name="mengsan_ic_burning_pact"], [data-card-name="mengsan_ic_second_wind"], [data-card-name="mengsan_ic_pacts_end"], [data-card-name="mengsan_ic_fiend_fire"], [data-card-name="mengsan_ic_impervious"], [data-card-name="mengsan_ic_not_yet"], [data-card-name="mengsan_ic_demon_form"], [data-card-name="mengsan_ic_pyre"], [data-card-name="mengsan_ic_crimson_mantle"], [data-card-name="mengsan_ic_flame_barrier"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"]):not(.infohidden):not(.infoflip) > :is(.name, .top-name) {
|
||||
/* 本地完整卡面不绘制空标题或重复的花色信息条。 */
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:not(.infohidden):not(.infoflip) > :is(.name, .top-name):empty {
|
||||
display: none !important;
|
||||
}
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:has(> .suit-num:not(:empty)) > .info:not(.gaintag) {
|
||||
display: none !important;
|
||||
}
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:not(.infohidden):not(.infoflip) > .suit-num > .suit {
|
||||
font-size: inherit !important;
|
||||
line-height: inherit;
|
||||
}
|
||||
/* 保留原生词缀内容,覆盖十周年 fullimage 的字号归零,避开费用和名称。 */
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:not(.infohidden):not(.infoflip) > .gaintag.info:not(:empty) {
|
||||
display: block !important;
|
||||
top: auto !important;
|
||||
bottom: 36px !important;
|
||||
left: 31px !important;
|
||||
right: 5px !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
margin: 0 !important;
|
||||
padding: 2px 3px !important;
|
||||
box-sizing: border-box;
|
||||
font-size: 12px !important;
|
||||
line-height: 14px !important;
|
||||
letter-spacing: 0 !important;
|
||||
white-space: normal !important;
|
||||
overflow-wrap: anywhere;
|
||||
overflow: visible !important;
|
||||
background: #231b17e6 !important;
|
||||
color: #fff1cb !important;
|
||||
text-shadow: none !important;
|
||||
border: 1px solid #d8ba83 !important;
|
||||
border-radius: 3px;
|
||||
z-index: 6 !important;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* 长牌名由翻译长度自动标记,新增牌不需要再抄写 CSS 名单。 */
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"][data-mengsan-long-name="true"]:not(.infohidden):not(.infoflip) > :is(.name, .top-name) {
|
||||
font-size: 12px !important;
|
||||
letter-spacing: 0 !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
@media (max-width: 640px), (max-height: 500px) {
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name="mengsan_ic_ashen_strike"], [data-card-name="mengsan_ic_fight_me"], [data-card-name="mengsan_ic_demonic_shield"], [data-card-name="mengsan_ic_forgotten_ritual"], [data-card-name="mengsan_ic_burning_pact"], [data-card-name="mengsan_ic_second_wind"], [data-card-name="mengsan_ic_pacts_end"], [data-card-name="mengsan_ic_fiend_fire"], [data-card-name="mengsan_ic_impervious"], [data-card-name="mengsan_ic_not_yet"], [data-card-name="mengsan_ic_demon_form"], [data-card-name="mengsan_ic_pyre"], [data-card-name="mengsan_ic_crimson_mantle"], [data-card-name="mengsan_ic_flame_barrier"], [data-card-name="mengsan_feijianhuixuanbiao"], [data-card-name="mengsan_jianbingdaji"], [data-card-name="mengsan_quanshenzhuangji"], [data-card-name="mengsan_rongrongzhiquan"], [data-card-name="mengsan_shandianpili"], [data-card-name="mengsan_shuangchongdaji"], [data-card-name="mengsan_tiezhanbo"], [data-card-name="mengsan_touchui"], [data-card-name="mengsan_tupo"], [data-card-name="mengsan_yubeidaji"], [data-card-name="mengsan_wanmeidaji"], [data-card-name="mengsan_yujin"], [data-card-name="mengsan_fangxue"], [data-card-name="mengsan_jianyi"], [data-card-name="mengsan_pomie"], [data-card-name="mengsan_songjianwushi"]):not(.infohidden):not(.infoflip) > :is(.name, .top-name) {
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"][data-mengsan-long-name="true"]:not(.infohidden):not(.infoflip) > :is(.name, .top-name) {
|
||||
font-size: 10px !important;
|
||||
padding: 2px 4px !important;
|
||||
}
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-local-art="full"]:not(.infohidden):not(.infoflip) > .gaintag.info:not(:empty) {
|
||||
font-size: 10px !important;
|
||||
line-height: 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Infection only; keep cost/name above it and leave native card::after alone. */
|
||||
@@ -332,130 +373,6 @@ body.mengsan-hand-ui-shuying :is(
|
||||
}
|
||||
}
|
||||
|
||||
/* 第二批刘备牌组长牌名,沿用现有手牌字号。 */
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name="mengsan_ic_relentless_assault"], [data-card-name="mengsan_ic_howl_from_beyond"], [data-card-name="mengsan_ic_battle_trance"], [data-card-name="mengsan_ic_infernal_blade"], [data-card-name="mengsan_ic_eager"], [data-card-name="mengsan_ic_feel_no_pain"], [data-card-name="mengsan_ic_stone_armor"], [data-card-name="mengsan_ic_primal_force"], [data-card-name="mengsan_ic_battle_ready"], [data-card-name="mengsan_ic_hellraiser"], [data-card-name="mengsan_ic_dark_embrace"], [data-card-name="mengsan_ic_unshakable"], [data-card-name="mengsan_ic_juggernaut"], [data-card-name="mengsan_ic_rally"]):not(.infohidden):not(.infoflip) > :is(.name, .top-name) {
|
||||
font-size: 12px !important; letter-spacing: 0 !important; white-space: nowrap !important;
|
||||
}
|
||||
@media (max-width: 640px), (max-height: 500px) {
|
||||
.mengsan-hand-zone-shuying > .card:is([data-card-name="mengsan_ic_relentless_assault"], [data-card-name="mengsan_ic_howl_from_beyond"], [data-card-name="mengsan_ic_battle_trance"], [data-card-name="mengsan_ic_infernal_blade"], [data-card-name="mengsan_ic_eager"], [data-card-name="mengsan_ic_feel_no_pain"], [data-card-name="mengsan_ic_stone_armor"], [data-card-name="mengsan_ic_primal_force"], [data-card-name="mengsan_ic_battle_ready"], [data-card-name="mengsan_ic_hellraiser"], [data-card-name="mengsan_ic_dark_embrace"], [data-card-name="mengsan_ic_unshakable"], [data-card-name="mengsan_ic_juggernaut"], [data-card-name="mengsan_ic_rally"]):not(.infohidden):not(.infoflip) > :is(.name, .top-name) {
|
||||
font-size: 10px !important; padding: 2px 4px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 四类共享牌的长名称与刘备牌组采用相同字号,窄屏仍完整显示。 */
|
||||
.mengsan-hand-zone-shuying > .card:is(
|
||||
[data-card-name="mengsan_colorless_dramatic_entrance"],
|
||||
[data-card-name="mengsan_colorless_ultimate_strike"],
|
||||
[data-card-name="mengsan_colorless_gang_up"],
|
||||
[data-card-name="mengsan_colorless_seeker_strike"],
|
||||
[data-card-name="mengsan_colorless_thrumming_hatchet"],
|
||||
[data-card-name="mengsan_colorless_mind_blast"],
|
||||
[data-card-name="mengsan_colorless_tag_team"],
|
||||
[data-card-name="mengsan_colorless_dark_shackles"],
|
||||
[data-card-name="mengsan_colorless_jack_of_all_trades"],
|
||||
[data-card-name="mengsan_colorless_thinking_ahead"],
|
||||
[data-card-name="mengsan_colorless_production"],
|
||||
[data-card-name="mengsan_colorless_believe_in_you"],
|
||||
[data-card-name="mengsan_colorless_restlessness"],
|
||||
[data-card-name="mengsan_colorless_panic_button"],
|
||||
[data-card-name="mengsan_colorless_ultimate_defend"],
|
||||
[data-card-name="mengsan_colorless_coordinate"],
|
||||
[data-card-name="mengsan_colorless_panache"],
|
||||
[data-card-name="mengsan_colorless_prowess"],
|
||||
[data-card-name="mengsan_colorless_prep_time"],
|
||||
[data-card-name="mengsan_colorless_hand_of_greed"],
|
||||
[data-card-name="mengsan_colorless_the_gambit"],
|
||||
[data-card-name="mengsan_colorless_secret_technique"],
|
||||
[data-card-name="mengsan_colorless_secret_weapon"],
|
||||
[data-card-name="mengsan_colorless_master_of_strategy"],
|
||||
[data-card-name="mengsan_colorless_alchemize"],
|
||||
[data-card-name="mengsan_colorless_scrawl"],
|
||||
[data-card-name="mengsan_colorless_hidden_gem"],
|
||||
[data-card-name="mengsan_colorless_beacon_of_hope"],
|
||||
[data-card-name="mengsan_colorless_eternal_armor"],
|
||||
[data-card-name="mengsan_event_byrd_swoop"],
|
||||
[data-card-name="mengsan_event_mad_science_sapping"],
|
||||
[data-card-name="mengsan_event_mad_science_violence"],
|
||||
[data-card-name="mengsan_event_mad_science_choking"],
|
||||
[data-card-name="mengsan_event_feeding_frenzy"],
|
||||
[data-card-name="mengsan_event_mad_science_energized"],
|
||||
[data-card-name="mengsan_event_mad_science_wisdom"],
|
||||
[data-card-name="mengsan_event_mad_science_chaos"],
|
||||
[data-card-name="mengsan_event_toric_toughness"],
|
||||
[data-card-name="mengsan_event_mad_science_expertise"],
|
||||
[data-card-name="mengsan_event_mad_science_curious"],
|
||||
[data-card-name="mengsan_event_mad_science_improvement"],
|
||||
[data-card-name="mengsan_event_neows_fury"],
|
||||
[data-card-name="mengsan_event_brightest_flame"],
|
||||
[data-card-name="mengsan_status_frantic_escape"],
|
||||
[data-card-name="mengsan_curse_spore_mind"],
|
||||
[data-card-name="mengsan_curse_ascenders_bane"],
|
||||
[data-card-name="mengsan_curse_curse_of_the_bell"],
|
||||
[data-card-name="mengsan_curse_poor_sleep"],
|
||||
[data-card-name="mengsan_status_deprecated_card"]
|
||||
):not(.infohidden):not(.infoflip) > :is(.name, .top-name) {
|
||||
font-size: 12px !important;
|
||||
letter-spacing: 0 !important;
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
@media (max-width: 640px), (max-height: 500px) {
|
||||
.mengsan-hand-zone-shuying > .card:is(
|
||||
[data-card-name="mengsan_colorless_dramatic_entrance"],
|
||||
[data-card-name="mengsan_colorless_ultimate_strike"],
|
||||
[data-card-name="mengsan_colorless_gang_up"],
|
||||
[data-card-name="mengsan_colorless_seeker_strike"],
|
||||
[data-card-name="mengsan_colorless_thrumming_hatchet"],
|
||||
[data-card-name="mengsan_colorless_mind_blast"],
|
||||
[data-card-name="mengsan_colorless_tag_team"],
|
||||
[data-card-name="mengsan_colorless_dark_shackles"],
|
||||
[data-card-name="mengsan_colorless_jack_of_all_trades"],
|
||||
[data-card-name="mengsan_colorless_thinking_ahead"],
|
||||
[data-card-name="mengsan_colorless_production"],
|
||||
[data-card-name="mengsan_colorless_believe_in_you"],
|
||||
[data-card-name="mengsan_colorless_restlessness"],
|
||||
[data-card-name="mengsan_colorless_panic_button"],
|
||||
[data-card-name="mengsan_colorless_ultimate_defend"],
|
||||
[data-card-name="mengsan_colorless_coordinate"],
|
||||
[data-card-name="mengsan_colorless_panache"],
|
||||
[data-card-name="mengsan_colorless_prowess"],
|
||||
[data-card-name="mengsan_colorless_prep_time"],
|
||||
[data-card-name="mengsan_colorless_hand_of_greed"],
|
||||
[data-card-name="mengsan_colorless_the_gambit"],
|
||||
[data-card-name="mengsan_colorless_secret_technique"],
|
||||
[data-card-name="mengsan_colorless_secret_weapon"],
|
||||
[data-card-name="mengsan_colorless_master_of_strategy"],
|
||||
[data-card-name="mengsan_colorless_alchemize"],
|
||||
[data-card-name="mengsan_colorless_scrawl"],
|
||||
[data-card-name="mengsan_colorless_hidden_gem"],
|
||||
[data-card-name="mengsan_colorless_beacon_of_hope"],
|
||||
[data-card-name="mengsan_colorless_eternal_armor"],
|
||||
[data-card-name="mengsan_event_byrd_swoop"],
|
||||
[data-card-name="mengsan_event_mad_science_sapping"],
|
||||
[data-card-name="mengsan_event_mad_science_violence"],
|
||||
[data-card-name="mengsan_event_mad_science_choking"],
|
||||
[data-card-name="mengsan_event_feeding_frenzy"],
|
||||
[data-card-name="mengsan_event_mad_science_energized"],
|
||||
[data-card-name="mengsan_event_mad_science_wisdom"],
|
||||
[data-card-name="mengsan_event_mad_science_chaos"],
|
||||
[data-card-name="mengsan_event_toric_toughness"],
|
||||
[data-card-name="mengsan_event_mad_science_expertise"],
|
||||
[data-card-name="mengsan_event_mad_science_curious"],
|
||||
[data-card-name="mengsan_event_mad_science_improvement"],
|
||||
[data-card-name="mengsan_event_neows_fury"],
|
||||
[data-card-name="mengsan_event_brightest_flame"],
|
||||
[data-card-name="mengsan_status_frantic_escape"],
|
||||
[data-card-name="mengsan_curse_spore_mind"],
|
||||
[data-card-name="mengsan_curse_ascenders_bane"],
|
||||
[data-card-name="mengsan_curse_curse_of_the_bell"],
|
||||
[data-card-name="mengsan_curse_poor_sleep"],
|
||||
[data-card-name="mengsan_status_deprecated_card"]
|
||||
):not(.infohidden):not(.infoflip) > :is(.name, .top-name) {
|
||||
font-size: 10px !important;
|
||||
padding: 2px 4px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 稀有度是独立装饰层,不占用原生 card::after,也不改变尺寸、命中区或立绘。 */
|
||||
.mengsan-hand-rarity-shuying { display: none; pointer-events: none; }
|
||||
.mengsan-hand-zone-shuying > .card[data-mengsan-rarity]:not([data-mengsan-rarity="pending"]):not(.infohidden):not(.infoflip) > .mengsan-hand-rarity-shuying {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { openCardLibrary } from "../card-library.js";
|
||||
import { askMenu, openModeSelection } from "../navigation.js";
|
||||
import { askMenu, openModeSelection, openNonameMenu } from "../navigation.js";
|
||||
import { ui, get } from "../../../../../noname.js";
|
||||
import config from "../../config.js";
|
||||
import { getRelic } from "../../relics/definitions.js";
|
||||
@@ -317,6 +317,17 @@ export const showMap = (run, { saveRun } = {}) => new Promise(resolve => {
|
||||
});
|
||||
}) });
|
||||
createMenuAction({ icon: "settings", name: "设置与说明", description: "显示、音效与玩法说明 · 开发中", onClick: () => showMenuPage("设置与说明", "个性化与帮助", parent => renderPlaceholder(parent, "后续将在这里提供显示、音效和操作设置。")) });
|
||||
createMenuAction({ icon: "settings", name: "显示 Noname 菜单", description: "打开无名杀原生菜单,管理扩展与游戏设置",
|
||||
onClick: async event => {
|
||||
event.stopPropagation();
|
||||
setMenuOpen(false);
|
||||
try { await openNonameMenu(); }
|
||||
catch (error) {
|
||||
console.error("梦三打开 Noname 菜单失败:", error);
|
||||
await askMenu("暂时无法显示菜单", error.message || "请稍后重试。", [{ id: "close", name: "回到行军图" }]);
|
||||
}
|
||||
},
|
||||
}).setAttribute("aria-haspopup", "dialog");
|
||||
}
|
||||
|
||||
function handleEscape(event) {
|
||||
|
||||
@@ -1,7 +1,65 @@
|
||||
import { lib, game, ui } from "../../../../noname.js";
|
||||
import { lib, game, ui, _status } from "../../../../noname.js";
|
||||
import { save } from "../../../../noname/util/config.js";
|
||||
|
||||
let selectingMode = false;
|
||||
let nonameMenuTask = null;
|
||||
|
||||
// Open the engine's real settings menu without ending the current expedition flow.
|
||||
export const openNonameMenu = async () => {
|
||||
if (nonameMenuTask) return nonameMenuTask;
|
||||
const container = ui.menuContainer;
|
||||
if (!container?.isConnected || typeof ui.click?.config !== "function"
|
||||
|| typeof ui.click?.configMenu !== "function") {
|
||||
throw new Error("Noname 菜单尚未加载,请稍后重试。");
|
||||
}
|
||||
nonameMenuTask = new Promise((resolve, reject) => {
|
||||
const previousFocus = document.activeElement;
|
||||
const initiallyHidden = container.classList.contains("hidden");
|
||||
const wasPaused = _status.paused2;
|
||||
const layerClass = "mengsan-noname-menu-open-shuying";
|
||||
const hadLayerClass = ui.window.classList.contains(layerClass);
|
||||
const previousTabindex = container.getAttribute("tabindex");
|
||||
const overlays = Array.from(ui.window.querySelectorAll(".mengsan-overlay-shuying"))
|
||||
.map(node => ({ node, inert: node.inert }));
|
||||
let closed = false;
|
||||
const finish = error => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
observer.disconnect();
|
||||
if (!hadLayerClass) ui.window.classList.remove(layerClass);
|
||||
for (const { node, inert } of overlays) node.inert = inert;
|
||||
if (previousTabindex == null) container.removeAttribute("tabindex");
|
||||
else container.setAttribute("tabindex", previousTabindex);
|
||||
if (previousFocus?.isConnected) previousFocus.focus?.({ preventScroll: true });
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
};
|
||||
const observer = new MutationObserver(() => {
|
||||
if (!container.isConnected || container.classList.contains("hidden")) finish();
|
||||
});
|
||||
try {
|
||||
observer.observe(container, { attributes: true, attributeFilter: ["class"] });
|
||||
observer.observe(ui.window, { childList: true });
|
||||
ui.window.classList.add(layerClass);
|
||||
for (const { node } of overlays) node.inert = true;
|
||||
if (initiallyHidden) ui.click.config();
|
||||
if (container.classList.contains("hidden")) throw new Error("Noname 菜单未能打开,请稍后重试。");
|
||||
container.setAttribute("tabindex", "-1");
|
||||
container.focus({ preventScroll: true });
|
||||
} catch (error) {
|
||||
if (initiallyHidden) {
|
||||
try {
|
||||
if (!container.classList.contains("hidden")) ui.click.configMenu();
|
||||
} catch (closeError) { console.error("Noname 菜单关闭失败:", closeError); }
|
||||
if (wasPaused) game.pause2();
|
||||
else game.resume2();
|
||||
}
|
||||
finish(error);
|
||||
}
|
||||
});
|
||||
try { await nonameMenuTask; }
|
||||
finally { nonameMenuTask = null; }
|
||||
};
|
||||
|
||||
// Reuse the registered engine splash, not reload-to-the-current-mode or a fake mode list.
|
||||
export const openModeSelection = async () => {
|
||||
@@ -96,11 +154,14 @@ export const mountMenu = (overlay, options = {}) => {
|
||||
if (opened) return;
|
||||
opened = true;
|
||||
options.onOpen?.();
|
||||
let action = null;
|
||||
try {
|
||||
const action = await askMenu("征程菜单", "退出不删除存档。未完成的节点与对话将在下次进入时重新开始。", [
|
||||
action = await askMenu("征程菜单", "退出不删除存档。未完成的节点与对话将在下次进入时重新开始。", [
|
||||
{ id: "resume", name: "继续当前界面" },
|
||||
{ id: "noname", name: "显示 Noname 菜单" },
|
||||
{ id: "exit", name: "返回模式选择" },
|
||||
]);
|
||||
if (action === "noname") { await openNonameMenu(); return; }
|
||||
if (action !== "exit") return;
|
||||
const confirmed = await askMenu("返回模式选择?", "保留上次已完成节点的存档。本次尚未完成的内容不会提交。", [
|
||||
{ id: "cancel", name: "留在梦三" },
|
||||
@@ -108,8 +169,10 @@ export const mountMenu = (overlay, options = {}) => {
|
||||
]);
|
||||
if (confirmed === "confirm") await openModeSelection();
|
||||
} catch (error) {
|
||||
console.error("梦三返回模式选择失败:", error);
|
||||
await askMenu("暂时无法返回", "没有清除征程。请关闭提示后重试。", [{ id: "close", name: "回到当前界面" }]);
|
||||
console.error("梦三菜单操作失败:", error);
|
||||
await askMenu(action === "noname" ? "暂时无法显示菜单" : "暂时无法返回",
|
||||
action === "noname" ? error.message || "请关闭提示后重试。" : "没有清除征程。请关闭提示后重试。",
|
||||
[{ id: "close", name: "回到当前界面" }]);
|
||||
} finally {
|
||||
opened = false;
|
||||
options.onClose?.();
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
/* Only elevate the engine menu and its dropdowns while opened from Mengsan. */
|
||||
.mengsan-noname-menu-open-shuying > .menu-container { z-index: 120 !important; }
|
||||
.mengsan-noname-menu-open-shuying > .popup-container { z-index: 121 !important; }
|
||||
|
||||
.mengsan-overlay-shuying {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
|
||||
@@ -206,22 +206,38 @@ export default function initShuYingMenu({ lib, game, ui, shuYing, updater }) {
|
||||
nopointer: true,
|
||||
};
|
||||
|
||||
const channelText = channel => `更新通道:${channel == "preview" ? "测试版" : "正式版"}(点击切换)`;
|
||||
const channelText = channel => `更新通道:<b>${channel == "preview" ? "测试版" : "正式版"}</b>(点击选择)`;
|
||||
menu.update_channel = {
|
||||
name: channelText(updater.getUpdateChannel()),
|
||||
clear: true,
|
||||
intro: "切换后立即从当前下载源的对应标签下载并校验文件,完成后需重启游戏。测试版可能回退到较早的内容。",
|
||||
intro: "打开选择窗口,确认后下载所选通道的更新,完成后需重启游戏。",
|
||||
async onclick() {
|
||||
if (!shuYing.m_bIsDownload) {
|
||||
alert("有其他文件正在下载,请稍后再试吧。");
|
||||
alert("正在处理其他操作,请稍后再试。");
|
||||
return;
|
||||
}
|
||||
|
||||
const current = updater.getUpdateChannel();
|
||||
const next = current == "preview" ? "stable" : "preview";
|
||||
shuYing.m_bIsDownload = false;
|
||||
this.innerHTML = `正在切换到${next == "preview" ? "测试版" : "正式版"}…`;
|
||||
try {
|
||||
const next = await shuYing.chooseSingleOptionDialog({
|
||||
title: "选择更新通道",
|
||||
intro: "确认后会下载所选通道的更新,完成后需重启游戏。测试版的版本可能早于当前正式版。",
|
||||
defaultKey: current, currentKey: current,
|
||||
cancelable: true, variant: "settings", confirmText: "切换并更新",
|
||||
options: [
|
||||
{
|
||||
key: "stable", name: "正式版", tag: current == "stable" ? "当前使用" : "日常游玩",
|
||||
description: "接收正式发布的版本,适合日常游玩。"
|
||||
},
|
||||
{
|
||||
key: "preview", name: "测试版", tag: current == "preview" ? "当前使用" : "提前体验",
|
||||
description: "提前体验新内容,可能包含尚未完善的功能。"
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!next || next == current) return;
|
||||
this.innerHTML = `正在切换到${next == "preview" ? "测试版" : "正式版"}…`;
|
||||
const version = await updater.checkVersion(shuYing, {
|
||||
channel: next,
|
||||
switchChannel: true,
|
||||
@@ -235,29 +251,53 @@ export default function initShuYingMenu({ lib, game, ui, shuYing, updater }) {
|
||||
const text = channelText(updater.getUpdateChannel());
|
||||
menu.update_channel.name = text;
|
||||
this.innerHTML = text;
|
||||
shuYing.m_bIsDownload = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const sourceText = source => `下载源:${source == "github" ? "GitHub" : "Gitea"}(点击切换)`;
|
||||
const sourceText = source => `下载源:<b>${source == "github" ? "GitHub" : "Gitea"}</b>(点击选择)`;
|
||||
menu.update_source = {
|
||||
name: sourceText(updater.getUpdateSource()),
|
||||
clear: true,
|
||||
intro: "选择从 GitHub 或 Gitea 获取标签与文件;切换后刷新在线版本,实际下载由版本检测触发。",
|
||||
onclick() {
|
||||
intro: "打开下载源选择窗口;确认后刷新在线版本,不会立即下载文件。",
|
||||
async onclick() {
|
||||
if (!shuYing.m_bIsDownload) {
|
||||
alert("有其他文件正在下载,请稍后再试吧。");
|
||||
alert("正在处理其他操作,请稍后再试。");
|
||||
return;
|
||||
}
|
||||
|
||||
const next = updater.getUpdateSource() == "github" ? "gitea" : "github";
|
||||
game.saveConfig("shuYing_update_source", next);
|
||||
const text = sourceText(next);
|
||||
menu.update_source.name = text;
|
||||
this.innerHTML = text;
|
||||
game.saveConfig("shuYing_online_version", null);
|
||||
shuYing.updateOnlineVersionMenu(null, "检测中...");
|
||||
shuYing.getVersion();
|
||||
const current = updater.getUpdateSource();
|
||||
shuYing.m_bIsDownload = false;
|
||||
try {
|
||||
const next = await shuYing.chooseSingleOptionDialog({
|
||||
title: "选择下载源",
|
||||
intro: "确认后保存下载源并刷新在线版本,不会立即下载文件。需要更新时,请点击“版本检测”。",
|
||||
defaultKey: current, currentKey: current,
|
||||
cancelable: true, variant: "settings", confirmText: "保存下载源",
|
||||
options: [
|
||||
{
|
||||
key: "gitea", name: "Gitea", tag: current == "gitea" ? "当前使用" : "默认下载源",
|
||||
description: "从 Gitea 获取版本信息和更新文件(国内源,下载速度上限1.4M)"
|
||||
},
|
||||
{
|
||||
key: "github", name: "GitHub", tag: current == "github" ? "当前使用" : "可选下载源",
|
||||
description: "从 GitHub 获取版本信息和更新文件。"
|
||||
},
|
||||
],
|
||||
});
|
||||
if (!next || next == current) return;
|
||||
game.saveConfig("shuYing_update_source", next);
|
||||
game.saveConfig("shuYing_online_version", null);
|
||||
shuYing.updateOnlineVersionMenu(null, "检测中...");
|
||||
void shuYing.getVersion();
|
||||
}
|
||||
finally {
|
||||
const text = sourceText(updater.getUpdateSource());
|
||||
menu.update_source.name = text;
|
||||
this.innerHTML = text;
|
||||
shuYing.m_bIsDownload = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -343,18 +383,63 @@ export default function initShuYingMenu({ lib, game, ui, shuYing, updater }) {
|
||||
},
|
||||
};
|
||||
|
||||
const mengsanLabelId = "shuying_mengsan_module_status";
|
||||
const mengsanStates = {
|
||||
checking: ["检测中", "请稍候"],
|
||||
installed: ["已安装", "点击卸载"],
|
||||
disabled: ["未安装", "点击安装"],
|
||||
installing: ["安装未完成", "点击修复"],
|
||||
repair: ["待修复", "点击修复"],
|
||||
uninstalling: ["卸载未完成", "继续卸载"],
|
||||
unknown: ["状态未确认", "点击检查"],
|
||||
working: ["处理中", "请稍候"],
|
||||
};
|
||||
const mengsanLabelHTML = status => {
|
||||
const [label, action] = mengsanStates[status] || mengsanStates.unknown;
|
||||
return `梦三模式:<b>${label}</b>(${action})`;
|
||||
};
|
||||
let mengsanLabelNode = null;
|
||||
let mengsanStatusRequest = 0;
|
||||
let mengsanModuleOperating = false;
|
||||
const renderMengsanStatus = status => {
|
||||
const content = mengsanLabelHTML(status);
|
||||
menu.mengsanModule.name = `<span id="${mengsanLabelId}" style="white-space:normal;">${content}</span>`;
|
||||
// Keep the existing node so closing the menu during a download still refreshes it.
|
||||
mengsanLabelNode = document.getElementById(mengsanLabelId) || mengsanLabelNode;
|
||||
if (mengsanLabelNode) mengsanLabelNode.innerHTML = content;
|
||||
};
|
||||
shuYing.updateMengsanModuleMenu = async () => {
|
||||
const request = ++mengsanStatusRequest;
|
||||
mengsanLabelNode = document.getElementById(mengsanLabelId) || mengsanLabelNode;
|
||||
if (mengsanModuleOperating) { renderMengsanStatus("working"); return; }
|
||||
let status;
|
||||
try { status = await updater.getMengsanModuleStatus(); }
|
||||
catch (error) {
|
||||
console.error("读取梦三安装状态失败:", error);
|
||||
status = "unknown";
|
||||
}
|
||||
if (request == mengsanStatusRequest) renderMengsanStatus(status);
|
||||
};
|
||||
menu.mengsanModule = {
|
||||
name: "梦三模式(按需安装/卸载)",
|
||||
name: `<span id="${mengsanLabelId}" style="white-space:normal;">${mengsanLabelHTML("checking")}</span>`,
|
||||
clear: true,
|
||||
onclick() {
|
||||
if (shuYing.m_bIsDownload) {
|
||||
shuYing.m_bIsDownload = false;
|
||||
updater.manageMengsanModule(shuYing);
|
||||
} else {
|
||||
async onclick() {
|
||||
if (!shuYing.m_bIsDownload) {
|
||||
alert("有其他文件正在下载,请稍后再试吧。");
|
||||
return;
|
||||
}
|
||||
shuYing.m_bIsDownload = false;
|
||||
mengsanModuleOperating = true;
|
||||
++mengsanStatusRequest; // Discard any pre-operation status lookup still in flight.
|
||||
renderMengsanStatus("working");
|
||||
try { await updater.manageMengsanModule(shuYing); }
|
||||
finally {
|
||||
mengsanModuleOperating = false;
|
||||
await shuYing.updateMengsanModuleMenu();
|
||||
}
|
||||
},
|
||||
};
|
||||
void shuYing.updateMengsanModuleMenu();
|
||||
|
||||
menu.repairBug = {
|
||||
name: "本地资源修复",
|
||||
|
||||
@@ -40,6 +40,7 @@ function beginUpdate(shuYing, full = false) {
|
||||
shuYing._progressRendered = 0;
|
||||
shuYing._updateStarted = Date.now();
|
||||
shuYing._updateStats = { cached: 0, hashed: 0 };
|
||||
void shuYing.updateMengsanModuleMenu?.();
|
||||
}
|
||||
|
||||
function relativeTarget(target) {
|
||||
@@ -154,6 +155,7 @@ async function finishUpdate(shuYing) {
|
||||
addLog(shuYing, `本次耗时 ${((Date.now() - shuYing._updateStarted) / 1000).toFixed(1)} 秒;缓存通过 ${stats?.cached || 0} 次,完整校验通过 ${stats?.hashed || 0} 次`);
|
||||
}
|
||||
await flushLog(shuYing);
|
||||
await shuYing.updateMengsanModuleMenu?.();
|
||||
}
|
||||
|
||||
function parseVersion(version) {
|
||||
@@ -1097,6 +1099,28 @@ async function saveMengsanRecord(record) {
|
||||
await saveUpdaterConfig({ [mengsanRecordKey]: record, [mengsanVersionKey]: version });
|
||||
}
|
||||
|
||||
async function getMengsanModuleStatus() {
|
||||
const record = getMengsanRecord();
|
||||
if (record) return record.status;
|
||||
const version = lib.config[mengsanVersionKey];
|
||||
if (version) return version == "disabled" ? "disabled" : "installed";
|
||||
|
||||
// Legacy ZIP installations may have no receipt; inspect only the local entry.
|
||||
const entry = "extension/术樱包/mengsan/register.js";
|
||||
if (lib.node?.fs || hasCordovaFiles() || typeof game.checkFile == "function") {
|
||||
const exists = await checkFile(entry);
|
||||
return exists == 1 ? "installed" : exists == -1 ? "disabled" : "unknown";
|
||||
}
|
||||
if (typeof game.readFile == "function") {
|
||||
try { await readFile(entry); return "installed"; }
|
||||
catch (error) {
|
||||
if (error?.code == "ENOENT" || error?.code == 1) return "disabled";
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
async function saveUpdaterConfig(values) {
|
||||
// Publish only after both the IndexedDB transaction and its requests commit.
|
||||
lib.status.reload++;
|
||||
@@ -1712,4 +1736,5 @@ export default {
|
||||
repairMissingFiles,
|
||||
repairCoreFiles,
|
||||
manageMengsanModule,
|
||||
getMengsanModuleStatus,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user