mirror of
https://github.com/Shu-Ying/noname-shuying.git
synced 2026-10-31 20:19:58 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b423e17b6 |
@@ -1,12 +1,12 @@
|
||||
name: Release extension
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
paths:
|
||||
- "extension.js"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "版本号,例如 2.0.1.2 或 2.0.1.2-rc.1"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -17,33 +17,23 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: pwsh
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
BEFORE_SHA: ${{ github.event.before }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Check out source
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.sha }}
|
||||
ref: ${{ github.event.repository.default_branch }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Read current version and detect change
|
||||
id: version
|
||||
run: |
|
||||
./tools/resolve_release_version.ps1 `
|
||||
-Root $env:GITHUB_WORKSPACE `
|
||||
-EventName $env:GITHUB_EVENT_NAME `
|
||||
-PreviousRef $env:BEFORE_SHA
|
||||
|
||||
- name: Generate manifest and create tag
|
||||
if: steps.version.outputs.should_release == 'true'
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$version = $env:RELEASE_VERSION
|
||||
@@ -128,7 +118,6 @@ jobs:
|
||||
Write-Host "已创建包含清单的标签:$tag"
|
||||
|
||||
- name: Build ZIP from tag
|
||||
if: steps.version.outputs.should_release == 'true'
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$version = $env:RELEASE_VERSION
|
||||
@@ -190,7 +179,6 @@ jobs:
|
||||
Write-Host "安装包:$package"
|
||||
|
||||
- name: Publish GitHub Release
|
||||
if: steps.version.outputs.should_release == 'true'
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if (!$env:GITHUB_TOKEN) {
|
||||
|
||||
Vendored
+11
-11
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"version": "2.0.2.3-rc.8",
|
||||
"generated_at": "2026-10-29T07:21:47.319980+00:00",
|
||||
"version": "2.0.2.3-rc.6",
|
||||
"generated_at": "2026-10-29T04:08:40.947987+00:00",
|
||||
"algorithm": "sha256",
|
||||
"update_channels": true,
|
||||
"file_count": 288,
|
||||
"size_bytes": 48048830,
|
||||
"folder_sha256": "9cc2a7cd12f4b066552c64428dfcf9584a12aaf0e6d1cf1e725c02dbe55582fe",
|
||||
"size_bytes": 48045889,
|
||||
"folder_sha256": "c1c22cf38893f208ab42a5b77d042fd7c623364d4871247f3729851adaae867f",
|
||||
"core": [
|
||||
"extension.js",
|
||||
"menu.js",
|
||||
@@ -439,7 +439,7 @@
|
||||
},
|
||||
"extension.js": {
|
||||
"size": 15731,
|
||||
"sha256": "0a13e40488a5022648ae31334a281a2ad7c439aded37c8743f2ac0746ff65591"
|
||||
"sha256": "0e801feff445e66a3ab9fade2e12b389019451e7dd203ac791d4409cc9076c2e"
|
||||
},
|
||||
"function.js": {
|
||||
"size": 38588,
|
||||
@@ -1350,8 +1350,8 @@
|
||||
"sha256": "bd89818b8f535c19b51f107ca2c75dec669852e4a604ed55f6fa945ecec8e6d6"
|
||||
},
|
||||
"update.js": {
|
||||
"size": 80395,
|
||||
"sha256": "faf3b894c872db201e9cf71a2c43fbc06ef9027270dd8b4c72f2668858d91702"
|
||||
"size": 77454,
|
||||
"sha256": "f2693e030c0008c788c3e1785e0d6b41dc1df1251624efc003ff0b51d4c2a9f0"
|
||||
},
|
||||
"update.txt": {
|
||||
"size": 4962,
|
||||
@@ -1696,7 +1696,7 @@
|
||||
"extension.js": {
|
||||
"type": "file",
|
||||
"size": 15731,
|
||||
"sha256": "0a13e40488a5022648ae31334a281a2ad7c439aded37c8743f2ac0746ff65591"
|
||||
"sha256": "0e801feff445e66a3ab9fade2e12b389019451e7dd203ac791d4409cc9076c2e"
|
||||
},
|
||||
"function.js": {
|
||||
"type": "file",
|
||||
@@ -2965,8 +2965,8 @@
|
||||
},
|
||||
"update.js": {
|
||||
"type": "file",
|
||||
"size": 80395,
|
||||
"sha256": "faf3b894c872db201e9cf71a2c43fbc06ef9027270dd8b4c72f2668858d91702"
|
||||
"size": 77454,
|
||||
"sha256": "f2693e030c0008c788c3e1785e0d6b41dc1df1251624efc003ff0b51d4c2a9f0"
|
||||
},
|
||||
"update.txt": {
|
||||
"type": "file",
|
||||
@@ -3116,7 +3116,7 @@
|
||||
"entry": "mengsan/register.js",
|
||||
"manifest": "dist/modules/mengsan.json",
|
||||
"file_count": 830,
|
||||
"size_bytes": 148053007,
|
||||
"size_bytes": 148043940,
|
||||
"legacy_manifest": "dist/modules/mengsan.legacy.json"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+36
-36
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"version": "2.0.2.3-rc.8",
|
||||
"generated_at": "2026-10-29T07:21:47.321994+00:00",
|
||||
"version": "2.0.2.3-rc.6",
|
||||
"generated_at": "2026-10-29T04:08:40.950013+00:00",
|
||||
"algorithm": "sha256",
|
||||
"update_channels": true,
|
||||
"file_count": 830,
|
||||
"size_bytes": 148053007,
|
||||
"folder_sha256": "fe2d3d9cc6b254fe5168e0dd23112a26766277aa969f6ec61ff3243e1c300a51",
|
||||
"size_bytes": 148043940,
|
||||
"folder_sha256": "f7d08826710e8bb0260488701afba4f73f4a9b28ce463735eaff398886b40392",
|
||||
"core": [
|
||||
"mengsan/register.js"
|
||||
],
|
||||
@@ -4072,8 +4072,8 @@
|
||||
"sha256": "db7b79b425cff1f460f83b6fa38ccff431849acc89b89d34b01017562eeea920"
|
||||
},
|
||||
"mengsan/ui/gm-manager.js": {
|
||||
"size": 9913,
|
||||
"sha256": "bdefd7704e499a25e7a02de26c6487dff55c15f2723089c8fd8d85155d9e2afc"
|
||||
"size": 9361,
|
||||
"sha256": "6083304040dd8f5f3b46497b250944e5edf94208fca8e94b42f921dd82cf733c"
|
||||
},
|
||||
"mengsan/ui/hand/adapter.js": {
|
||||
"size": 935,
|
||||
@@ -4092,20 +4092,20 @@
|
||||
"sha256": "4b804c1d42c6c1171d42a37136fcb903bfb3128beae6a357b52ef9cede54908f"
|
||||
},
|
||||
"mengsan/ui/hand/input.js": {
|
||||
"size": 13946,
|
||||
"sha256": "52e18e674a51047a965103d6b708ff52886d9f02d4c3834a54037e31c996aeea"
|
||||
"size": 12295,
|
||||
"sha256": "afd6bca628fe478dcc7dec217f8e4e0cdbccd8f29f55b77266793f61c7ee7087"
|
||||
},
|
||||
"mengsan/ui/hand/overlays.js": {
|
||||
"size": 3970,
|
||||
"sha256": "9e489fc939f97a21266764f1beccdda1cc3ac9df22f72c633647d7481fa4e662"
|
||||
},
|
||||
"mengsan/ui/hand/style.css": {
|
||||
"size": 15563,
|
||||
"sha256": "0796ae6914b3241a85beebe1db81900e00ae8b997301d6d6a0d379dfcc02951e"
|
||||
"size": 15562,
|
||||
"sha256": "d673f460b44db76148dea6b213f7e0fa5c310ae2d7bcf6b4e86fa9ce3e6f0e05"
|
||||
},
|
||||
"mengsan/ui/hud/index.js": {
|
||||
"size": 24929,
|
||||
"sha256": "d7932d6f7abb3ee793e63acbdac7774d874a9a96b7034f15579fcdeea78f2111"
|
||||
"size": 19710,
|
||||
"sha256": "671e9c691f172a70cc5ff7e4e721c8a966f6d53d5f760e2fd46e9ccc10871e43"
|
||||
},
|
||||
"mengsan/ui/hud/overlays.js": {
|
||||
"size": 7007,
|
||||
@@ -4116,8 +4116,8 @@
|
||||
"sha256": "b18ae987f451453f77a397093cd57d8a8bd1fc03dbe845e3fc5a943da0a8ff04"
|
||||
},
|
||||
"mengsan/ui/hud/style.css": {
|
||||
"size": 20187,
|
||||
"sha256": "9906a3727c492bf4bce8e6a1c10318e7dd2be77db9d9a4a98432231dfe4e2254"
|
||||
"size": 19765,
|
||||
"sha256": "7e2c39086ee8768e3eb9adc6fe244c6f19150ff0c60f73216a9d9b4743a3c007"
|
||||
},
|
||||
"mengsan/ui/intent-display.js": {
|
||||
"size": 13291,
|
||||
@@ -4140,12 +4140,12 @@
|
||||
"sha256": "f9d5af85381d23525d8f985635709fd14df234123cd2d04851f0015952e47ab1"
|
||||
},
|
||||
"mengsan/ui/relic-strip.js": {
|
||||
"size": 4005,
|
||||
"sha256": "85b10f28637618bc52c0521968d8ac674af6c91c88b9d7f622e83753be9aeac8"
|
||||
"size": 4035,
|
||||
"sha256": "75993cde0dd7ad76cf064684de87f35f5b174e6105b1ee89c441dd038a3730d9"
|
||||
},
|
||||
"mengsan/ui/relics.js": {
|
||||
"size": 4474,
|
||||
"sha256": "1121847176e229cdf3e682166126c50c37bdbc7ccefebadc6143da1ec63c6f22"
|
||||
"size": 4113,
|
||||
"sha256": "653172b8bde8e96ac79b910de76e03815ae9815ed08ab09445edc3b8f73ce750"
|
||||
},
|
||||
"mengsan/ui/reward-package.js": {
|
||||
"size": 4256,
|
||||
@@ -4156,8 +4156,8 @@
|
||||
"sha256": "c39778c5c256ebb0ea3af19fab34b325d21ab875522162868b4ab2ada34cd12e"
|
||||
},
|
||||
"mengsan/ui/style.css": {
|
||||
"size": 80819,
|
||||
"sha256": "6823e80898b4595eaeebec5f0127dac837b4b7174bbae22d537e89c81716398c"
|
||||
"size": 79928,
|
||||
"sha256": "cd291794da3e60569902ff6f09d5a7336f0f4c1edde43ed5ac2d22a1efb762b0"
|
||||
},
|
||||
"mengsan/ui/virtual-card-grid.js": {
|
||||
"size": 4820,
|
||||
@@ -8346,8 +8346,8 @@
|
||||
},
|
||||
"gm-manager.js": {
|
||||
"type": "file",
|
||||
"size": 9913,
|
||||
"sha256": "bdefd7704e499a25e7a02de26c6487dff55c15f2723089c8fd8d85155d9e2afc"
|
||||
"size": 9361,
|
||||
"sha256": "6083304040dd8f5f3b46497b250944e5edf94208fca8e94b42f921dd82cf733c"
|
||||
},
|
||||
"hand": {
|
||||
"type": "directory",
|
||||
@@ -8374,8 +8374,8 @@
|
||||
},
|
||||
"input.js": {
|
||||
"type": "file",
|
||||
"size": 13946,
|
||||
"sha256": "52e18e674a51047a965103d6b708ff52886d9f02d4c3834a54037e31c996aeea"
|
||||
"size": 12295,
|
||||
"sha256": "afd6bca628fe478dcc7dec217f8e4e0cdbccd8f29f55b77266793f61c7ee7087"
|
||||
},
|
||||
"overlays.js": {
|
||||
"type": "file",
|
||||
@@ -8384,8 +8384,8 @@
|
||||
},
|
||||
"style.css": {
|
||||
"type": "file",
|
||||
"size": 15563,
|
||||
"sha256": "0796ae6914b3241a85beebe1db81900e00ae8b997301d6d6a0d379dfcc02951e"
|
||||
"size": 15562,
|
||||
"sha256": "d673f460b44db76148dea6b213f7e0fa5c310ae2d7bcf6b4e86fa9ce3e6f0e05"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -8394,8 +8394,8 @@
|
||||
"children": {
|
||||
"index.js": {
|
||||
"type": "file",
|
||||
"size": 24929,
|
||||
"sha256": "d7932d6f7abb3ee793e63acbdac7774d874a9a96b7034f15579fcdeea78f2111"
|
||||
"size": 19710,
|
||||
"sha256": "671e9c691f172a70cc5ff7e4e721c8a966f6d53d5f760e2fd46e9ccc10871e43"
|
||||
},
|
||||
"overlays.js": {
|
||||
"type": "file",
|
||||
@@ -8409,8 +8409,8 @@
|
||||
},
|
||||
"style.css": {
|
||||
"type": "file",
|
||||
"size": 20187,
|
||||
"sha256": "9906a3727c492bf4bce8e6a1c10318e7dd2be77db9d9a4a98432231dfe4e2254"
|
||||
"size": 19765,
|
||||
"sha256": "7e2c39086ee8768e3eb9adc6fe244c6f19150ff0c60f73216a9d9b4743a3c007"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -8446,13 +8446,13 @@
|
||||
},
|
||||
"relic-strip.js": {
|
||||
"type": "file",
|
||||
"size": 4005,
|
||||
"sha256": "85b10f28637618bc52c0521968d8ac674af6c91c88b9d7f622e83753be9aeac8"
|
||||
"size": 4035,
|
||||
"sha256": "75993cde0dd7ad76cf064684de87f35f5b174e6105b1ee89c441dd038a3730d9"
|
||||
},
|
||||
"relics.js": {
|
||||
"type": "file",
|
||||
"size": 4474,
|
||||
"sha256": "1121847176e229cdf3e682166126c50c37bdbc7ccefebadc6143da1ec63c6f22"
|
||||
"size": 4113,
|
||||
"sha256": "653172b8bde8e96ac79b910de76e03815ae9815ed08ab09445edc3b8f73ce750"
|
||||
},
|
||||
"reward-package.js": {
|
||||
"type": "file",
|
||||
@@ -8466,8 +8466,8 @@
|
||||
},
|
||||
"style.css": {
|
||||
"type": "file",
|
||||
"size": 80819,
|
||||
"sha256": "6823e80898b4595eaeebec5f0127dac837b4b7174bbae22d537e89c81716398c"
|
||||
"size": 79928,
|
||||
"sha256": "cd291794da3e60569902ff6f09d5a7336f0f4c1edde43ed5ac2d22a1efb762b0"
|
||||
},
|
||||
"virtual-card-grid.js": {
|
||||
"type": "file",
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "2.0.2.3-rc.8",
|
||||
"version": "2.0.2.3-rc.6",
|
||||
"module_id": "mengsan",
|
||||
"snapshot": "legacy",
|
||||
"files": [
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import { configureDiagnostics } from "./diagnostics/index.js";
|
||||
|
||||
export const type = "extension";
|
||||
export let shuYing = new Object();
|
||||
const shuYingLocalVersion = "2.0.2.3-rc.8";
|
||||
const shuYingLocalVersion = "2.0.2.3-rc.6";
|
||||
|
||||
shuYing.name = "术樱包";
|
||||
shuYing.editable = false;
|
||||
|
||||
@@ -7,20 +7,6 @@ const app = path.resolve(__dirname, '../../../..');
|
||||
const packages = process.env.CODEX_NODE_MODULES || 'C:/Users/Diuse/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules';
|
||||
const { chromium } = require(path.join(packages, 'playwright'));
|
||||
const prefix = '/extension/' + encodeURIComponent('术樱包') + '/mengsan';
|
||||
const holdStatus = async (page, locator, pointerType = 'mouse') => {
|
||||
const box = await locator.boundingBox();
|
||||
const point = {x:box.x + box.width / 2,y:box.y + box.height / 2};
|
||||
if (pointerType === 'touch') {
|
||||
await locator.dispatchEvent('pointerdown', {button:0,pointerId:44,
|
||||
pointerType,clientX:point.x,clientY:point.y});
|
||||
await page.waitForTimeout(550);
|
||||
await page.evaluate(() => window.dispatchEvent(
|
||||
new PointerEvent('pointerup', {pointerId:44,pointerType:'touch'})));
|
||||
} else {
|
||||
await page.mouse.move(point.x,point.y); await page.mouse.down();
|
||||
await page.waitForTimeout(550); await page.mouse.up();
|
||||
}
|
||||
};
|
||||
// Native themes use different button sizes. Drag onto an exposed point on the
|
||||
// original player, rather than assuming its center is clear in the dense fixture.
|
||||
const exposedTarget = (page,index) => page.evaluate(index => {
|
||||
@@ -279,68 +265,17 @@ const server = http.createServer((req, res) => {
|
||||
await page.evaluate(()=>{game.players[7].hp=0;hud.refresh();});
|
||||
assert.equal(await page.evaluate(()=>game.players[7].style.cssText===originalEnemyPresentation[6].style&&!game.players[7].classList.contains('mengsan-hud-defeated-shuying')),true,'death keeps the original battlefield presentation');checks++;
|
||||
await page.evaluate(()=>{game.players[7].hp=40;game.me.storage.mengsanVulnerable_shuying=3;hud.refresh();});
|
||||
assert.equal(await page.locator('.mengsan-hud-statuses-shuying').innerText(),'','status chips contain icons only');checks++;
|
||||
const heldStatus = page.locator('.mengsan-hud-statuses-shuying button').first();
|
||||
await heldStatus.dispatchEvent('pointerdown', {button:0,pointerId:45,
|
||||
pointerType:'touch',clientX:10,clientY:10});
|
||||
await page.waitForTimeout(150);
|
||||
await page.evaluate(() => {
|
||||
game.me.storage.mengsanWeak_shuying=1; hud.refresh();
|
||||
});
|
||||
await page.waitForTimeout(450);
|
||||
assert.equal(await page.locator('dialog.mengsan-hud-dialog-shuying').count(),1,'unrelated status update preserves an active hold');checks++;
|
||||
await page.evaluate(() => window.dispatchEvent(
|
||||
new PointerEvent('pointerup', {pointerId:45,pointerType:'touch'})));
|
||||
await page.evaluate(() => {
|
||||
game.me.storage.mengsanVulnerable_shuying=5; hud.refresh();
|
||||
});
|
||||
assert.match(await page.locator('dialog.mengsan-hud-dialog-shuying').innerText(),/易伤 5/,'open details show the current value');checks++;
|
||||
await page.evaluate(() => {
|
||||
delete game.me.storage.mengsanVulnerable_shuying; hud.refresh();
|
||||
});
|
||||
assert.match(await page.locator('dialog.mengsan-hud-dialog-shuying').innerText(),/已失效/,'removed state is explicitly expired');checks++;
|
||||
await page.getByRole('button',{name:'关闭',exact:true}).click();
|
||||
await page.evaluate(() => {
|
||||
delete game.me.storage.mengsanWeak_shuying;
|
||||
game.me.storage.mengsanVulnerable_shuying=3; hud.refresh();
|
||||
});
|
||||
assert.match(await page.locator('.mengsan-hud-statuses-shuying').innerText(),/3/);checks++;
|
||||
await page.evaluate(()=>{for(const key of ['Weak','Frail','Artifact','Constrict','Tangled','Shrink','Plow','Slippery'])game.me.storage['mengsan'+key+'_shuying']=2;hud.refresh();});
|
||||
assert.equal(await page.evaluate(()=>{const row=document.querySelector('.mengsan-hud-statuses-shuying').getBoundingClientRect();return [...document.querySelectorAll('.mengsan-hud-statuses-shuying button')].every(n=>{const r=n.getBoundingClientRect();return r.bottom<=row.bottom+1&&r.right<=row.right+1;});}),true,'matrix overflow keeps the visible statuses and more button inside the grid');checks++;
|
||||
// Compact icon chips fit more states; register enough real display fields
|
||||
// to exercise overflow on wide screens as well as phones.
|
||||
await page.evaluate(async prefix => {
|
||||
const {bindCombatStatusSource} = await import(prefix+'/battle/status-sources.js');
|
||||
const keys=['turnStrength','turnEnergy','turnBlock','turnLoseHp',
|
||||
'infernoLoseHp','lossDamage','lossStrength','vulnerableBonus',
|
||||
'attackBlock','retaliation','colossus','freeAttack','noDraw',
|
||||
'noEnergy','doubleTap','plating','exhaustBlock','exhaustDraw',
|
||||
'vulnerableDraw','juggle','endAttack','retrieveAttack','bodyguard',
|
||||
'autoStrike','firstBlock','blockDamage','barricade','corruption'];
|
||||
bindCombatStatusSource(game.me,session,'overflow-test',() => ({
|
||||
powers:Object.fromEntries(keys.map(key => [key,1])),
|
||||
}));
|
||||
hud.refresh();
|
||||
},prefix);
|
||||
await holdStatus(page,page.getByRole('button',{
|
||||
name:'点击查看全部状态',exact:true}));
|
||||
await page.getByRole('button',{name:'查看全部状态',exact:true}).click();
|
||||
assert.ok(await page.getByRole('dialog').isVisible(),'overflow states remain available in the details');checks++;
|
||||
await page.getByRole('button',{name:'关闭',exact:true}).click();
|
||||
assert.equal(await page.evaluate(()=>{const s=document.querySelector('.mengsan-hud-center-shuying').getBoundingClientRect(),p=game.me.getBoundingClientRect();return s.bottom<=p.top&&p.top-s.bottom<=13;}),true,'status chips automatically appear just above the portrait');checks++;
|
||||
await page.locator('.mengsan-hud-statuses-shuying button').first().tap();
|
||||
assert.equal(await page.locator('dialog.mengsan-hud-dialog-shuying').count(),1,'short tap opens the status list');checks++;
|
||||
const statusList = await page.locator('.mengsan-hud-detail-body-shuying').evaluate(body => ({
|
||||
rows:body.querySelectorAll('details').length,
|
||||
position:getComputedStyle(body).position,
|
||||
minHeight:Math.min(...[...body.querySelectorAll('summary')].map(row=>row.getBoundingClientRect().height)),
|
||||
height:body.getBoundingClientRect().height,
|
||||
scrollable:body.scrollHeight>body.clientHeight,
|
||||
}));
|
||||
assert.ok(statusList.rows>20&&statusList.position!=='absolute'&&statusList.minHeight>=48&&statusList.height>0&&statusList.scrollable,'status list keeps all entries in normal flow with large tap targets: '+JSON.stringify(statusList));checks++;
|
||||
await page.getByRole('button',{name:'关闭',exact:true}).click();
|
||||
await holdStatus(page,page.locator('.mengsan-hud-statuses-shuying button').first());
|
||||
await page.locator('.mengsan-hud-statuses-shuying button').first().click();
|
||||
assert.ok(await page.locator('dialog.mengsan-hud-dialog-shuying').isVisible());checks++;
|
||||
await page.getByRole('button',{name:'关闭',exact:true}).click();
|
||||
await holdStatus(page,page.locator('.mengsan-hud-statuses-shuying button').first(),'touch');
|
||||
await page.locator('.mengsan-hud-statuses-shuying button').first().tap();
|
||||
assert.ok(await page.locator('dialog.mengsan-hud-dialog-shuying').isVisible(),'touch opens the player status details');checks++;
|
||||
await page.getByRole('button',{name:'关闭',exact:true}).click();
|
||||
await page.evaluate(()=>game.uncheck());
|
||||
|
||||
@@ -8,7 +8,6 @@ import { SLIMED_NAME, createSlimedData } from "../cards/slimed-card.js";
|
||||
import { WOUND_NAME, createWoundData } from "../cards/wound-card.js";
|
||||
import { INFECTION_NAME, createInfectionData } from "../cards/infection-card.js";
|
||||
import { getDiagnostics } from "../../diagnostics/index.js";
|
||||
import { _status } from "../../../../noname.js";
|
||||
|
||||
const statusFactories = {
|
||||
[DAZED_NAME]: createDazedData,
|
||||
@@ -133,17 +132,7 @@ export function mountGMManager(session, owner, piles, battle, resources, game) {
|
||||
diagnostics.info("temporaryCard.added", {
|
||||
cardName: data.name, cardId: id,
|
||||
});
|
||||
try {
|
||||
// check() reuses the choices from before directgain.
|
||||
// Invalidate them without clearing the user's selection.
|
||||
const event = _status.event;
|
||||
if (event?.player === owner) {
|
||||
delete event._cardChoice;
|
||||
delete event._targetChoice;
|
||||
delete event._skillChoice;
|
||||
}
|
||||
game.check();
|
||||
}
|
||||
try { game.check(); }
|
||||
catch (error) {
|
||||
game.log("GM 临时牌已入手,选择状态刷新失败",
|
||||
error.message);
|
||||
|
||||
+11
-43
@@ -69,8 +69,7 @@ export function createHandInput(player, session, adapter, fan, env, refresh, log
|
||||
event.name !== "chooseToUse" || event.player !== player || !event.isMine?.() ||
|
||||
event.skill || event.complexSelect || event.custom?.replace?.card || event.custom?.replace?.target || event.custom?.replace?.confirm ||
|
||||
!env.isActiveCardUse(event, player) || !env.canPayCard?.(player, card) ||
|
||||
(ui.selected.cards.length && (ui.selected.cards.length !== 1 || ui.selected.cards[0] !== card)) ||
|
||||
ui.selected.targets.length || ui.selected.buttons.length ||
|
||||
ui.selected.cards.length || ui.selected.targets.length || ui.selected.buttons.length ||
|
||||
!card.classList.contains("selectable") || card.classList.contains("noclick") ||
|
||||
status.mousedragging || status.dragged || status.paused2) return false;
|
||||
const cards = get.select(event.selectCard);
|
||||
@@ -82,28 +81,19 @@ export function createHandInput(player, session, adapter, fan, env, refresh, log
|
||||
aim = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
aim.classList.add("mengsan-hand-aim-shuying");
|
||||
aim.setAttribute("aria-hidden", "true");
|
||||
aim.setAttribute("preserveAspectRatio", "none");
|
||||
const path = document.createElementNS(aim.namespaceURI, "path");
|
||||
// Keep the SVG in the same zoom/transform space as the real hand.
|
||||
// Android WebView can paint a body-fixed SVG in a different viewport.
|
||||
aim.appendChild(path); ui.arena.appendChild(aim);
|
||||
aim.appendChild(path); document.body.appendChild(aim);
|
||||
}
|
||||
const rect = card.getBoundingClientRect();
|
||||
const x = rect.left + rect.width / 2, y = rect.top + 12;
|
||||
const width = ui.arena.clientWidth, height = ui.arena.clientHeight;
|
||||
aim.setAttribute("viewBox", `0 0 ${width} ${height}`);
|
||||
// Use painted bounds instead of relying on WebView CTM handling of zoom.
|
||||
// With preserveAspectRatio=none, viewport-to-viewBox scaling is linear.
|
||||
const bounds = aim.getBoundingClientRect();
|
||||
if (!(bounds.width > 0 && bounds.height > 0)) {
|
||||
removeAim(); return;
|
||||
}
|
||||
const scaleX = width / bounds.width;
|
||||
const scaleY = height / bounds.height;
|
||||
const start = { x: (x - bounds.left) * scaleX,
|
||||
y: (y - bounds.top) * scaleY };
|
||||
const end = { x: (point.x - bounds.left) * scaleX,
|
||||
y: (point.y - bounds.top) * scaleY };
|
||||
aim.setAttribute("viewBox", `0 0 ${window.innerWidth} ${window.innerHeight}`);
|
||||
// clientX/Y 与卡牌矩形都是视口坐标,SVG 本身却可能受到 body zoom/transform 影响。
|
||||
// 用 SVG 实际屏幕矩阵反变换,不能重复除 documentZoom 或假定 SVG 没有偏移。
|
||||
const matrix = aim.getScreenCTM();
|
||||
if (!matrix) { removeAim(); return; }
|
||||
const inverse = matrix.inverse();
|
||||
const start = new window.DOMPoint(x, y).matrixTransform(inverse);
|
||||
const end = new window.DOMPoint(point.x, point.y).matrixTransform(inverse);
|
||||
if (![start.x, start.y, end.x, end.y].every(Number.isFinite)) { removeAim(); return; }
|
||||
// 十周年金色细线(本地 CONFIG.LINE_COLOR/LINE_WIDTH),不采用尖塔箭头/骨节造型。
|
||||
aim.firstElementChild.setAttribute("d", `M ${start.x} ${start.y} L ${end.x} ${end.y}`);
|
||||
@@ -167,7 +157,7 @@ export function createHandInput(player, session, adapter, fan, env, refresh, log
|
||||
if (!state.moved && Math.hypot(point.x - state.x, point.y - state.y) < 9) return;
|
||||
if (!state.moved) {
|
||||
state.moved = true;
|
||||
if (!ui.selected.cards.includes(state.card)) engineCall(() => ui.click.card.call(state.card));
|
||||
engineCall(() => ui.click.card.call(state.card));
|
||||
state.selected = ui.selected.cards.includes(state.card);
|
||||
const targets = range(state.event);
|
||||
if (!state.selected || !live(state) || !targets) { cleanup(true); return; }
|
||||
@@ -239,28 +229,6 @@ export function createHandInput(player, session, adapter, fan, env, refresh, log
|
||||
for (const type of ["mousedown", "mousemove", "mouseup", "click", "touchstart", "touchmove", "touchend"]) {
|
||||
listen(window, type, compatibility, { capture: true, passive: false });
|
||||
}
|
||||
} else if (adapter.root && status && get) {
|
||||
// Older Android WebViews expose TouchEvent but no PointerEvent.
|
||||
// Route them through the same selection and drawing code.
|
||||
const touchEvent = (event, touch, type) => ({
|
||||
button: 0, isPrimary: true, pointerId: touch.identifier,
|
||||
target: event.target, clientX: touch.clientX, clientY: touch.clientY,
|
||||
type, preventDefault: () => event.preventDefault(),
|
||||
stopImmediatePropagation: () => event.stopImmediatePropagation(),
|
||||
});
|
||||
const options = { capture: true, passive: false };
|
||||
listen(window, "touchstart", event => {
|
||||
if (event.touches.length !== 1) { cancel(); return; }
|
||||
start(touchEvent(event, event.touches[0], "pointerdown"));
|
||||
}, options);
|
||||
for (const [type, handler] of [["touchmove", move], ["touchend", end], ["touchcancel", end]]) {
|
||||
listen(window, type, event => {
|
||||
const touch = Array.from(event.changedTouches).find(item => item.identifier === gesture?.id);
|
||||
if (touch) handler(touchEvent(event, touch, type === "touchcancel" ? "pointercancel" : type));
|
||||
}, options);
|
||||
}
|
||||
listen(window, "click", compatibility, options);
|
||||
listen(window, "blur", cancel);
|
||||
}
|
||||
listen(adapter.root, "pointerover", hover);
|
||||
listen(adapter.root, "pointerleave", () => { if (!gesture) fan.setHover(null); });
|
||||
|
||||
@@ -77,10 +77,10 @@
|
||||
transform-origin: 50% 50% !important;
|
||||
}
|
||||
.mengsan-hand-aim-shuying {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
overflow: visible;
|
||||
|
||||
+24
-142
@@ -55,171 +55,56 @@ export function mountBattleHUD(player, battle, env) {
|
||||
const statuses = create('div', 'mengsan-hud-statuses-shuying', center);
|
||||
statuses.setAttribute('aria-label', '玩家当前状态');
|
||||
const link = create('link', '', null); link.rel = 'stylesheet'; link.href = `${assetBase}/ui/hud/style.css`;
|
||||
let detailTarget = null, detailId = null, detailBody = null;
|
||||
let detailSignature = null;
|
||||
const closeDialog = () => {
|
||||
if (!dialog) return;
|
||||
const current = dialog; dialog = null;
|
||||
detailTarget = null; detailId = null; detailBody = null;
|
||||
detailSignature = null;
|
||||
if (current.open) current.close(); current.remove();
|
||||
if (previousFocus?.isConnected) previousFocus.focus();
|
||||
};
|
||||
const refreshDetails = () => {
|
||||
if (!dialog || !detailBody) return;
|
||||
const entries = collectCombatStatuses(detailTarget, battle);
|
||||
const signature = JSON.stringify(entries);
|
||||
if (signature === detailSignature) return;
|
||||
const expanded = detailSignature === null ? new Set(detailId ? [detailId] : []) :
|
||||
new Set([...detailBody.querySelectorAll('details[open]')].map(row => row.dataset.statusId));
|
||||
detailSignature = signature; detailBody.replaceChildren();
|
||||
if (detailId && !entries.some(entry => entry.id === detailId)) {
|
||||
create('p', '', detailBody, '该状态已失效。');
|
||||
}
|
||||
const openDetails = target => {
|
||||
if (disposed || !battle.session.active) return;
|
||||
closeDialog(); previousFocus = document.activeElement;
|
||||
dialog = create('dialog', 'mengsan-hud-dialog-shuying', document.body);
|
||||
const current = dialog;
|
||||
create('h2', '', dialog, `${nameOf(target)} · 当前状态`);
|
||||
const close = create('button', 'mengsan-hud-dialog-close-shuying', dialog, '关闭'); close.addEventListener('click', closeDialog);
|
||||
const entries = collectCombatStatuses(target, battle);
|
||||
for (const entry of entries) {
|
||||
const row = create('details', 'mengsan-info-row-shuying',
|
||||
detailBody);
|
||||
row.dataset.statusId = entry.id;
|
||||
row.open = expanded.has(entry.id);
|
||||
const summary = create('summary', '', row);
|
||||
if (entry.icon) {
|
||||
const icon = create('img', 'mengsan-relic-icon-shuying', summary);
|
||||
icon.src = `${assetBase}/assets/status/${entry.icon}`; icon.alt = '';
|
||||
}
|
||||
create('strong', '', summary, `${entry.name}${entry.value == null ? '' : ` ${entry.value}${entry.unit ? ` ${entry.unit}` : ''}`}`);
|
||||
const row = create('section', 'mengsan-hud-detail-row-shuying', dialog);
|
||||
create('strong', '', row, `${entry.name}${entry.value == null ? '' : ` ${entry.value}${entry.unit ? ` ${entry.unit}` : ''}`}`);
|
||||
create('p', '', row, entry.detail);
|
||||
}
|
||||
if (!entries.length && !detailId) create('p', '', detailBody, '暂无已登记状态。');
|
||||
};
|
||||
const openDetails = (target, id = null) => {
|
||||
if (disposed || frozen || !battle.session.active) return;
|
||||
closeDialog(); previousFocus = document.activeElement;
|
||||
dialog = create('dialog', 'mengsan-hud-dialog-shuying mengsan-deck-dialog-shuying mengsan-relic-dialog-shuying', document.body);
|
||||
const current = dialog;
|
||||
const header = create('header', 'ms-deck-header', dialog);
|
||||
create('h2', '', header, `${nameOf(target)} · 当前状态`);
|
||||
const close = create('button', 'ms-deck-button', header, '关闭'); close.addEventListener('click', closeDialog);
|
||||
detailTarget = target; detailId = id;
|
||||
detailBody = create('div', 'mengsan-hud-detail-body-shuying mengsan-relic-list-shuying', dialog);
|
||||
refreshDetails();
|
||||
if (!entries.length) create('p', '', dialog, '暂无已登记状态。');
|
||||
// Unknown/native marks retain their engine intro and are never hidden by the HUD.
|
||||
dialog.addEventListener('cancel', event => { event.preventDefault(); closeDialog(); });
|
||||
dialog.addEventListener('close', () => { if (dialog === current) closeDialog(); });
|
||||
for (const type of ['pointerdown', 'pointerup', 'click', 'touchstart', 'touchend', 'keydown']) dialog.addEventListener(type, event => event.stopPropagation());
|
||||
dialog.showModal(); close.focus();
|
||||
};
|
||||
let statusHold = null, statusTimer = null;
|
||||
const cancelStatusHold = () => {
|
||||
if (statusTimer !== null) window.clearTimeout(statusTimer);
|
||||
statusHold?.button.classList.remove('is-pressed');
|
||||
statusHold = null; statusTimer = null;
|
||||
};
|
||||
const statusButton = event => {
|
||||
const button = event.target.closest?.('.mengsan-hud-status-shuying');
|
||||
return button && statuses.contains(button) ? button : null;
|
||||
};
|
||||
const statusDown = event => {
|
||||
cancelStatusHold();
|
||||
const button = statusButton(event);
|
||||
if (!button || event.button !== 0 || disposed || frozen) return;
|
||||
statusHold = { button, id: event.pointerId,
|
||||
x: event.clientX, y: event.clientY };
|
||||
button.classList.add('is-pressed');
|
||||
statusTimer = window.setTimeout(() => {
|
||||
cancelStatusHold();
|
||||
if (button.isConnected) {
|
||||
openDetails(player, button.dataset.statusId || null);
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
const statusMove = event => {
|
||||
if (event.pointerId !== statusHold?.id) return;
|
||||
if (Math.hypot(event.clientX - statusHold.x,
|
||||
event.clientY - statusHold.y) > 8) cancelStatusHold();
|
||||
};
|
||||
const statusUp = event => {
|
||||
if (event.pointerId === statusHold?.id) cancelStatusHold();
|
||||
};
|
||||
const statusListeners = [
|
||||
[statuses, 'pointerdown', statusDown],
|
||||
[statuses, 'scroll', cancelStatusHold],
|
||||
[window, 'pointermove', statusMove, true],
|
||||
[window, 'pointerup', statusUp, true],
|
||||
[window, 'pointercancel', statusUp, true],
|
||||
[window, 'blur', cancelStatusHold],
|
||||
];
|
||||
for (const args of statusListeners) {
|
||||
args[0].addEventListener(...args.slice(1));
|
||||
}
|
||||
const stopStatusInput = () => {
|
||||
cancelStatusHold();
|
||||
for (const args of statusListeners) {
|
||||
args[0].removeEventListener(...args.slice(1));
|
||||
}
|
||||
};
|
||||
statuses.addEventListener('click', event => {
|
||||
event.preventDefault();
|
||||
const button = statusButton(event);
|
||||
// Every entry opens the whole list; precise long presses are optional.
|
||||
if (button && !dialog) {
|
||||
openDetails(player, button.dataset.statusId || null);
|
||||
}
|
||||
});
|
||||
statuses.addEventListener('contextmenu', event => event.preventDefault());
|
||||
let statusSignature = null;
|
||||
function renderStatuses() {
|
||||
const entries = collectCombatStatuses(player, battle);
|
||||
const columns = Math.max(1,
|
||||
Math.floor(((center.clientWidth || 76) + 6) / 38));
|
||||
const columns = layout === 'narrow' ? 1 : 2;
|
||||
const rows = Math.max(1, Math.floor((statuses.clientHeight || 80) / 38));
|
||||
const maximum = Math.max(0, columns * rows - (entries.length > columns * rows ? 1 : 0));
|
||||
const signature = JSON.stringify([maximum, entries]);
|
||||
if (signature !== statusSignature) {
|
||||
statusSignature = signature;
|
||||
statusSignature = signature; statuses.replaceChildren();
|
||||
center.hidden = entries.length === 0;
|
||||
const existing = new Map([...statuses.children].map(button =>
|
||||
[button.dataset.statusId || null, button]));
|
||||
const wantedButtons = [];
|
||||
for (const entry of entries.slice(0, maximum)) {
|
||||
const button = existing.get(entry.id) ||
|
||||
create('button', 'mengsan-hud-status-shuying', null);
|
||||
button.dataset.statusId = entry.id;
|
||||
button.setAttribute('aria-label', `${entry.name} ${entry.value ?? ''} ${entry.unit},点击查看状态列表`);
|
||||
button.setAttribute('aria-haspopup', 'dialog');
|
||||
if (button.dataset.icon !== (entry.icon || '')) {
|
||||
button.dataset.icon = entry.icon || '';
|
||||
button.replaceChildren();
|
||||
button.classList.remove('mengsan-hud-status-fallback-shuying');
|
||||
if (entry.icon) {
|
||||
const image = create('img', '', button);
|
||||
image.src = `${assetBase}/assets/status/${entry.icon}`;
|
||||
image.alt = '';
|
||||
image.addEventListener('error', () => {
|
||||
image.remove();
|
||||
button.classList.add('mengsan-hud-status-fallback-shuying');
|
||||
}, { once: true });
|
||||
}
|
||||
const button = create('button', 'mengsan-hud-status-shuying', statuses);
|
||||
button.setAttribute('aria-label', `${entry.name} ${entry.value ?? ''} ${entry.unit},查看状态详情`);
|
||||
if (entry.icon) {
|
||||
const image = create('img', '', button); image.src = `${assetBase}/assets/status/${entry.icon}`; image.alt = '';
|
||||
image.addEventListener('error', () => image.remove(), { once: true });
|
||||
}
|
||||
wantedButtons.push(button);
|
||||
create('span', '', button, entry.name);
|
||||
if (entry.value != null) create('b', '', button, entry.value);
|
||||
button.addEventListener('click', () => openDetails(player));
|
||||
}
|
||||
if (entries.length > maximum) {
|
||||
const more = existing.get(null) || create('button',
|
||||
'mengsan-hud-status-shuying mengsan-hud-status-more-shuying',
|
||||
null);
|
||||
more.setAttribute('aria-label', '点击查看全部状态');
|
||||
more.setAttribute('aria-haspopup', 'dialog');
|
||||
wantedButtons.push(more);
|
||||
}
|
||||
if (statusHold && !wantedButtons.includes(statusHold.button)) {
|
||||
cancelStatusHold();
|
||||
}
|
||||
for (const button of [...statuses.children]) {
|
||||
if (!wantedButtons.includes(button)) button.remove();
|
||||
}
|
||||
for (const [index, button] of wantedButtons.entries()) {
|
||||
if (statuses.children[index] !== button) {
|
||||
statuses.insertBefore(button, statuses.children[index] || null);
|
||||
}
|
||||
const more = create('button', 'mengsan-hud-status-shuying', statuses, `+${entries.length - maximum}`);
|
||||
more.setAttribute('aria-label', '查看全部状态'); more.addEventListener('click', () => openDetails(player));
|
||||
}
|
||||
}
|
||||
const wanted = new Set();
|
||||
@@ -351,8 +236,7 @@ export function mountBattleHUD(player, battle, env) {
|
||||
text(name, nameOf(player)); text(hp, `生命 ${player.hp} / ${player.maxHp}`); text(block, `格挡 ${player.hujia || 0}`);
|
||||
healthFill.style.width = `${Math.max(0, Math.min(100, player.maxHp > 0 ? player.hp / player.maxHp * 100 : 0))}%`;
|
||||
text(energy, `费用 ${player.storage.mengsanEnergy_shuying ?? 0} / ${player.storage.mengsanMaxEnergy_shuying ?? 0}`);
|
||||
syncActors(); syncExternalEquipment(); renderStatuses();
|
||||
refreshDetails(); syncControls(); syncNotifications();
|
||||
syncActors(); syncExternalEquipment(); renderStatuses(); syncControls(); syncNotifications();
|
||||
overlays.refresh({ footerHeight:hudHeightFor(), status:_status });
|
||||
}
|
||||
function schedule() {
|
||||
@@ -362,7 +246,6 @@ export function mountBattleHUD(player, battle, env) {
|
||||
const timer = window.setInterval(schedule, 200);
|
||||
function freeze() {
|
||||
if(disposed || frozen)return;
|
||||
stopStatusInput();
|
||||
frozen=true;window.clearInterval(timer);
|
||||
if(frame!==null){window.cancelAnimationFrame(frame);frame=null;}
|
||||
observer?.disconnect();resize?.disconnect();closeDialog();overlays.dispose();
|
||||
@@ -370,7 +253,6 @@ export function mountBattleHUD(player, battle, env) {
|
||||
}
|
||||
function dispose() {
|
||||
if (disposed) return; disposed = true;
|
||||
stopStatusInput();
|
||||
window.clearInterval(timer); if (frame !== null) window.cancelAnimationFrame(frame);
|
||||
observer?.disconnect(); resize?.disconnect(); window.removeEventListener('resize', schedule);
|
||||
document.removeEventListener('visibilitychange', schedule);
|
||||
|
||||
@@ -19,16 +19,11 @@
|
||||
.mengsan-hud-health-track-shuying>span { height:100%;background:#9bb78d; }
|
||||
.mengsan-hud-energy-shuying { color:#eacb87;font-size:16px;font-variant-numeric:tabular-nums;white-space:nowrap; }
|
||||
.mengsan-hud-center-shuying { left:40px;right:auto;top:auto;bottom:var(--mengsan-side-dock-bottom);width:var(--mengsan-profile-width);height:var(--mengsan-status-height); }
|
||||
.mengsan-hud-shuying .mengsan-hud-statuses-shuying { display:grid;grid-template-columns:repeat(auto-fill,32px);grid-auto-rows:32px;align-content:end;gap:6px;overflow:hidden;white-space:nowrap;height:100%; }
|
||||
.mengsan-hud-shuying .mengsan-hud-status-shuying { display:flex;align-items:center;justify-content:center;width:32px;height:32px;min-width:0;min-height:32px;padding:2px;background:transparent;border-radius:6px;user-select:none;-webkit-user-select:none;-webkit-touch-callout:none;touch-action:pan-y; }
|
||||
.mengsan-hud-status-shuying img { width:26px;height:26px;object-fit:contain;pointer-events:none; }
|
||||
.mengsan-hud-status-shuying.is-pressed { border-color:#dcc18e;background:#ffffff12; }
|
||||
.mengsan-hud-status-more-shuying::after { content:'\2022\2022\2022';letter-spacing:1px; }
|
||||
.mengsan-hud-status-fallback-shuying::after { content:'';width:12px;height:12px;border:2px solid #dcc18e;transform:rotate(45deg); }
|
||||
/* Equipment Spine effects live on this body canvas (native z-index 7).
|
||||
Keep them above the portrait (25) and below action controls (30).
|
||||
Inline elevations used by awakening animations still take precedence. */
|
||||
body.mengsan-hud-active-shuying>#decadeUI-canvas { z-index:28; }
|
||||
.mengsan-hud-shuying .mengsan-hud-statuses-shuying { display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-rows:32px;align-content:end;gap:6px;overflow:hidden;white-space:nowrap;height:100%; }
|
||||
.mengsan-hud-shuying .mengsan-hud-status-shuying { display:flex;align-items:center;gap:4px;max-width:100%;min-width:0;min-height:32px;padding:3px 6px;background:transparent;border-radius:10px; }
|
||||
.mengsan-hud-status-shuying span { overflow:hidden;text-overflow:ellipsis;font-size:12px; }
|
||||
.mengsan-hud-status-shuying b { font-variant-numeric:tabular-nums;font-size:13px; }
|
||||
.mengsan-hud-status-shuying img { width:22px;height:22px;object-fit:contain; }
|
||||
.mengsan-hud-hand-shuying { position:absolute!important;left:var(--mengsan-hud-left)!important;right:var(--mengsan-hud-right)!important;bottom:68px!important;top:auto!important;width:calc(100% - var(--mengsan-hud-left) - var(--mengsan-hud-right))!important;height:calc(var(--mengsan-hud-height,274px) - 126px)!important;z-index:24!important;background:none!important;border:0!important;transform:none!important;pointer-events:none; }
|
||||
.mengsan-hud-hand-shuying .handcards>.card { pointer-events:auto; }
|
||||
.mengsan-hud-mebg-shuying,.mengsan-hud-hand-shuying>.fakeme.avatar { display:none!important; }
|
||||
@@ -119,6 +114,7 @@ body.mengsan-hud-active-shuying #arena>.mengsan-hud-controls-shuying>.mengsan-hu
|
||||
body.mengsan-hud-active-shuying #arena .mengsan-hud-native-skills-shuying>div { width:100%!important;min-width:0!important;box-sizing:border-box!important;padding:8px 10px!important;min-height:40px!important;font:14px/20px "Microsoft YaHei",sans-serif!important;white-space:normal!important; }
|
||||
[data-mengsan-hud-layout="narrow"] .mengsan-hud-center-shuying { left:22px; }
|
||||
[data-mengsan-hud-layout="short"] .mengsan-hud-shuying .mengsan-hud-statuses-shuying { height:100%; }
|
||||
[data-mengsan-hud-layout="narrow"] .mengsan-hud-shuying .mengsan-hud-statuses-shuying { grid-template-columns:1fr; }
|
||||
.mengsan-hud-prompt-shuying { box-sizing:border-box!important;height:auto!important;min-height:0!important;overflow:auto!important;padding:2px 6px!important;background:none!important;border:0!important;box-shadow:none!important;text-align:center!important;z-index:32!important;color:#f6edcb!important;text-shadow:0 1px 3px #000,0 0 6px #000!important; }
|
||||
body.mengsan-hud-active-shuying .mengsan-hud-card-intro-shuying { box-sizing:border-box!important;z-index:90!important;height:auto!important;min-height:0!important;overflow:auto!important;border-radius:14px!important;background:#202020f5!important;box-shadow:0 8px 24px #0007!important;color:#f4f4f4!important;transition:none!important; }
|
||||
body.mengsan-hud-active-shuying #window>.mengsan-hud-card-intro-shuying { z-index:90!important; }
|
||||
|
||||
@@ -2,7 +2,7 @@ export function mountRelicStrip(root, getRelics, open, createIcon,
|
||||
document, window) {
|
||||
const strip = document.createElement("div");
|
||||
strip.className = "mengsan-hand-relics-shuying";
|
||||
strip.setAttribute("aria-label", "持有遗物,点击查看列表");
|
||||
strip.setAttribute("aria-label", "持有遗物,长按查看描述");
|
||||
const before = root.style.getPropertyValue("--mengsan-relic-space");
|
||||
const priority = root.style.getPropertyPriority("--mengsan-relic-space");
|
||||
let pending = null, timer = null, signature = null, disposed = false;
|
||||
@@ -38,6 +38,7 @@ export function mountRelicStrip(root, getRelics, open, createIcon,
|
||||
const click = event => {
|
||||
stop(event);
|
||||
event.preventDefault();
|
||||
if (event.detail !== 0) return;
|
||||
const button = event.target.closest?.("button");
|
||||
const relic = getRelics().find(item => item.id === button?.dataset.relicId);
|
||||
if (relic) open(relic);
|
||||
@@ -70,7 +71,7 @@ export function mountRelicStrip(root, getRelics, open, createIcon,
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.dataset.relicId = relic.id;
|
||||
button.setAttribute("aria-label", `${relic.name},点击查看遗物列表`);
|
||||
button.setAttribute("aria-label", `${relic.name},长按查看描述`);
|
||||
button.setAttribute("aria-haspopup", "dialog");
|
||||
const fallback = document.createElement("span");
|
||||
fallback.textContent = relic.name;
|
||||
|
||||
+7
-14
@@ -26,29 +26,22 @@ export function mountRelics(session, run, battleRelics, { stripRoot = ui.me } =
|
||||
button.textContent = `遗物 ${relics.length}`;
|
||||
strip?.refresh();
|
||||
if (!list) return;
|
||||
const expanded = new Set([...list.querySelectorAll('details[open]')].map(row => row.dataset.relicId));
|
||||
if (!list.children.length && selected) expanded.add(selected);
|
||||
list.replaceChildren();
|
||||
if (!relics.length) {
|
||||
const empty = document.createElement("p");
|
||||
empty.textContent = "本次征程尚未获得遗物。";
|
||||
list.appendChild(empty);
|
||||
}
|
||||
for (const relic of relics) {
|
||||
const row = document.createElement("details");
|
||||
row.className = "mengsan-info-row-shuying";
|
||||
row.dataset.relicId = relic.id;
|
||||
row.open = expanded.has(relic.id);
|
||||
const summary = document.createElement("summary");
|
||||
const icon=createRelicIcon(relic.image);if(icon)summary.appendChild(icon);
|
||||
const name = document.createElement("strong");
|
||||
for (const relic of selected ? relics.filter(r => r.id === selected) : relics) {
|
||||
const row = document.createElement("section");
|
||||
const icon=createRelicIcon(relic.image);if(icon)row.appendChild(icon);
|
||||
const name = document.createElement("h3");
|
||||
name.textContent = relic.name;
|
||||
summary.appendChild(name);
|
||||
const description = document.createElement("p");
|
||||
description.textContent = relic.description;
|
||||
const status = document.createElement("small");
|
||||
status.textContent = `${relic.pool} · ${relic.tier}${relic.ancient ? ` · ${relic.ancient}` : ""} · ${battleRelics.status(relic)}`;
|
||||
row.append(summary, description, status);
|
||||
row.append(name, description, status);
|
||||
list.appendChild(row);
|
||||
}
|
||||
};
|
||||
@@ -60,11 +53,11 @@ export function mountRelics(session, run, battleRelics, { stripRoot = ui.me } =
|
||||
const currentDialog = dialog;
|
||||
dialog.className = "mengsan-deck-dialog-shuying " +
|
||||
"mengsan-relic-dialog-shuying";
|
||||
dialog.setAttribute("aria-label", "遗物(道具)");
|
||||
dialog.setAttribute("aria-label", relic?.name || "遗物(道具)");
|
||||
const header = document.createElement("header");
|
||||
header.className = "ms-deck-header";
|
||||
const title = document.createElement("h2");
|
||||
title.textContent = "遗物(道具)";
|
||||
title.textContent = relic?.name || "遗物(道具)";
|
||||
const dismiss = document.createElement("button");
|
||||
dismiss.type = "button";
|
||||
dismiss.className = "ms-deck-button";
|
||||
|
||||
@@ -1434,13 +1434,6 @@ body.mengsan-battle-ui-shuying :is(
|
||||
}
|
||||
.mengsan-relic-dialog-shuying { max-width: 620px; }
|
||||
.mengsan-relic-list-shuying { overflow-y: auto; padding: 12px 0; }
|
||||
.mengsan-deck-dialog-shuying .mengsan-relic-list-shuying { position:relative;display:block;flex:1;min-height:0;overscroll-behavior:contain;touch-action:pan-y; }
|
||||
.mengsan-info-row-shuying { display:block;border-bottom:1px solid #b9975d55;padding:8px 0; }
|
||||
.mengsan-info-row-shuying>summary { display:list-item;min-height:48px;padding:8px 10px;cursor:pointer;touch-action:manipulation;color:#ffe0a1;line-height:32px; }
|
||||
.mengsan-info-row-shuying>summary:focus-visible { outline:2px solid #ffe0a1;outline-offset:-2px; }
|
||||
.mengsan-info-row-shuying>summary>.mengsan-relic-icon-shuying { width:32px;height:32px;vertical-align:middle;margin-right:12px; }
|
||||
.mengsan-info-row-shuying>summary>strong { display:inline;vertical-align:middle;overflow-wrap:anywhere; }
|
||||
.mengsan-info-row-shuying>p,.mengsan-info-row-shuying>small { display:block;padding:8px 12px;white-space:pre-line;overflow-wrap:anywhere; }
|
||||
.mengsan-relic-list-shuying section {
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid #b9975d55;
|
||||
|
||||
@@ -17,16 +17,6 @@
|
||||
3. 发布工作流为每个模块生成独立清单,并将清单提交到发布标签。
|
||||
4. 完整 ZIP 安装包仍包含所有模块,供离线或整包安装使用。
|
||||
|
||||
修改 `extension.js` 中的 `shuYingLocalVersion`,提交并推送到 GitHub
|
||||
默认分支后,工作流会比较推送前后的版本号。版本号变化时自动生成和校验
|
||||
清单、创建 `v版本号` 标签,并打包发布;同版本的代码修改不重复发布。
|
||||
手动运行 GitHub 发布工作流也读取该次提交的版本号,无需再次填写版本。
|
||||
|
||||
版本格式为 `2.0.2.4` 或 `2.0.2.4-rc.1`;带 `-rc.N` 的 Release
|
||||
标记为测试版,其他标记为正式版。已有同名标签时停止,避免覆盖旧发布。
|
||||
Gitea 保持接收同一发布标签后打包的流程;使用 Gitea 更新源时,需将该
|
||||
标签同步到 Gitea,单独推送分支代码不会触发 Gitea 发布。
|
||||
|
||||
客户端从标签的 raw 文件路径读取清单。因此 Gitea 发布必须使用已经包含
|
||||
`dist/manifest.json` 和 `dist/modules/*.json` 的同一标签;当前 Gitea 工作流
|
||||
会生成并上传附件,但不会改写触发它的标签。
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Root,
|
||||
[string]$EventName = 'workflow_dispatch',
|
||||
[string]$PreviousRef = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Read-Version([string]$Ref) {
|
||||
$source = git show "${Ref}:extension.js"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Cannot read extension.js at $Ref" }
|
||||
$matches = [regex]::Matches(($source -join "`n"),
|
||||
'(?m)^\s*const\s+shuYingLocalVersion\s*=\s*"([^"\r\n]+)"\s*;')
|
||||
if ($matches.Count -ne 1) { throw "Expected one version at $Ref" }
|
||||
return $matches[0].Groups[1].Value
|
||||
}
|
||||
|
||||
Push-Location -LiteralPath $Root
|
||||
try {
|
||||
$version = Read-Version 'HEAD'
|
||||
if ($version -cnotmatch '^\d+\.\d+\.\d+\.\d+(-rc\.\d+)?$') {
|
||||
throw "Invalid release version: $version"
|
||||
}
|
||||
$changed = $true
|
||||
if ($EventName -eq 'push') {
|
||||
if ($PreviousRef -notmatch '^[0-9a-fA-F]{40}$') {
|
||||
throw 'Push event must provide its previous commit SHA'
|
||||
}
|
||||
if ($PreviousRef -notmatch '^0+$') {
|
||||
$previous = Read-Version $PreviousRef
|
||||
$changed = $previous -ne $version
|
||||
}
|
||||
}
|
||||
if (!$env:GITHUB_OUTPUT -or !$env:GITHUB_ENV) {
|
||||
throw 'Actions output and environment files are required'
|
||||
}
|
||||
$utf8 = New-Object System.Text.UTF8Encoding($false)
|
||||
$release = $changed.ToString().ToLowerInvariant()
|
||||
[IO.File]::AppendAllText($env:GITHUB_OUTPUT,
|
||||
"should_release=$release`n", $utf8)
|
||||
[IO.File]::AppendAllText($env:GITHUB_ENV,
|
||||
"RELEASE_VERSION=$version`nRELEASE_TAG=v$version`n", $utf8)
|
||||
Write-Host "Version: $version; release required: $release"
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -12,7 +12,6 @@ let sessionVerified = new Set();
|
||||
let verificationContext = null;
|
||||
let verificationDirty = false;
|
||||
let fullVerification = false;
|
||||
const progressLayers = new WeakMap();
|
||||
|
||||
function cacheContext() {
|
||||
if (lib.node?.fs && typeof window.__dirname == "string") return `node:${window.__dirname}`;
|
||||
@@ -266,69 +265,12 @@ function setBusy(shuYing, busy) {
|
||||
}
|
||||
|
||||
function ensureProgressNode(shuYing) {
|
||||
const node = shuYing.text;
|
||||
Object.assign(node.style, {
|
||||
position: "fixed", inset: "14px auto auto 50%", margin: "0",
|
||||
maxWidth: "calc(100vw - 28px)", zIndex: "2147483647",
|
||||
});
|
||||
// Native modal dialogs outrank every z-index. A manual popover is non-modal
|
||||
// and joins their top layer without intercepting game input or focus.
|
||||
if (typeof node.showPopover == "function") {
|
||||
if (node.parentNode !== document.body) document.body.appendChild(node);
|
||||
node.setAttribute("popover", "manual");
|
||||
if (node.matches(":popover-open")) node.hidePopover();
|
||||
node.showPopover();
|
||||
} else {
|
||||
// Old WebViews can display progress inside the active modal's top
|
||||
// layer without opening another modal or making the game inert.
|
||||
let layer = progressLayers.get(node);
|
||||
if (!layer) {
|
||||
layer = { dialogs: [], observer: null };
|
||||
layer.sync = (records = []) => {
|
||||
const initial = layer.dialogs.length === 0;
|
||||
const dialogs = [...document.querySelectorAll('dialog[open]')]
|
||||
.filter(dialog => {
|
||||
try { return dialog.matches(':modal'); }
|
||||
catch { return true; }
|
||||
});
|
||||
layer.dialogs = layer.dialogs.filter(dialog =>
|
||||
dialogs.includes(dialog));
|
||||
for (const dialog of dialogs) {
|
||||
if (!layer.dialogs.includes(dialog)) layer.dialogs.push(dialog);
|
||||
}
|
||||
if (initial) {
|
||||
const focused = document.activeElement?.closest('dialog');
|
||||
if (dialogs.includes(focused)) {
|
||||
layer.dialogs = layer.dialogs.filter(dialog =>
|
||||
dialog !== focused);
|
||||
layer.dialogs.push(focused);
|
||||
}
|
||||
}
|
||||
for (const record of records) {
|
||||
if (record.type !== 'attributes' ||
|
||||
!dialogs.includes(record.target)) continue;
|
||||
layer.dialogs = layer.dialogs.filter(dialog =>
|
||||
dialog !== record.target);
|
||||
layer.dialogs.push(record.target);
|
||||
}
|
||||
const host = layer.dialogs[layer.dialogs.length - 1] ||
|
||||
document.body;
|
||||
if (node.parentNode !== host) host.appendChild(node);
|
||||
};
|
||||
layer.observer = new MutationObserver(layer.sync);
|
||||
layer.observer.observe(document.body, {
|
||||
childList: true, subtree: true,
|
||||
attributes: true, attributeFilter: ['open'],
|
||||
});
|
||||
progressLayers.set(node, layer);
|
||||
}
|
||||
layer.sync();
|
||||
if (!shuYing.text.parentNode) {
|
||||
document.body.appendChild(shuYing.text);
|
||||
}
|
||||
}
|
||||
|
||||
function removeProgressNode(shuYing) {
|
||||
progressLayers.get(shuYing.text)?.observer.disconnect();
|
||||
progressLayers.delete(shuYing.text);
|
||||
if (shuYing.text.parentNode) {
|
||||
shuYing.text.parentNode.removeChild(shuYing.text);
|
||||
}
|
||||
@@ -414,8 +356,6 @@ function setProgress(shuYing, options = {}) {
|
||||
const now = Date.now();
|
||||
if (!options.force && now - (shuYing._progressRendered || 0) < 120) return;
|
||||
shuYing._progressRendered = now;
|
||||
// Re-promote after any Mengsan modal opened since the previous update.
|
||||
ensureProgressNode(shuYing);
|
||||
|
||||
const current = Math.max(0, Number(options.current) || 0);
|
||||
const total = Math.max(0, Number(options.total) || 0);
|
||||
|
||||
Reference in New Issue
Block a user