@lily-cooperative/custom-farm 0.38.0 → 0.38.1
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 +2 -3
- package/dist/webui.js +3 -58
- package/overlay/overlay-webview.ps1 +13 -3
- package/overlay-electron/main.js +9 -1
- package/overlay-electron/zorder.ps1 +12 -6
- package/package.json +1 -1
- package/src/farm.js +2 -0
- package/src/index.js +8 -0
- package/src/world.js +58 -36
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
| **状态自定义** | 数值状态、开关状态、条件规则、消耗行为(例如饥饿时自动吃背包里的东西) |
|
|
30
30
|
| **道具自定义** | 种子/农作物/消耗品/装饰的买价卖价、能不能吃、吃掉后的状态效果、图标、摆放贴图、图层、缩放 |
|
|
31
31
|
| **合成表** | 1~4 个材料合成 1 个产物,桌宠可以用 `farm_craft` 合成 |
|
|
32
|
+
| **性能** | 图层节流:不节流(默认)/ 自动 / 节流;改完自动重启图层 |
|
|
32
33
|
| **设置模板 ZIP** | 导出/导入全部设置和自定义贴图(不含存档) |
|
|
33
34
|
| **环境提示词** | 发给桌宠的静态提示词,写“饿了怎么办、什么时候提醒主人”等 |
|
|
34
35
|
|
|
@@ -116,13 +117,11 @@ Coopanion 只提供打包后的 `Coopanion.exe`,没有可复用的独立 `elec
|
|
|
116
117
|
|
|
117
118
|
### 备份与重置
|
|
118
119
|
|
|
119
|
-
**游戏配置**
|
|
120
|
+
**游戏配置** 页最上面有「存档与重置」,不再单独占页签:
|
|
120
121
|
|
|
121
122
|
- **备份存档**:把 `state.json` 和 `custom.json` 复制到 `data\farm\backups\backup-时间\`
|
|
122
123
|
- **重置一切**:先自动备份,再清空 `state.json` 和 `custom.json`,World 自动重启并生成默认目录;安装路径和已上传的 PNG 文件不会被删除
|
|
123
124
|
|
|
124
|
-
也可以从「存档与重置」页签操作同样的按钮。
|
|
125
|
-
|
|
126
125
|
## 桌面图层
|
|
127
126
|
|
|
128
127
|
- 图层拆成 `bg` / `top` 两个透明窗口:`bg` 在桌宠下面,`top` 是桌宠的 owned window,永远在桌宠上面。
|
package/dist/webui.js
CHANGED
|
@@ -37,7 +37,7 @@ if (typeof document !== 'undefined') {
|
|
|
37
37
|
|
|
38
38
|
/** 调 World 的 manage 面板:备份 / 重置 / 自检。 */
|
|
39
39
|
async function farmManageCall(method, args = []) {
|
|
40
|
-
const res = await fetch('/api/webui/providers/' + encodeURIComponent('world:farm') + '/panels/
|
|
40
|
+
const res = await fetch('/api/webui/providers/' + encodeURIComponent('world:farm') + '/panels/customize/' + method, {
|
|
41
41
|
method: 'POST',
|
|
42
42
|
headers: { 'content-type': 'application/json' },
|
|
43
43
|
body: JSON.stringify({ args }),
|
|
@@ -219,7 +219,7 @@ function injectFarmConfigActions() {
|
|
|
219
219
|
|
|
220
220
|
field.append(backup, reset, status);
|
|
221
221
|
row.append(label, field);
|
|
222
|
-
view.
|
|
222
|
+
view.prepend(section, desc, row);
|
|
223
223
|
} catch { /* 不是宿主的配置页就算了 */ }
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -2912,64 +2912,8 @@ function ecologyPanel(ctx) {
|
|
|
2912
2912
|
reload();
|
|
2913
2913
|
}
|
|
2914
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
|
-
|
|
2970
2915
|
export default {
|
|
2971
2916
|
panels: {
|
|
2972
|
-
manage: { mount: managePanel },
|
|
2973
2917
|
bag: { mount: bagPanel },
|
|
2974
2918
|
shop: { mount: shopPanel },
|
|
2975
2919
|
customize: { mount: panel },
|
|
@@ -2978,3 +2922,4 @@ export default {
|
|
|
2978
2922
|
items: { mount: itemsPanel },
|
|
2979
2923
|
},
|
|
2980
2924
|
};
|
|
2925
|
+
|
|
@@ -9,7 +9,8 @@ param(
|
|
|
9
9
|
[string]$LogPath = '',
|
|
10
10
|
[int]$StripHeight = 320,
|
|
11
11
|
[int]$LeaseSeconds = 20,
|
|
12
|
-
[ValidateSet('bg', 'top')][string]$Layer = 'bg'
|
|
12
|
+
[ValidateSet('bg', 'top')][string]$Layer = 'bg',
|
|
13
|
+
[ValidateSet('off', 'auto', 'on')][string]$Throttle = 'off'
|
|
13
14
|
)
|
|
14
15
|
|
|
15
16
|
$ErrorActionPreference = 'Stop'
|
|
@@ -64,6 +65,8 @@ public static class FarmWebViewWin32 {
|
|
|
64
65
|
[DllImport("user32.dll")] public static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
|
65
66
|
[DllImport("user32.dll")] public static extern bool EnumChildWindows(IntPtr hWnd, EnumProc cb, IntPtr lParam);
|
|
66
67
|
[DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr hWnd, IntPtr after, int x, int y, int cx, int cy, uint flags);
|
|
68
|
+
[DllImport("user32.dll")] public static extern IntPtr GetWindow(IntPtr hWnd, uint cmd);
|
|
69
|
+
public const uint GW_HWNDPREV = 3;
|
|
67
70
|
[DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
|
|
68
71
|
[DllImport("user32.dll")] public static extern bool EnumWindows(EnumProc cb, IntPtr lParam);
|
|
69
72
|
[DllImport("user32.dll", CharSet=CharSet.Unicode)] public static extern int GetWindowTextW(IntPtr hWnd, System.Text.StringBuilder s, int n);
|
|
@@ -111,6 +114,7 @@ public static class FarmWebViewWin32 {
|
|
|
111
114
|
'@
|
|
112
115
|
|
|
113
116
|
$script:owner = [IntPtr]::Zero
|
|
117
|
+
$script:bgPet = [IntPtr]::Zero
|
|
114
118
|
function Update-PetZOrder {
|
|
115
119
|
try {
|
|
116
120
|
$h = (New-Object System.Windows.Interop.WindowInteropHelper($win)).Handle
|
|
@@ -123,8 +127,11 @@ function Update-PetZOrder {
|
|
|
123
127
|
$script:owner = $pet
|
|
124
128
|
}
|
|
125
129
|
} else {
|
|
126
|
-
[
|
|
127
|
-
|
|
130
|
+
if ($script:bgPet -ne $pet -or [FarmWebViewWin32]::GetWindow($h, [FarmWebViewWin32]::GW_HWNDPREV) -ne $pet) {
|
|
131
|
+
[void][FarmWebViewWin32]::SetWindowPos($h, $pet, 0, 0, 0, 0,
|
|
132
|
+
([FarmWebViewWin32]::SWP_NOMOVE -bor [FarmWebViewWin32]::SWP_NOSIZE -bor [FarmWebViewWin32]::SWP_NOACTIVATE))
|
|
133
|
+
$script:bgPet = $pet
|
|
134
|
+
}
|
|
128
135
|
}
|
|
129
136
|
} catch { }
|
|
130
137
|
}
|
|
@@ -152,6 +159,9 @@ $props = New-Object Microsoft.Web.WebView2.Wpf.CoreWebView2CreationProperties
|
|
|
152
159
|
$ud = Join-Path (Split-Path -Parent $StatePath) ('webview2-' + $Layer)
|
|
153
160
|
New-Item -ItemType Directory -Path $ud -Force | Out-Null
|
|
154
161
|
$props.UserDataFolder = $ud
|
|
162
|
+
if ($Throttle -eq 'off') {
|
|
163
|
+
$props.AdditionalBrowserArguments = '--disable-backgrounding-occluded-windows --disable-renderer-backgrounding --disable-features=CalculateNativeWinOcclusion'
|
|
164
|
+
}
|
|
155
165
|
$wv.CreationProperties = $props
|
|
156
166
|
$win.Content = $wv
|
|
157
167
|
|
package/overlay-electron/main.js
CHANGED
|
@@ -14,6 +14,8 @@ const screensPath = path.join(path.dirname(statePath), 'screens.json');
|
|
|
14
14
|
const assetsDir = path.resolve(arg('assets-dir', path.join(__dirname, '..', 'assets', 'sprites')));
|
|
15
15
|
const logPath = arg('log', '');
|
|
16
16
|
const pidPath = arg('pid', '');
|
|
17
|
+
const rawThrottle = String(arg('throttle', 'off'));
|
|
18
|
+
const THROTTLE = ['off', 'auto', 'on'].includes(rawThrottle) ? rawThrottle : 'off';
|
|
17
19
|
const logStream = logPath ? fs.createWriteStream(logPath, { flags: 'a' }) : null;
|
|
18
20
|
function log(s) {
|
|
19
21
|
const line = new Date().toISOString() + ' [electron] ' + s + '\n';
|
|
@@ -127,11 +129,12 @@ function createWindow(layer) {
|
|
|
127
129
|
webPreferences: {
|
|
128
130
|
nodeIntegration: true,
|
|
129
131
|
contextIsolation: false,
|
|
130
|
-
backgroundThrottling:
|
|
132
|
+
backgroundThrottling: THROTTLE !== 'off',
|
|
131
133
|
},
|
|
132
134
|
});
|
|
133
135
|
win.setAlwaysOnTop(true, 'screen-saver');
|
|
134
136
|
win.setIgnoreMouseEvents(true, { forward: true });
|
|
137
|
+
if (THROTTLE === 'off') win.webContents.setBackgroundThrottling(false);
|
|
135
138
|
try { win.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); } catch { }
|
|
136
139
|
win.loadFile(path.join(__dirname, 'renderer.html'), { query: { state: statePath, assets: assetsDir, layer } });
|
|
137
140
|
win.once('ready-to-show', () => {
|
|
@@ -162,6 +165,11 @@ function fit() {
|
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
app.commandLine.appendSwitch('enable-transparent-visuals');
|
|
168
|
+
if (THROTTLE === 'off') {
|
|
169
|
+
app.commandLine.appendSwitch('disable-backgrounding-occluded-windows');
|
|
170
|
+
app.commandLine.appendSwitch('disable-renderer-backgrounding');
|
|
171
|
+
app.commandLine.appendSwitch('disable-features', 'CalculateNativeWinOcclusion');
|
|
172
|
+
}
|
|
165
173
|
|
|
166
174
|
if (!app.requestSingleInstanceLock()) {
|
|
167
175
|
app.quit();
|
|
@@ -11,6 +11,7 @@ $ErrorActionPreference = 'SilentlyContinue'
|
|
|
11
11
|
$bgPtr = [IntPtr]([Int64]$Bg)
|
|
12
12
|
$topPtr = [IntPtr]([Int64]$Top)
|
|
13
13
|
$script:owner = [IntPtr]::Zero
|
|
14
|
+
$script:bgPet = [IntPtr]::Zero
|
|
14
15
|
|
|
15
16
|
Add-Type -TypeDefinition @'
|
|
16
17
|
using System;
|
|
@@ -25,6 +26,8 @@ public static class FarmZ {
|
|
|
25
26
|
[DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
|
|
26
27
|
[DllImport("user32.dll", EntryPoint="SetWindowLongPtrW", SetLastError=true)] public static extern IntPtr SetWindowLongPtr(IntPtr h, int n, IntPtr v);
|
|
27
28
|
[DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int cx, int cy, uint flags);
|
|
29
|
+
[DllImport("user32.dll")] public static extern IntPtr GetWindow(IntPtr h, uint cmd);
|
|
30
|
+
public const uint GW_HWNDPREV = 3;
|
|
28
31
|
public const int GWLP_HWNDPARENT = -8;
|
|
29
32
|
public const uint SWP_NOSIZE = 0x1;
|
|
30
33
|
public const uint SWP_NOMOVE = 0x2;
|
|
@@ -54,11 +57,14 @@ while ([FarmZ]::IsWindow($bgPtr) -and [FarmZ]::IsWindow($topPtr)) {
|
|
|
54
57
|
[void][FarmZ]::SetWindowLongPtr($topPtr, [FarmZ]::GWLP_HWNDPARENT, $pet)
|
|
55
58
|
$script:owner = $pet
|
|
56
59
|
}
|
|
57
|
-
[
|
|
58
|
-
|
|
60
|
+
if ($script:bgPet -ne $pet -or [FarmZ]::GetWindow($bgPtr, [FarmZ]::GW_HWNDPREV) -ne $pet) {
|
|
61
|
+
[void][FarmZ]::SetWindowPos($bgPtr, $pet, 0, 0, 0, 0,
|
|
62
|
+
([FarmZ]::SWP_NOMOVE -bor [FarmZ]::SWP_NOSIZE -bor [FarmZ]::SWP_NOACTIVATE))
|
|
63
|
+
$script:bgPet = $pet
|
|
64
|
+
}
|
|
59
65
|
}
|
|
60
|
-
Start-Sleep -Milliseconds
|
|
66
|
+
Start-Sleep -Milliseconds 1000
|
|
61
67
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
package/package.json
CHANGED
package/src/farm.js
CHANGED
|
@@ -55,6 +55,8 @@ export const DEFAULTS = {
|
|
|
55
55
|
overlayEngine: 'auto',
|
|
56
56
|
/** 备用 electron.exe / electron 可执行文件路径;空 = 自动找。 */
|
|
57
57
|
electronPath: '',
|
|
58
|
+
/** 图层节流:off = 不节流(默认);auto = 交给 Chromium;on = 允许后台/遮挡节流。 */
|
|
59
|
+
throttle: 'off',
|
|
58
60
|
/** 最多同时种几棵(屏幕上不重叠地放得下的上限) */
|
|
59
61
|
maxPlants: 12,
|
|
60
62
|
/** 屏幕逻辑宽度,用来把"像素宽"换算成 x 间距 */
|
package/src/index.js
CHANGED
|
@@ -76,6 +76,14 @@ export const farmDefinition = {
|
|
|
76
76
|
{ value: 'wpf', label: zh ? 'WPF/WebView2(不需要 Electron)' : 'WPF/WebView2 (no Electron)' },
|
|
77
77
|
];
|
|
78
78
|
}
|
|
79
|
+
if (kind === 'farm-throttle') {
|
|
80
|
+
const zh = String(language).toLowerCase().startsWith('zh');
|
|
81
|
+
return [
|
|
82
|
+
{ value: 'off', label: zh ? '不节流(默认,推荐)' : 'Off (default, recommended)' },
|
|
83
|
+
{ value: 'auto', label: zh ? '自动(交给 Chromium)' : 'Auto (Chromium decides)' },
|
|
84
|
+
{ value: 'on', label: zh ? '节流(后台/遮挡时降帧)' : 'On (throttle when backgrounded/occluded)' },
|
|
85
|
+
];
|
|
86
|
+
}
|
|
79
87
|
return [];
|
|
80
88
|
},
|
|
81
89
|
describe: {
|
package/src/world.js
CHANGED
|
@@ -334,6 +334,24 @@ function farmConfigGroups(language = 'zh', defaultHomePath = '') {
|
|
|
334
334
|
},
|
|
335
335
|
},
|
|
336
336
|
},
|
|
337
|
+
{
|
|
338
|
+
id: 'world:farm/performance',
|
|
339
|
+
owner: 'world:farm',
|
|
340
|
+
schema: {
|
|
341
|
+
type: 'object',
|
|
342
|
+
title: t('性能', 'Performance'),
|
|
343
|
+
description: t('桌面图层的后台/遮挡节流。默认不节流,收获和动画更跟手;如果 CPU/GPU 占用高,可改成自动或节流。', 'Background/occlusion throttling for the desktop layers. Off by default for responsive harvest animations; switch to Auto or Throttle if CPU/GPU usage is high.'),
|
|
344
|
+
properties: {
|
|
345
|
+
'worlds.farm.throttle': {
|
|
346
|
+
type: 'string',
|
|
347
|
+
title: t('节流', 'Throttling'),
|
|
348
|
+
description: t('不节流:禁用后台/遮挡节流(推荐)。自动:交给 Chromium。节流:允许后台和遮挡时降帧。', 'Off disables background/occlusion throttling (recommended). Auto lets Chromium decide. On allows lower frame rates when backgrounded or occluded.'),
|
|
349
|
+
'x-options': 'farm-throttle',
|
|
350
|
+
'x-hot': true,
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
},
|
|
337
355
|
];
|
|
338
356
|
}
|
|
339
357
|
|
|
@@ -410,6 +428,7 @@ export class FarmWorld {
|
|
|
410
428
|
screen: String(this.cfg?.screen ?? ''),
|
|
411
429
|
overlayEngine: ['auto', 'electron', 'wpf'].includes(String(this.cfg?.overlayEngine ?? 'auto')) ? String(this.cfg?.overlayEngine ?? 'auto') : 'auto',
|
|
412
430
|
electronPath: String(this.cfg?.electronPath ?? ''),
|
|
431
|
+
throttle: ['off', 'auto', 'on'].includes(String(this.cfg?.throttle ?? 'off')) ? String(this.cfg?.throttle ?? 'off') : 'off',
|
|
413
432
|
maxPlants: this.#num('maxPlants', 1, 24),
|
|
414
433
|
screenWidth,
|
|
415
434
|
zoom: this.#num('zoom', 1, 8),
|
|
@@ -1591,11 +1610,6 @@ export class FarmWorld {
|
|
|
1591
1610
|
{ label: '天数', value: started ? '第 ' + s.day + ' 天' : '—', tone: 'plain' },
|
|
1592
1611
|
],
|
|
1593
1612
|
panels: [{
|
|
1594
|
-
id: 'manage',
|
|
1595
|
-
title: '存档与重置',
|
|
1596
|
-
description: '备份存档,或把存档和全部自定义设置重置为默认。',
|
|
1597
|
-
getMethods: ['state'],
|
|
1598
|
-
}, {
|
|
1599
1613
|
id: 'bag',
|
|
1600
1614
|
title: '背包',
|
|
1601
1615
|
description: '背包、农场预览、摆放道具与手动合成;装饰在这里输入坐标放置。',
|
|
@@ -1642,33 +1656,7 @@ export class FarmWorld {
|
|
|
1642
1656
|
async invoke(panel, method, args = []) {
|
|
1643
1657
|
return this.#serial(async () => {
|
|
1644
1658
|
const cfg = this.#cfgAll();
|
|
1645
|
-
if (panel === 'manage')
|
|
1646
|
-
if (method === 'backup') return this.#backupNow();
|
|
1647
|
-
if (method === 'reset') return this.#resetAll();
|
|
1648
|
-
if (method === 'check') return this.#envCheck();
|
|
1649
|
-
if (method === 'setElectronPath') {
|
|
1650
|
-
const file = String(args?.[0] ?? '').trim();
|
|
1651
|
-
if (file && !existsSync(file)) throw new Error('找不到这个 Electron 文件:' + file);
|
|
1652
|
-
this.persistConfig?.({ electronPath: file });
|
|
1653
|
-
try { this.cfg.electronPath = file; } catch { /* cfg 可能是只读代理 */ }
|
|
1654
|
-
await this.#stopLayers('Electron 路径变更');
|
|
1655
|
-
this.#ensureLayers('Electron 路径变更');
|
|
1656
|
-
this.#layerEngineKey = this.#engine() + '|' + file;
|
|
1657
|
-
return this.#envCheck();
|
|
1658
|
-
}
|
|
1659
|
-
if (method === 'setEngine') {
|
|
1660
|
-
const mode = String(args?.[0] ?? 'auto');
|
|
1661
|
-
if (!['auto', 'electron', 'wpf'].includes(mode)) throw new Error('不认识的图层引擎:' + mode);
|
|
1662
|
-
this.persistConfig?.({ overlayEngine: mode });
|
|
1663
|
-
try { this.cfg.overlayEngine = mode; } catch { /* cfg 可能是只读代理 */ }
|
|
1664
|
-
await this.#stopLayers('图层引擎变更');
|
|
1665
|
-
this.#ensureLayers('图层引擎变更');
|
|
1666
|
-
this.#layerEngineKey = this.#engine() + '|' + String(this.cfg?.electronPath ?? '');
|
|
1667
|
-
return this.#envCheck();
|
|
1668
|
-
}
|
|
1669
|
-
if (method === 'state') return { ok: true };
|
|
1670
|
-
throw new Error('不认识的存档操作:' + method);
|
|
1671
|
-
}
|
|
1659
|
+
if (panel === 'manage') return this.#manageAction(method, args);
|
|
1672
1660
|
if (panel === 'status') return this.#statusInvoke(method, args, cfg);
|
|
1673
1661
|
if (panel === 'bag') return this.#bagInvoke(method, args);
|
|
1674
1662
|
if (panel === 'shop') return this.#shopInvoke(method, args);
|
|
@@ -1729,8 +1717,39 @@ export class FarmWorld {
|
|
|
1729
1717
|
};
|
|
1730
1718
|
}
|
|
1731
1719
|
|
|
1720
|
+
async #manageAction(method, args = []) {
|
|
1721
|
+
if (method === 'backup') return this.#backupNow();
|
|
1722
|
+
if (method === 'reset') return this.#resetAll();
|
|
1723
|
+
if (method === 'check') return this.#envCheck();
|
|
1724
|
+
if (method === 'setElectronPath') {
|
|
1725
|
+
const file = String(args?.[0] ?? '').trim();
|
|
1726
|
+
if (file && !existsSync(file)) throw new Error('找不到这个 Electron 文件:' + file);
|
|
1727
|
+
this.persistConfig?.({ electronPath: file });
|
|
1728
|
+
try { this.cfg.electronPath = file; } catch { /* cfg 可能是只读代理 */ }
|
|
1729
|
+
await this.#stopLayers('Electron 路径变更');
|
|
1730
|
+
this.#ensureLayers('Electron 路径变更');
|
|
1731
|
+
this.#layerEngineKey = this.#engine() + '|' + file + '|' + String(this.cfg?.throttle ?? 'off');
|
|
1732
|
+
return this.#envCheck();
|
|
1733
|
+
}
|
|
1734
|
+
if (method === 'setEngine') {
|
|
1735
|
+
const mode = String(args?.[0] ?? 'auto');
|
|
1736
|
+
if (!['auto', 'electron', 'wpf'].includes(mode)) throw new Error('不认识的图层引擎:' + mode);
|
|
1737
|
+
this.persistConfig?.({ overlayEngine: mode });
|
|
1738
|
+
try { this.cfg.overlayEngine = mode; } catch { /* cfg 可能是只读代理 */ }
|
|
1739
|
+
await this.#stopLayers('图层引擎变更');
|
|
1740
|
+
this.#ensureLayers('图层引擎变更');
|
|
1741
|
+
this.#layerEngineKey = this.#engine() + '|' + String(this.cfg?.electronPath ?? '') + '|' + String(this.cfg?.throttle ?? 'off');
|
|
1742
|
+
return this.#envCheck();
|
|
1743
|
+
}
|
|
1744
|
+
if (method === 'state') return { ok: true };
|
|
1745
|
+
throw new Error('不认识的存档操作:' + method);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1732
1748
|
/** 「农场自定义」页的数据面。统一返回最新 snapshot,面板不用再读一次。 */
|
|
1733
1749
|
async #customInvoke(method, args, cfg) {
|
|
1750
|
+
if (['backup', 'reset', 'check', 'setEngine', 'setElectronPath'].includes(method)) {
|
|
1751
|
+
return this.#manageAction(method, args);
|
|
1752
|
+
}
|
|
1734
1753
|
if (method === 'sprite') {
|
|
1735
1754
|
const name = String(args?.[0] ?? '');
|
|
1736
1755
|
return { $binary: this.#catalogOrThrow().spriteBase64(name) };
|
|
@@ -2413,7 +2432,7 @@ export class FarmWorld {
|
|
|
2413
2432
|
}
|
|
2414
2433
|
|
|
2415
2434
|
async #syncLayerEngine() {
|
|
2416
|
-
const key = this.#engine() + '|' + String(this.cfg?.electronPath ?? '');
|
|
2435
|
+
const key = this.#engine() + '|' + String(this.cfg?.electronPath ?? '') + '|' + String(this.cfg?.throttle ?? 'off');
|
|
2417
2436
|
if (!this.#layerEngineKey) { this.#layerEngineKey = key; return; }
|
|
2418
2437
|
if (key === this.#layerEngineKey) return;
|
|
2419
2438
|
this.#layerEngineKey = key;
|
|
@@ -2529,7 +2548,7 @@ export class FarmWorld {
|
|
|
2529
2548
|
try {
|
|
2530
2549
|
const launcher = join(paths.electronDir, 'launch.js');
|
|
2531
2550
|
const pidFile = join(this.dataDir, 'farm', 'electron.pid');
|
|
2532
|
-
const child = spawn(process.execPath, [launcher, '--electron', exe, '--state-path', paths.overlayState, '--assets-dir', paths.assetsDir, '--pid', pidFile, '--log', join(dirname(this.overlayFile()), 'electron.log')], { detached: true, stdio: 'ignore', windowsHide: true, env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' } });
|
|
2551
|
+
const child = spawn(process.execPath, [launcher, '--electron', exe, '--throttle', String(this.cfg?.throttle ?? 'off'), '--state-path', paths.overlayState, '--assets-dir', paths.assetsDir, '--pid', pidFile, '--log', join(dirname(this.overlayFile()), 'electron.log')], { detached: true, stdio: 'ignore', windowsHide: true, env: { ...process.env, ELECTRON_RUN_AS_NODE: '1' } });
|
|
2533
2552
|
child.unref();
|
|
2534
2553
|
this.#layerChild = child;
|
|
2535
2554
|
this.#layerAt = Date.now();
|
|
@@ -2568,7 +2587,7 @@ export class FarmWorld {
|
|
|
2568
2587
|
const child = spawn('powershell.exe',
|
|
2569
2588
|
['-NoProfile', '-ExecutionPolicy', 'Bypass', '-WindowStyle', 'Hidden', '-File', script,
|
|
2570
2589
|
'-StatePath', paths.overlayState, '-HomeDir', paths.home, '-AssetsDir', paths.assetsDir,
|
|
2571
|
-
'-PidPath', pidFile, '-LogPath', logFile, '-Layer', layer],
|
|
2590
|
+
'-PidPath', pidFile, '-LogPath', logFile, '-Layer', layer, '-Throttle', String(this.cfg?.throttle ?? 'off')],
|
|
2572
2591
|
{ stdio: 'ignore', windowsHide: true });
|
|
2573
2592
|
child.unref();
|
|
2574
2593
|
try { writeFileSync(pidFile, String(child.pid)); } catch { }
|
|
@@ -2807,8 +2826,11 @@ export class FarmWorld {
|
|
|
2807
2826
|
this.#started = true;
|
|
2808
2827
|
this.#schedule();
|
|
2809
2828
|
this.#cmdTimer = setInterval(() => { void this.#drainCommands(); }, 1000);
|
|
2829
|
+
if (this.persistConfig && this.cfg?.throttle === undefined) {
|
|
2830
|
+
try { this.persistConfig({ throttle: 'off' }); this.cfg.throttle = 'off'; } catch { /* 默认仍是 off */ }
|
|
2831
|
+
}
|
|
2810
2832
|
this.#hidden = this.#flag('hideFarm');
|
|
2811
|
-
this.#layerEngineKey = this.#engine() + '|' + String(this.cfg?.electronPath ?? '');
|
|
2833
|
+
this.#layerEngineKey = this.#engine() + '|' + String(this.cfg?.electronPath ?? '') + '|' + String(this.cfg?.throttle ?? 'off');
|
|
2812
2834
|
if (!this.#hidden) { try { this.#ensureLayers(); } catch { /* 忽略 */ } }
|
|
2813
2835
|
this.#envCheckPending = Boolean(this.#host?.pushEvent);
|
|
2814
2836
|
// 不做周期巡检:巡检会触发"杀掉再起",和正在启动的进程互相打断(这就是之前反复缺层的原因)
|