@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 +57 -8
- package/describe.json +8 -0
- package/dist/webui.js +272 -4
- package/overlay/README.md +4 -0
- package/overlay/overlay-webview.ps1 +260 -0
- package/overlay/webview2/LICENSE.txt +27 -0
- package/overlay/webview2/Microsoft.Web.WebView2.Core.dll +0 -0
- package/overlay/webview2/Microsoft.Web.WebView2.Wpf.dll +0 -0
- package/overlay/webview2/NOTICE.txt +85 -0
- package/overlay/webview2/WebView2Loader.dll +0 -0
- package/overlay-electron/launch.js +43 -30
- package/overlay-electron/main.js +104 -34
- package/overlay-electron/renderer.js +39 -11
- package/overlay-electron/zorder.ps1 +64 -0
- package/package.json +2 -7
- package/src/catalog.js +3 -0
- package/src/farm.js +8 -0
- package/src/index.js +22 -1
- package/src/paths.js +2 -1
- package/src/world.js +313 -54
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
|
-
-
|
|
86
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
|
23
|
+
const refreshFarmConfigUi = () => {
|
|
24
24
|
renameFarmConfigTab();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|