@lily-cooperative/custom-farm 0.34.0 → 0.35.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/describe.json ADDED
@@ -0,0 +1,345 @@
1
+ {
2
+ "tools": [
3
+ {
4
+ "name": "farm_status",
5
+ "description": "查看田地:每棵作物的位置 xPct(屏幕底部百分比)、等级、成熟进度、水分、是否熟了/枯萎了、还能收几次,以及背包里有什么、地上掉了什么、野草有几丛/哪些能收、主人自定义的状态(数值 + 开关)当前是多少。最后一行「最近田里的变化」会列出最近少掉的棵和原因——田空了先看这行,别当成存档被清。想知道情况就查这个,田地不会主动推送生长进度。",
6
+ "parameters": {
7
+ "type": "object",
8
+ "properties": {}
9
+ },
10
+ "tags": [
11
+ "read",
12
+ "snapshot"
13
+ ],
14
+ "summary": {
15
+ "zh": "看田:每棵作物的位置、等级、进度、水分、是否可收/枯萎,外加背包、地上的掉落物和最近田里的变化(少了哪棵、为什么)。",
16
+ "en": "Show every plant (position, level, progress, moisture, ripe/wilted) plus the bag, the drops, and recent field changes (which plant is gone and why)."
17
+ }
18
+ },
19
+ {
20
+ "name": "farm_stand",
21
+ "description": "站到某个位置。at 必填:先用桌宠自己的 pet_move_to 走过去,再把到达的位置(回执里那个百分比除以 100)填进 at。给了 plot 还会顺便校验你有没有真的站到那棵旁边。播种、收割、捡东西都要靠它放行。",
22
+ "parameters": {
23
+ "type": "object",
24
+ "properties": {
25
+ "at": {
26
+ "type": "number",
27
+ "minimum": 0,
28
+ "maximum": 1,
29
+ "description": "你现在的位置(0~1),pet_move_to 回执里那个百分比除以 100。必填。"
30
+ },
31
+ "plot": {
32
+ "type": "integer",
33
+ "description": "可选:顺便声明你站在几号地旁边。"
34
+ }
35
+ },
36
+ "required": [
37
+ "at"
38
+ ],
39
+ "additionalProperties": false
40
+ },
41
+ "tags": [
42
+ "write"
43
+ ],
44
+ "summary": {
45
+ "zh": "站到某个位置(at 必填),播种/收割/捡东西都要先站过去。",
46
+ "en": "Stand at a position (at is required); planting, harvesting and collecting all need it."
47
+ }
48
+ },
49
+ {
50
+ "name": "farm_plant",
51
+ "description": "在脚下种一棵。必须先用 pet_move_to 走过去并 farm_stand 报好位置。种一棵要消耗 1 包对应种子(背包里的 seed_tomato 这种);没有种子就自己补 1~2 包(farm_shop 看价、farm_buy 买),不用问主人。crop 可以填作物名(番茄/南瓜/玉米/土豆/草莓/小白菜等),不填就按轮作自动选;x 不填就种在脚下,也可以自己指定 0~1,但必须在当前农田范围内(农田始终以屏幕中心为中线,宽度可调)。默认允许轻微重叠,太挤会被拒绝并告诉你已经种在哪些百分比。",
52
+ "parameters": {
53
+ "type": "object",
54
+ "properties": {
55
+ "crop": {
56
+ "type": "string",
57
+ "description": "作物名或者 key;不填自动轮作。"
58
+ },
59
+ "x": {
60
+ "type": "number",
61
+ "minimum": 0,
62
+ "maximum": 1,
63
+ "description": "种在屏幕底部哪个位置;不填就种在脚下。"
64
+ }
65
+ },
66
+ "additionalProperties": false
67
+ },
68
+ "tags": [
69
+ "write"
70
+ ],
71
+ "summary": {
72
+ "zh": "在脚下种一棵:要消耗 1 包对应种子(种子在背包里,没有就自己补 1~2 包),位置可自己挑,两棵不能重叠。",
73
+ "en": "Plant one at your feet; consumes one seed pack from the bag (restock 1–2 packs yourself if empty), position optional, no overlap."
74
+ }
75
+ },
76
+ {
77
+ "name": "farm_water",
78
+ "description": "浇水:走到一棵旁边(先 pet_move_to + farm_stand),一次把够得着的这一片全浇透(含旁边几棵,不用一株一株浇)。给 plot 就以那棵为中心,省略就以你站的位置为中心。已经浇透的会跳过。太久不浇会枯萎,枯了浇不活,要拔掉重种。",
79
+ "parameters": {
80
+ "type": "object",
81
+ "properties": {
82
+ "plot": {
83
+ "type": "integer",
84
+ "description": "可选:以这棵为中心浇它和旁边够得着的;省略则浇你脚下够得着的全部。"
85
+ }
86
+ },
87
+ "additionalProperties": false
88
+ },
89
+ "tags": [
90
+ "write"
91
+ ],
92
+ "summary": {
93
+ "zh": "浇水:一次浇透脚下够得着的一片(含旁边几棵),已经浇透的会跳过。",
94
+ "en": "Water the whole reachable patch in one call (neighbours included); already-full plants are skipped."
95
+ }
96
+ },
97
+ {
98
+ "name": "farm_harvest",
99
+ "description": "收割脚下这棵熟的作物,或者一丛长成、带收获物的杂草。产物会掉在地上,不会直接进背包——收完再 farm_collect 才拿到手。必须站过去。番茄能收 4 次、草莓 5 次,收完退回中间等级继续长;小麦/土豆/卷心菜/玉米/南瓜只能收 1 次,收完就拔掉(所以收一次地里就少一棵,不是 bug)。杂草要长到最高阶段才收得动;没配收获物的杂草只能靠 farm_plant 铲掉。",
100
+ "parameters": {
101
+ "type": "object",
102
+ "properties": {
103
+ "plot": {
104
+ "type": "integer",
105
+ "description": "收哪一棵;省略则收脚下这棵。"
106
+ }
107
+ },
108
+ "additionalProperties": false
109
+ },
110
+ "tags": [
111
+ "write"
112
+ ],
113
+ "summary": {
114
+ "zh": "收脚下这棵熟的,产物掉在地上,要用 farm_collect 捡。",
115
+ "en": "Harvest the ripe plant at your feet; the produce drops on the ground and must be collected."
116
+ }
117
+ },
118
+ {
119
+ "name": "farm_pull",
120
+ "description": "拔掉一棵枯萎的作物(要站在它跟前)。枯萎的作物浇不活,只能拔掉重种;拔的时候会冒一团烟。",
121
+ "parameters": {
122
+ "type": "object",
123
+ "properties": {
124
+ "plot": {
125
+ "type": "integer",
126
+ "description": "拔哪一棵;省略则拔脚下这棵。"
127
+ }
128
+ },
129
+ "additionalProperties": false
130
+ },
131
+ "tags": [
132
+ "write"
133
+ ]
134
+ },
135
+ {
136
+ "name": "farm_sell",
137
+ "description": "把背包里有出售价的道具卖掉换金币(作物/种子/消耗品/装饰)。没在「道具自定义」里设出售价的不能卖。不给 count 就全卖。",
138
+ "parameters": {
139
+ "type": "object",
140
+ "properties": {
141
+ "item": {
142
+ "type": "string",
143
+ "description": "背包里的 item id 或名字,比如 tomato / 番茄 / seed_tomato。"
144
+ },
145
+ "count": {
146
+ "type": "integer",
147
+ "description": "卖几个;省略=全卖。"
148
+ }
149
+ },
150
+ "required": [
151
+ "item"
152
+ ],
153
+ "additionalProperties": false
154
+ },
155
+ "tags": [
156
+ "write"
157
+ ]
158
+ },
159
+ {
160
+ "name": "farm_shop",
161
+ "description": "看商店:列出所有设了购买价的种子/消耗品/装饰(买价、卖价)。没设购买价的不在商店;没设出售价的不能卖。",
162
+ "parameters": {
163
+ "type": "object",
164
+ "properties": {
165
+ "kind": {
166
+ "type": "string",
167
+ "enum": [
168
+ "seed",
169
+ "consumable",
170
+ "decor",
171
+ "furniture",
172
+ "all"
173
+ ],
174
+ "description": "看哪一类;默认 all。"
175
+ }
176
+ },
177
+ "additionalProperties": false
178
+ },
179
+ "tags": [
180
+ "read"
181
+ ]
182
+ },
183
+ {
184
+ "name": "farm_buy",
185
+ "description": "买设了购买价的道具(种子/消耗品/装饰):id 是道具 id(seed_tomato),count 一次买几个;也可以 items:[{id,count}] 一次买多种。买来先进背包;装饰要再用 farm_place 摆。种子不够可以自己补 1~2 包,不用问;买装饰/大件先问主人。",
186
+ "parameters": {
187
+ "type": "object",
188
+ "properties": {
189
+ "id": {
190
+ "type": "string",
191
+ "description": "道具 id(seed_tomato / coffee / scarecrow)"
192
+ },
193
+ "count": {
194
+ "type": "integer",
195
+ "description": "买几个(默认 1,最多 99)"
196
+ },
197
+ "items": {
198
+ "type": "array",
199
+ "description": "一次买多种(推荐,省 token):[{id:\"seed_tomato\",count:1},{id:\"coffee\",count:2}]",
200
+ "items": {
201
+ "type": "object",
202
+ "properties": {
203
+ "id": {
204
+ "type": "string"
205
+ },
206
+ "count": {
207
+ "type": "integer"
208
+ }
209
+ },
210
+ "required": [
211
+ "id"
212
+ ],
213
+ "additionalProperties": false
214
+ }
215
+ }
216
+ },
217
+ "additionalProperties": false
218
+ },
219
+ "tags": [
220
+ "write"
221
+ ]
222
+ },
223
+ {
224
+ "name": "farm_place",
225
+ "description": "把背包里的装饰道具放到农场地面上(消耗 1 个)。「上」层装饰和杂草同层,会清掉落点杂草;「下」层在作物后方、不清草。x 不填就放在你当前站的位置。必须先买/合成到背包里,且道具在「道具自定义」里配了摆放贴图。",
226
+ "parameters": {
227
+ "type": "object",
228
+ "properties": {
229
+ "item": {
230
+ "type": "string",
231
+ "description": "装饰道具 id 或名字(比如 scarecrow / 稻草人)"
232
+ },
233
+ "x": {
234
+ "type": "number",
235
+ "description": "放到屏宽的百分之几(0~1);省略=你站的位置"
236
+ }
237
+ },
238
+ "required": [
239
+ "item"
240
+ ],
241
+ "additionalProperties": false
242
+ },
243
+ "tags": [
244
+ "write"
245
+ ]
246
+ },
247
+ {
248
+ "name": "farm_collect",
249
+ "description": "把脚下够得着的掉落物收进背包。必须站过去(先 farm_stand 报位置)。地上没有就报错并告诉你掉落物在哪些百分比。",
250
+ "parameters": {
251
+ "type": "object",
252
+ "properties": {}
253
+ },
254
+ "tags": [
255
+ "write"
256
+ ],
257
+ "summary": {
258
+ "zh": "把脚下的掉落物收进背包。",
259
+ "en": "Pick up the drops around your feet into the bag."
260
+ }
261
+ },
262
+ {
263
+ "name": "farm_consume",
264
+ "description": "吃掉背包里的作物 / 杂草收获物,用来缓解「饥饿」这类状态。需要主人先在「状态自定义」里配好消耗行为(触发状态 + 可消耗道具 + 状态效果)。item 不填就自动挑背包里第一种可吃的;count 默认 1。吃掉后会按配置加减状态值(通常是饱食度)。",
265
+ "parameters": {
266
+ "type": "object",
267
+ "properties": {
268
+ "item": {
269
+ "type": "string",
270
+ "description": "可选:产物 id 或名字(比如 tomato / 番茄)。不填自动挑。"
271
+ },
272
+ "count": {
273
+ "type": "integer",
274
+ "minimum": 1,
275
+ "maximum": 99,
276
+ "description": "吃几个,默认 1。"
277
+ }
278
+ },
279
+ "additionalProperties": false
280
+ },
281
+ "tags": [
282
+ "write"
283
+ ],
284
+ "summary": {
285
+ "zh": "吃掉背包里的作物/杂草收获物,缓解「饥饿」这类状态(要主人在状态自定义里配好消耗行为)。",
286
+ "en": "Eat a crop/weed produce to relieve states like hunger (configured in the status page)."
287
+ }
288
+ },
289
+ {
290
+ "name": "farm_craft",
291
+ "description": "按「道具合成表」消耗背包里的材料合成道具。recipe 填合成表 id;不填就列出所有合成表和材料。材料不够会报缺什么。",
292
+ "parameters": {
293
+ "type": "object",
294
+ "properties": {
295
+ "recipe": {
296
+ "type": "string",
297
+ "description": "合成表 id(不填=列出所有合成表)"
298
+ }
299
+ },
300
+ "additionalProperties": false
301
+ },
302
+ "tags": [
303
+ "write"
304
+ ],
305
+ "summary": {
306
+ "zh": "按「道具合成表」消耗背包材料合成道具;不填 recipe 就列出所有表。",
307
+ "en": "Craft from the configured recipes; without recipe it lists all recipes."
308
+ }
309
+ }
310
+ ],
311
+ "events": [
312
+ {
313
+ "type": "farm.ripe",
314
+ "description": {
315
+ "zh": "有作物成熟了;正文写明哪几棵、在屏幕底部百分之几,并提示走过去收、收完再捡。",
316
+ "en": "Some plants ripened; the text names them and their position, and says to walk over, harvest, then collect."
317
+ },
318
+ "trigger": "debounce"
319
+ },
320
+ {
321
+ "type": "farm.dry",
322
+ "description": {
323
+ "zh": "有作物土太干了;正文列出哪几棵、在屏幕底部百分之几,提示走过去浇水。",
324
+ "en": "Some plants are running dry; the text names them and their position and says to water them."
325
+ },
326
+ "trigger": "debounce"
327
+ },
328
+ {
329
+ "type": "farm.day",
330
+ "description": {
331
+ "zh": "翻到新的一天;正文带上天气、可收数量、地上的掉落物、土干的数量和背包件数。",
332
+ "en": "A new day started: weather, ripe count, drops on the ground, dry plants and bag size."
333
+ },
334
+ "trigger": "debounce"
335
+ },
336
+ {
337
+ "type": "farm.state",
338
+ "description": {
339
+ "zh": "自定义状态触发消耗行为:比如「饥饿」生效时自动吃掉一个作物(正文写明吃了什么、状态变化),或者提醒桌宠用 farm_consume 去吃;背包里没有可吃道具时也会提醒。",
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
+ },
342
+ "trigger": "debounce"
343
+ }
344
+ ]
345
+ }
package/dist/webui.css CHANGED
@@ -138,7 +138,7 @@
138
138
  .cf-frame .cf-x { top: -6px; right: -6px; }
139
139
 
140
140
 
141
- /* 背包 / 商店页:和 WPF 背包一样的 4 列图标格 */
141
+ /* 背包 / 商店页:4 列图标格 */
142
142
  .bg-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
143
143
  .bg-card {
144
144
  display: flex;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@lily-cooperative/custom-farm",
3
- "version": "0.34.0",
3
+ "version": "0.35.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
- "description": "自定义农场(Custom Farm)—— 给 Coopanion 桌宠的种田扩展:18 种作物只是起点。农作物、杂草、装饰物、天气、状态、道具、合成表和贴图都能在面板里完全自定义;种、浇、收、吃、买卖全部本地确定性运行,不唤模型。",
9
+ "description": "自定义农场(Custom Farm)—— 给 Coopanion 桌宠的种田扩展:18 种作物只是起点。农作物、杂草、装饰物、天气、状态、道具、合成表和贴图都能在面板里完全自定义;种、浇、收、吃、买卖都由你决定。",
10
10
  "keywords": [
11
11
  "cortico-world",
12
12
  "coopanion",
@@ -42,9 +42,9 @@
42
42
  "dist",
43
43
  "overlay",
44
44
  "overlay-electron",
45
- "bag",
46
45
  "assets",
47
46
  "icon.png",
47
+ "describe.json",
48
48
  "README.md",
49
49
  "LICENSE"
50
50
  ],
@@ -59,11 +59,11 @@
59
59
  "locales": {
60
60
  "zh": {
61
61
  "displayName": "自定义农场",
62
- "description": "18 种作物只是起点:农作物、杂草、装饰物、天气、状态、道具、合成表和贴图都能在面板里完全自定义,想种什么、长什么样、吃什么补什么,全由你决定。种、浇、收、吃、买卖全部本地确定性运行,不唤模型。"
62
+ "description": "18 种作物只是起点:农作物、杂草、装饰物、天气、状态、道具、合成表和贴图都能在面板里完全自定义,想种什么、长什么样、吃什么补什么,全由你决定。"
63
63
  },
64
64
  "en": {
65
65
  "displayName": "Custom Farm",
66
- "description": "18 crops are just the beginning: crops, weeds, decorations, weather, states, items, recipes and sprites are all fully customizable in the panel. Plant, water, harvest, eat and trade locally with deterministic ticks — no model calls."
66
+ "description": "18 crops are just the beginning: crops, weeds, decorations, weather, states, items, recipes and sprites are all fully customizable in the panel. Plant, water, harvest, eat and trade the way you like."
67
67
  }
68
68
  },
69
69
  "tags": [
package/src/farm.js CHANGED
@@ -47,6 +47,8 @@ export function farmPatch(cfg = DEFAULTS) {
47
47
  export const REACH = 0.05;
48
48
 
49
49
  export const DEFAULTS = {
50
+ /** 安装目录:overlay / overlay-electron / assets 放在这里;空 = <dataDir>/custom-farm */
51
+ installPath: '',
50
52
  /** 最多同时种几棵(屏幕上不重叠地放得下的上限) */
51
53
  maxPlants: 12,
52
54
  /** 屏幕逻辑宽度,用来把"像素宽"换算成 x 间距 */
package/src/index.js CHANGED
@@ -63,6 +63,7 @@ export const farmDefinition = {
63
63
  dataDir: ctx.dataDir,
64
64
  botDir: ctx.botDir,
65
65
  packageDir: ctx.packageDir,
66
+ persist: (patch) => ctx.persist(patch),
66
67
  }),
67
68
  describe: {
68
69
  tools: describeTools(FARM_TOOL_DECLS),
package/src/paths.js CHANGED
@@ -1,50 +1,47 @@
1
- /**
2
- * 路径解析:所有东西都从扩展自己的位置推出来,不写死本机绝对路径。
3
- *
4
- * <Pet>\custom-farm\src\paths.js ← 本文件
5
- * <Pet>\overlay\overlay.ps1
6
- * <Pet>\bag\bag.ps1
7
- * <Pet>\assets\sprites\
8
- *
9
- * 所以「整个 Pet 文件夹」拷到另一台机器(或换个盘)都能直接用。
10
- * 扩展被复制进 Coopanion 的 extensions 目录(而不是 link)时,用环境变量
11
- * PET_HOME 或 World 配置里的 petRoot 指定 <Pet> 在哪。
12
- */
13
- import { existsSync } from 'node:fs';
14
- import { dirname, join, resolve } from 'node:path';
15
- import { fileURLToPath } from 'node:url';
16
-
17
- const SRC_DIR = dirname(fileURLToPath(import.meta.url));
18
- export const EXTENSION_DIR = resolve(SRC_DIR, '..');
19
-
20
- const looksLikePetHome = (dir) => Boolean(dir) && (
21
- existsSync(join(dir, 'overlay', 'overlay.ps1')) ||
22
- (existsSync(join(dir, 'overlay-electron', 'main.js')) && existsSync(join(dir, 'assets', 'sprites')))
23
- );
24
-
25
- /** 找到 <Pet> 根目录;找不到就返回 null(图层和背包会被自动关掉,不影响农场本身)。 */
26
- export function resolvePetHome(cfgRoot) {
27
- const candidates = [];
28
- if (typeof cfgRoot === 'string' && cfgRoot.trim()) candidates.push(resolve(cfgRoot.trim()));
29
- const env = process.env.PET_HOME;
30
- if (env && env.trim()) candidates.push(resolve(env.trim()));
31
- candidates.push(EXTENSION_DIR);
32
- candidates.push(resolve(EXTENSION_DIR, '..'));
33
- for (const dir of candidates) {
34
- if (looksLikePetHome(dir)) return dir;
35
- }
36
- return null;
37
- }
38
-
39
- export function makePaths(petHome) {
40
- const home = petHome ?? resolve(EXTENSION_DIR, '..');
41
- return {
42
- home,
43
- overlayDir: join(home, 'overlay'),
44
- overlayScript: join(home, 'overlay', 'overlay.ps1'),
45
- overlayState: join(home, 'overlay', 'runtime', 'state.json'),
46
- electronDir: join(home, 'overlay-electron'),
47
- bagScript: join(home, 'bag', 'bag.ps1'),
48
- assetsDir: join(home, 'assets', 'sprites'),
49
- };
50
- }
1
+ /**
2
+ * 路径解析:home 目录里放 overlay / overlay-electron / assets。
3
+ *
4
+ * 默认 home 是 <dataDir>/custom-farm;扩展从 npm 安装时,包内也自带这三个目录,
5
+ * 首次启动会把它们复制过去。之后在「游戏配置 → 安装」里改路径,World 会把 home
6
+ * 搬到新位置并重启桌面图层。
7
+ */
8
+ import { existsSync } from 'node:fs';
9
+ import { dirname, join, resolve } from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ const SRC_DIR = dirname(fileURLToPath(import.meta.url));
13
+ export const EXTENSION_DIR = resolve(SRC_DIR, '..');
14
+
15
+ export const HOME_NAME = 'custom-farm';
16
+
17
+ /** 一个目录是否已经有可用的桌面图层和素材。 */
18
+ export function looksLikeHome(dir) {
19
+ if (!dir) return false;
20
+ return existsSync(join(dir, 'overlay', 'overlay.ps1'))
21
+ || (existsSync(join(dir, 'overlay-electron', 'main.js')) && existsSync(join(dir, 'assets', 'sprites')));
22
+ }
23
+
24
+ /** 默认 home:Coopanion 数据目录下的 custom-farm。 */
25
+ export function defaultHome(dataDir) {
26
+ return join(dataDir, HOME_NAME);
27
+ }
28
+
29
+ /** 扩展包自带或开发目录里的 overlay / assets 源。 */
30
+ export function bundleHome(packageDir) {
31
+ const candidates = [packageDir, resolve(packageDir, '..'), EXTENSION_DIR, resolve(EXTENSION_DIR, '..')];
32
+ for (const dir of candidates) {
33
+ if (looksLikeHome(dir)) return dir;
34
+ }
35
+ return packageDir;
36
+ }
37
+
38
+ export function makePaths(home) {
39
+ return {
40
+ home,
41
+ overlayDir: join(home, 'overlay'),
42
+ overlayScript: join(home, 'overlay', 'overlay.ps1'),
43
+ overlayState: join(home, 'overlay', 'runtime', 'state.json'),
44
+ electronDir: join(home, 'overlay-electron'),
45
+ assetsDir: join(home, 'assets', 'sprites'),
46
+ };
47
+ }
package/src/shop.js CHANGED
@@ -101,7 +101,7 @@ export function buyPriceOf(itemId) {
101
101
  /**
102
102
  * 家具总开关:2026-10-27 起暂时下架(摆放方案还没定)。
103
103
  * false = 商店不列、farm_buy 不卖家具、farm_place 报错;存档里已经有的家具保留不删。
104
- * 恢复:改回 true,并把 bag\bag.ps1 里的 $ShowFurniture 改成 $true。
104
+ * 恢复:改回 true。
105
105
  */
106
106
  export const FURNITURE_ENABLED = false;
107
107