@lily-cooperative/custom-farm 0.36.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,7 +21,7 @@
21
21
  | 面板 | 能改什么 |
22
22
  |---|---|
23
23
  | **农场自定义 → 农场** | 地面贴图、地表高度、农田大小、导入自己的 PNG |
24
- | **农场自定义 → 农作物** | 增删作物、阶段数、每阶段天数、产物卖价、种子买价、各阶段/种子/产物贴图 |
24
+ | **农场自定义 → 农作物** | 增删作物、阶段数、每阶段天数、产物卖价、种子买价、各阶段/种子/产物贴图、上下图层 |
25
25
  | **农场自定义 → 杂草** | 增删杂草、阶段与天数、出现权重、上下图层、收获物名字/价值/贴图 |
26
26
  | **农场自定义 → 浇水特效 / 枯萎** | 浇水帧序列、水花帧序列、帧间隔、枯萎贴图 |
27
27
  | **生态自定义** | 一个游戏日多长、离线最多补几天、刷新间隔、每天是否推事件、浇水蒸发/补水/枯萎天数、天气 |
@@ -61,6 +61,8 @@
61
61
 
62
62
  扩展包自带 `overlay` / `overlay-electron` / `assets`,第一次启动会把它们复制到安装目录,不需要另外准备项目文件夹。
63
63
 
64
+ **扩展不再把 Electron 作为 npm 依赖下载。** 安装时只装扩展本身;有没有 Electron 运行时都不会影响安装。桌面图层按下面的「环境自检 / 图层引擎」选择。
65
+
64
66
  ### 本地开发
65
67
 
66
68
  ```powershell
@@ -71,7 +73,31 @@ powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install.ps1
71
73
  Invoke-RestMethod -Method Post http://127.0.0.1:17788/api/run/restart
72
74
  ```
73
75
 
74
- ## 安装路径
76
+ ## 游戏配置
77
+
78
+ ### 环境自检 / 图层引擎
79
+
80
+ **游戏配置页顶部**有「环境自检」按钮,会检查:
81
+
82
+ - 当前平台和 Node 版本
83
+ - 是否能找到 `electron.exe`
84
+ - 安装目录里的 `overlay-webview.ps1`、WebView2 库和 WebView2 运行时是否可用
85
+
86
+ 自检会明确显示「缺少 Electron 运行时」还是「WPF/WebView2 图层可用」。下面的「图层引擎」切选单可以选:
87
+
88
+ - **自动**:有备用 Electron 就用,没有就用 WPF/WebView2(推荐,不会下载 Electron)
89
+ - **备用 Electron**:只使用检测到的 Electron,需要 `electron.exe`
90
+ - **WPF(WebView2)**:完全不使用 Electron,但加载和 Electron 相同的 `renderer.html/renderer.js`
91
+
92
+ WPF 回退现在是 WebView2 宿主,不再用另一套 WPF 自绘;因此缩放、精灵位置、阴影、粒子、水滴、晃动等视觉效果和 Electron 基本完全一致。需要 Windows 10/11 自带的 WebView2 Runtime。
93
+
94
+ 自检里的「Electron 包」下拉会列出检测到的路径;也可以在「备用 Electron」配置项里手动选 `electron.exe`。
95
+
96
+ 第一次启用时还会自动做一次自检;如果当前引擎缺运行时,会推一次 `farm.env` 事件,让桌宠把“缺少 Electron 运行时 / 怎么修”转告主人。之后不会重复提醒。
97
+
98
+ Coopanion 只提供打包后的 `Coopanion.exe`,没有可复用的独立 `electron.exe`;所以默认不再下载第二份 Electron。备用 Electron 可以放在安装目录的 `node_modules\electron\dist\electron.exe`,或在「备用 Electron」里手动选择路径。
99
+
100
+ ### 安装路径
75
101
 
76
102
  在 Coopanion 的 **游戏配置 → 安装** 里可以直接看到和修改安装路径:
77
103
 
@@ -80,12 +106,32 @@ Invoke-RestMethod -Method Post http://127.0.0.1:17788/api/run/restart
80
106
  - 存档和自定义配置仍在 `data\home\companion\data\farm\`,不会被搬走
81
107
  - 安装目录里的 `overlay\runtime\` 是运行痕迹,可以清;`assets\sprites\cust_*.png` 是你上传的贴图
82
108
 
109
+ ### 显示器
110
+
111
+ **游戏配置 → 显示器** 可以选择图层放在哪块屏幕:
112
+
113
+ - 「自动」使用主屏
114
+ - 下拉里会列出当前所有显示器,改完约 1 秒内移动到目标屏幕
115
+ - 这个设置作用于 Electron 图层;WPF/WebView2 回退使用主屏(与现有 WPF 行为一致)
116
+
117
+ ### 备份与重置
118
+
119
+ **游戏配置** 页底部有「存档与重置」:
120
+
121
+ - **备份存档**:把 `state.json` 和 `custom.json` 复制到 `data\farm\backups\backup-时间\`
122
+ - **重置一切**:先自动备份,再清空 `state.json` 和 `custom.json`,World 自动重启并生成默认目录;安装路径和已上传的 PNG 文件不会被删除
123
+
124
+ 也可以从「存档与重置」页签操作同样的按钮。
125
+
83
126
  ## 桌面图层
84
127
 
85
- - **Windows 10/11**:默认 Electron/HTML 图层;Electron 不可用时回退 WPF。
86
- - **macOS / Linux**:核心 World、WebUI 和 Electron 图层可用;没有 Windows WPF 回退。
128
+ - 图层拆成 `bg` / `top` 两个透明窗口:`bg` 在桌宠下面,`top` 是桌宠的 owned window,永远在桌宠上面。
129
+ - 野草、装饰、作物、掉落物和特效按 `layer` 设置分配到两个窗口;上层野草/装饰可以正常遮挡桌宠,下层作物在桌宠后面。
130
+ - 层级守护每 500ms 重新确认桌宠窗口;桌宠重新置顶、显示/隐藏、被点击或窗口重建后,会自动重新挂 owner / 把 `bg` 放回桌宠下面,防止图层整体翻转。
131
+ - **Windows 10/11**:默认「自动」——检测到备用 Electron 就用 Electron,否则用 WPF/WebView2;不会为了安装扩展下载 Electron。两个引擎都使用同一套 Canvas renderer,缩放和特效一致。
132
+ - **macOS / Linux**:暂不支持;`os` 已收紧为 `win32`。
87
133
  - 图层跟着桌宠显示/隐藏/退出;任务栏换边、换屏会重新计算位置。
88
- - 独立 WPF 背包已移除;背包、商店、农场预览、摆放和手动合成都在 WebUI。
134
+ - 背包、商店、农场预览、摆放和手动合成都在 WebUI。
89
135
 
90
136
  ## 工具
91
137
 
@@ -105,7 +151,7 @@ Invoke-RestMethod -Method Post http://127.0.0.1:17788/api/run/restart
105
151
  | `farm_sell` | 卖背包里的道具 |
106
152
  | `farm_place` | 把装饰放到地上 |
107
153
 
108
- 事件:`farm.ripe`、`farm.dry`、`farm.day`、`farm.state`。`farm.day` 默认关,`dailyEvent=true` 才推。
154
+ 事件:`farm.ripe`、`farm.dry`、`farm.day`、`farm.state`、`farm.env`。`farm.day` 默认关,`dailyEvent=true` 才推;`farm.env` 只在第一次启用自检失败时推一次,让桌宠把环境问题转告主人。
109
155
 
110
156
  ## 默认参数
111
157
 
@@ -136,7 +182,7 @@ Invoke-RestMethod -Method Post http://127.0.0.1:17788/api/run/restart
136
182
  | `dist/webui.js` / `.css` | 「自定义农场」面板 |
137
183
  | `icon.png` | 扩展图标(安装页显示) |
138
184
  | `describe.json` | 工具/事件自描述 |
139
- | `overlay/` | WPF 图层回退 |
185
+ | `overlay/` | WPF/WebView2 回退(同一套渲染器) |
140
186
  | `overlay-electron/` | Electron 图层 |
141
187
  | `assets/` | 内置作物/杂草/地面/装饰/图标 |
142
188
 
@@ -150,7 +196,10 @@ Invoke-RestMethod -Method Post http://127.0.0.1:17788/api/run/restart
150
196
 
151
197
  - Coopanion `>= 0.2.13`,World API `6`
152
198
  - Node.js `>= 22`(Coopanion 自带)
153
- - Electron `^45.1.0`(桌面图层依赖)
199
+ - 仅 Windows 10/11;macOS / Linux 暂不支持,等后续测试
200
+ - Electron 不是 npm 依赖;备用 Electron 可选,Windows 没有时用 WPF/WebView2
201
+ - WPF 回退需要 Windows 10/11 的 WebView2 Runtime(系统一般自带)
202
+ - Windows 10/11 已实机测试;macOS / Linux 未实机测试
154
203
 
155
204
  ## 许可
156
205
 
package/describe.json CHANGED
@@ -340,6 +340,14 @@
340
340
  "en": "A custom state triggered a consume action: e.g. hunger ate a crop (the text says what and the state change), or asks the pet to use farm_consume; also fires when nothing edible is in the bag."
341
341
  },
342
342
  "trigger": "debounce"
343
+ },
344
+ {
345
+ "type": "farm.env",
346
+ "description": {
347
+ "zh": "第一次启用时环境自检发现 Electron/WPF 运行时缺失;正文说明缺什么、怎么在「游戏配置 → 环境自检」里修复,让桌宠转告主人。",
348
+ "en": "The first-start environment check found a missing Electron/WPF runtime; the text says what is missing and how to fix it in Game Config → Environment check, so the pet can tell its owner."
349
+ },
350
+ "trigger": "debounce"
343
351
  }
344
352
  ]
345
353
  }
package/dist/webui.js CHANGED
@@ -20,16 +20,209 @@ function renameFarmConfigTab() {
20
20
  } catch { /* 不是浏览器环境就算了 */ }
21
21
  }
22
22
  if (typeof document !== 'undefined') {
23
- const start = () => {
23
+ const refreshFarmConfigUi = () => {
24
24
  renameFarmConfigTab();
25
- new MutationObserver(renameFarmConfigTab).observe(document.body, { childList: true, subtree: true, characterData: true });
26
- window.addEventListener('hashchange', renameFarmConfigTab);
27
- setInterval(renameFarmConfigTab, 1000);
25
+ injectFarmEnvCheck();
26
+ injectFarmConfigActions();
27
+ };
28
+ const start = () => {
29
+ refreshFarmConfigUi();
30
+ new MutationObserver(refreshFarmConfigUi).observe(document.body, { childList: true, subtree: true, characterData: true });
31
+ window.addEventListener('hashchange', refreshFarmConfigUi);
32
+ setInterval(refreshFarmConfigUi, 1000);
28
33
  };
29
34
  if (document.body) start();
30
35
  else document.addEventListener('DOMContentLoaded', start, { once: true });
31
36
  }
32
37
 
38
+ /** 调 World 的 manage 面板:备份 / 重置 / 自检。 */
39
+ async function farmManageCall(method, args = []) {
40
+ const res = await fetch('/api/webui/providers/' + encodeURIComponent('world:farm') + '/panels/manage/' + method, {
41
+ method: 'POST',
42
+ headers: { 'content-type': 'application/json' },
43
+ body: JSON.stringify({ args }),
44
+ });
45
+ const data = await res.json().catch(() => ({}));
46
+ if (!res.ok) throw new Error(data.error || ('HTTP ' + res.status));
47
+ return data;
48
+ }
49
+
50
+ /** 游戏配置顶部的环境自检 + Electron/WPF 切选单。 */
51
+ function injectFarmEnvCheck() {
52
+ try {
53
+ const hash = String(location.hash || '');
54
+ if (!hash.includes('world%3Afarm') && !hash.includes('world:farm')) return;
55
+ const view = document.querySelector('.configview');
56
+ if (!view || view.querySelector('[data-farm-env]')) return;
57
+
58
+ const section = document.createElement('div');
59
+ section.className = 'tsection';
60
+ section.dataset.farmEnv = '1';
61
+ section.textContent = '环境自检';
62
+
63
+ const desc = document.createElement('div');
64
+ desc.className = 'tsecdesc';
65
+ desc.textContent = '检查图层运行时是否齐全。Coopanion 不提供独立的 electron.exe;没有备用 Electron 时会用 WPF 图层,不会为了安装扩展再下载 Electron。';
66
+
67
+ const row = document.createElement('div');
68
+ row.className = 'trow';
69
+ const label = document.createElement('span');
70
+ label.className = 'tlabel';
71
+ label.textContent = '图层引擎';
72
+ const field = document.createElement('div');
73
+ field.className = 'tfield';
74
+ field.style.display = 'flex';
75
+ field.style.gap = '8px';
76
+ field.style.flexWrap = 'wrap';
77
+
78
+ const select = document.createElement('select');
79
+ select.className = 'field';
80
+ for (const [value, text] of [['auto', '自动(有备用 Electron 就用,否则 WPF/WebView2)'], ['electron', '备用 Electron(需要 electron.exe)'], ['wpf', 'WPF/WebView2(不需要 Electron)']]) {
81
+ const opt = document.createElement('option');
82
+ opt.value = value;
83
+ opt.textContent = text;
84
+ select.appendChild(opt);
85
+ }
86
+ const checkBtn = document.createElement('button');
87
+ checkBtn.type = 'button';
88
+ checkBtn.className = 'btn sm primary';
89
+ checkBtn.textContent = '环境自检';
90
+ const electronSelect = document.createElement('select');
91
+ electronSelect.className = 'field';
92
+ const electronAuto = document.createElement('option');
93
+ electronAuto.value = '';
94
+ electronAuto.textContent = 'Electron:自动查找';
95
+ electronSelect.appendChild(electronAuto);
96
+ const status = document.createElement('div');
97
+ status.className = 'tdesc';
98
+ status.style.whiteSpace = 'pre-line';
99
+ status.style.marginTop = '6px';
100
+
101
+ const render = (data) => {
102
+ if (!data) return;
103
+ if (data.engine) select.value = data.engine;
104
+ const current = data.electron && data.electron.path ? data.electron.path : '';
105
+ electronSelect.replaceChildren(electronAuto);
106
+ electronAuto.textContent = 'Electron:自动查找';
107
+ for (const row of data.electronCandidates || []) {
108
+ const opt = document.createElement('option');
109
+ opt.value = row.path;
110
+ opt.textContent = 'Electron:' + row.label + (row.ok ? '' : '(缺失)');
111
+ opt.disabled = !row.ok;
112
+ electronSelect.appendChild(opt);
113
+ }
114
+ if (current) electronSelect.value = current;
115
+ if (!current && !(data.electronCandidates || []).some((r) => r.ok)) {
116
+ const opt = document.createElement('option');
117
+ opt.value = '';
118
+ opt.textContent = 'Electron:未找到';
119
+ opt.disabled = true;
120
+ electronSelect.appendChild(opt);
121
+ }
122
+ status.textContent = data.message || data.text || '';
123
+ status.style.color = data.ok ? '' : 'var(--extension-alert, #c0392b)';
124
+ };
125
+ const check = async () => {
126
+ checkBtn.disabled = true;
127
+ try { render(await farmManageCall('check')); } catch (err) { status.textContent = '自检失败:' + ((err && err.message) || err); } finally { checkBtn.disabled = false; }
128
+ };
129
+ checkBtn.addEventListener('click', check);
130
+ select.addEventListener('change', async () => {
131
+ status.textContent = '正在切换图层引擎…';
132
+ try {
133
+ render(await farmManageCall('setEngine', [select.value]));
134
+ } catch (err) {
135
+ status.textContent = '切换失败:' + ((err && err.message) || err);
136
+ }
137
+ });
138
+ electronSelect.addEventListener('change', async () => {
139
+ status.textContent = '正在切换 Electron…';
140
+ try {
141
+ render(await farmManageCall('setElectronPath', [electronSelect.value]));
142
+ } catch (err) {
143
+ status.textContent = '切换失败:' + ((err && err.message) || err);
144
+ }
145
+ });
146
+
147
+ field.append(select, electronSelect, checkBtn);
148
+ row.append(label, field);
149
+ view.prepend(section, desc, row, status);
150
+ void check();
151
+ } catch { /* 不是宿主的配置页就算了 */ }
152
+ }
153
+
154
+ /** 往宿主的「游戏配置」页里塞「存档与重置」按钮。 */
155
+ function injectFarmConfigActions() {
156
+ try {
157
+ const hash = String(location.hash || '');
158
+ if (!hash.includes('world%3Afarm') && !hash.includes('world:farm')) return;
159
+ const view = document.querySelector('.configview');
160
+ if (!view || view.querySelector('[data-farm-manage]')) return;
161
+
162
+ const section = document.createElement('div');
163
+ section.className = 'tsection';
164
+ section.dataset.farmManage = '1';
165
+ section.textContent = '存档与重置';
166
+
167
+ const desc = document.createElement('div');
168
+ desc.className = 'tsecdesc';
169
+ desc.textContent = '备份会复制 state.json 和 custom.json 到 data/farm/backups;重置会先备份,再清空存档和全部自定义设置,World 随后重启。';
170
+
171
+ const row = document.createElement('div');
172
+ row.className = 'trow';
173
+ const label = document.createElement('span');
174
+ label.className = 'tlabel';
175
+ label.textContent = '操作';
176
+ const field = document.createElement('div');
177
+ field.className = 'tfield';
178
+ field.style.display = 'flex';
179
+ field.style.gap = '8px';
180
+ field.style.flexWrap = 'wrap';
181
+ const status = document.createElement('span');
182
+ status.className = 'tdesc';
183
+ status.setAttribute('role', 'status');
184
+
185
+ const button = (text, primary) => {
186
+ const b = document.createElement('button');
187
+ b.type = 'button';
188
+ b.className = 'btn sm' + (primary ? ' primary' : '');
189
+ b.textContent = text;
190
+ return b;
191
+ };
192
+ const backup = button('备份存档', true);
193
+ const reset = button('重置一切', false);
194
+
195
+ backup.addEventListener('click', async () => {
196
+ backup.disabled = true;
197
+ status.textContent = '正在备份…';
198
+ try {
199
+ const r = await farmManageCall('backup');
200
+ status.textContent = r.text || '已备份';
201
+ } catch (err) {
202
+ status.textContent = '备份失败:' + ((err && err.message) || err);
203
+ } finally { backup.disabled = false; }
204
+ });
205
+
206
+ reset.addEventListener('click', async () => {
207
+ if (!window.confirm('确定重置一切?会清空存档和全部自定义设置(会先自动备份)。')) return;
208
+ reset.disabled = true;
209
+ status.textContent = '正在重置…';
210
+ try {
211
+ const r = await farmManageCall('reset');
212
+ status.textContent = r.text || '已重置,World 正在重启…';
213
+ setTimeout(() => { try { location.reload(); } catch { /* ignore */ } }, 3000);
214
+ } catch (err) {
215
+ status.textContent = '重置失败:' + ((err && err.message) || err);
216
+ reset.disabled = false;
217
+ }
218
+ });
219
+
220
+ field.append(backup, reset, status);
221
+ row.append(label, field);
222
+ view.append(section, desc, row);
223
+ } catch { /* 不是宿主的配置页就算了 */ }
224
+ }
225
+
33
226
  const TEXT = {
34
227
  zh: {
35
228
  title: '农场',
@@ -84,6 +277,9 @@ const TEXT = {
84
277
  weedLayer: '图层',
85
278
  weedLayerTop: '上(和作物/桌宠同前台层)',
86
279
  weedLayerBottom: '下(背景层,作物后方)',
280
+ cropLayer: '图层',
281
+ cropLayerTop: '上(和桌宠同前台层)',
282
+ cropLayerBottom: '下(背景层,桌宠后方)',
87
283
  addWeed: '添加杂草',
88
284
  name: '名字',
89
285
  stages: '阶段数',
@@ -188,6 +384,9 @@ const TEXT = {
188
384
  weedLayer: 'Layer',
189
385
  weedLayerTop: 'Top (foreground with crops/pet)',
190
386
  weedLayerBottom: 'Bottom (background, behind crops)',
387
+ cropLayer: 'Layer',
388
+ cropLayerTop: 'Top (foreground with pet)',
389
+ cropLayerBottom: 'Bottom (background, behind pet)',
191
390
  addWeed: 'Add weed',
192
391
  name: 'Name',
193
392
  stages: 'Stages',
@@ -483,6 +682,7 @@ function panel(ctx) {
483
682
  preview: [...c.preview],
484
683
  previewSeed: c.previewSeed,
485
684
  previewItem: c.previewItem,
685
+ layer: c.layer === 'top' ? 'top' : 'bottom',
486
686
  };
487
687
  const details = ui.h('details', 'cf-card');
488
688
  if (!c.builtin) details.open = false;
@@ -493,10 +693,20 @@ function panel(ctx) {
493
693
  ui.pill(c.builtin ? T.builtin : T.custom, c.builtin ? 'plain' : 'on'),
494
694
  ui.chip(c.stages + ' 阶段', 'plain'),
495
695
  ui.chip(T.daysTotal(c.totalDays), 'plain'),
696
+ ui.chip(c.layer === 'top' ? T.cropLayerTop : T.cropLayerBottom, c.layer === 'top' ? 'on' : 'plain'),
496
697
  );
497
698
  const body = ui.h('div', 'cf-body');
498
699
  details.append(summary, body);
499
700
 
701
+ const layerSel = ui.select({
702
+ value: draft.layer,
703
+ options: [
704
+ { value: 'top', label: T.cropLayerTop },
705
+ { value: 'bottom', label: T.cropLayerBottom },
706
+ ],
707
+ });
708
+ layerSel.addEventListener('change', () => { draft.layer = layerSel.value; }, { signal: ctx.signal });
709
+
500
710
  const row1 = ui.h('div', 'cf-row');
501
711
  row1.append(
502
712
  ui.field(T.name, textInput(draft.name, (v) => { draft.name = v; title.textContent = v || c.key; })),
@@ -506,6 +716,7 @@ function panel(ctx) {
506
716
  resizeSprites(draft);
507
717
  paintStages();
508
718
  })),
719
+ ui.field(T.cropLayer, layerSel),
509
720
  );
510
721
 
511
722
  const stagesBox = ui.h('div', 'cf-stages');
@@ -566,6 +777,7 @@ function panel(ctx) {
566
777
  sprites: draft.sprites,
567
778
  seedSprite: draft.seedSprite,
568
779
  itemSprite: draft.itemSprite,
780
+ layer: draft.layer,
569
781
  }], { toast: T.saved });
570
782
  off.dispose();
571
783
  })(),
@@ -2700,8 +2912,64 @@ function ecologyPanel(ctx) {
2700
2912
  reload();
2701
2913
  }
2702
2914
 
2915
+ function managePanel(ctx) {
2916
+ const { ui } = ctx;
2917
+ const root = ui.h('div', 'cf-root');
2918
+ ctx.root.appendChild(root);
2919
+ const sheet = ui.sheet({
2920
+ title: '存档与重置',
2921
+ desc: '备份会复制 state.json 和 custom.json 到 data/farm/backups;重置会先备份,再清空存档和全部自定义设置,World 随后重启。',
2922
+ });
2923
+ const bar = ui.rowbar();
2924
+ const status = ui.h('p', 'cf-note', '');
2925
+ const check = ui.button('环境自检', {
2926
+ onClick: async () => {
2927
+ check.disabled = true;
2928
+ status.textContent = '正在检查…';
2929
+ try {
2930
+ const r = await ctx.invoke('check');
2931
+ status.textContent = r.message || r.text || '';
2932
+ } catch (err) {
2933
+ status.textContent = '自检失败:' + ((err && err.message) || err);
2934
+ } finally { check.disabled = false; }
2935
+ },
2936
+ });
2937
+ const backup = ui.button('备份存档', {
2938
+ variant: 'primary',
2939
+ onClick: async () => {
2940
+ backup.disabled = true;
2941
+ status.textContent = '正在备份…';
2942
+ try {
2943
+ const r = await ctx.invoke('backup');
2944
+ status.textContent = r.text || '已备份';
2945
+ } catch (err) {
2946
+ status.textContent = '备份失败:' + ((err && err.message) || err);
2947
+ } finally { backup.disabled = false; }
2948
+ },
2949
+ });
2950
+ const reset = ui.button('重置一切', {
2951
+ onClick: async () => {
2952
+ if (!window.confirm('确定重置一切?会清空存档和全部自定义设置(会先自动备份)。')) return;
2953
+ reset.disabled = true;
2954
+ status.textContent = '正在重置…';
2955
+ try {
2956
+ const r = await ctx.invoke('reset');
2957
+ status.textContent = r.text || '已重置,World 正在重启…';
2958
+ setTimeout(() => { try { location.reload(); } catch { /* ignore */ } }, 3000);
2959
+ } catch (err) {
2960
+ status.textContent = '重置失败:' + ((err && err.message) || err);
2961
+ reset.disabled = false;
2962
+ }
2963
+ },
2964
+ });
2965
+ bar.append(check, backup, reset);
2966
+ sheet.body.append(bar, status);
2967
+ root.appendChild(sheet.el);
2968
+ }
2969
+
2703
2970
  export default {
2704
2971
  panels: {
2972
+ manage: { mount: managePanel },
2705
2973
  bag: { mount: bagPanel },
2706
2974
  shop: { mount: shopPanel },
2707
2975
  customize: { mount: panel },
package/overlay/README.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # 桌面图层(overlay)· 使用说明
2
2
 
3
+ > 运行时 WPF 回退已改为 WebView2 宿主 `overlay-webview.ps1`,加载与 Electron 相同的
4
+ > `overlay-electron/renderer.html` / `renderer.js`;本文档描述的是保留的旧 WPF 自绘
5
+ > 实现(`overlay.ps1`),正常启动不再使用它。
6
+ >
3
7
  > v4:作物种在任务栏上沿的全宽透明条带上;统一缩放、掉落物、摇晃、浇水/枯萎特效、
4
8
  > 收割的作物**不发光**。图层只渲染,状态全从 `state.json` 读。
5
9