cortico 0.1.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.
Files changed (311) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/package.json +43 -0
  4. package/src/boot.ts +46 -0
  5. package/src/bot.ts +1342 -0
  6. package/src/config-file.ts +41 -0
  7. package/src/core/README.md +92 -0
  8. package/src/core/billing.ts +26 -0
  9. package/src/core/blobs.ts +106 -0
  10. package/src/core/bus.ts +311 -0
  11. package/src/core/config-schema.ts +201 -0
  12. package/src/core/config.ts +262 -0
  13. package/src/core/core.ts +699 -0
  14. package/src/core/cost.ts +244 -0
  15. package/src/core/event-store.ts +304 -0
  16. package/src/core/fork.ts +113 -0
  17. package/src/core/generation.ts +118 -0
  18. package/src/core/instance-lock.ts +215 -0
  19. package/src/core/ipc-logger.ts +105 -0
  20. package/src/core/language.ts +62 -0
  21. package/src/core/log-context.ts +38 -0
  22. package/src/core/loop.ts +1858 -0
  23. package/src/core/markers.ts +48 -0
  24. package/src/core/prefix.ts +121 -0
  25. package/src/core/run.ts +95 -0
  26. package/src/core/secrets.ts +18 -0
  27. package/src/core/session.ts +56 -0
  28. package/src/core/sessions.ts +261 -0
  29. package/src/core/state.ts +104 -0
  30. package/src/core/template.ts +72 -0
  31. package/src/core/timers.ts +129 -0
  32. package/src/core/tool-log.ts +135 -0
  33. package/src/core/transcript.ts +92 -0
  34. package/src/core/truncate.ts +121 -0
  35. package/src/core/types.ts +1140 -0
  36. package/src/core/usage-log.ts +79 -0
  37. package/src/core/util.ts +386 -0
  38. package/src/deploy-listing.ts +160 -0
  39. package/src/deploy.ts +162 -0
  40. package/src/extensions/README.md +72 -0
  41. package/src/extensions/dry-mount.ts +390 -0
  42. package/src/extensions/manifest.ts +153 -0
  43. package/src/extensions/runtime.ts +42 -0
  44. package/src/extensions.ts +519 -0
  45. package/src/launcher.ts +285 -0
  46. package/src/paths.ts +155 -0
  47. package/src/protocol/open-responses/LICENSE +201 -0
  48. package/src/protocol/open-responses/README.md +14 -0
  49. package/src/protocol/open-responses/context-helpers.ts +27 -0
  50. package/src/protocol/open-responses/context-log.ts +59 -0
  51. package/src/protocol/open-responses/context.ts +89 -0
  52. package/src/protocol/open-responses/generated.ts +115 -0
  53. package/src/protocol/open-responses/index.ts +33 -0
  54. package/src/protocol/open-responses/openapi.json +4230 -0
  55. package/src/protocol/open-responses/stream.ts +146 -0
  56. package/src/protocol/open-responses/tokens.ts +31 -0
  57. package/src/providers/README.md +86 -0
  58. package/src/providers/base.ts +111 -0
  59. package/src/providers/configuration.ts +97 -0
  60. package/src/providers/console/config.ts +21 -0
  61. package/src/providers/console/settings.ts +443 -0
  62. package/src/providers/console/strings.ts +74 -0
  63. package/src/providers/console/types.ts +11 -0
  64. package/src/providers/llamacpp/archive.ts +148 -0
  65. package/src/providers/llamacpp/catalog.ts +247 -0
  66. package/src/providers/llamacpp/config.ts +36 -0
  67. package/src/providers/llamacpp/console/client.ts +6 -0
  68. package/src/providers/llamacpp/console/models-panel.ts +120 -0
  69. package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
  70. package/src/providers/llamacpp/console/server.ts +153 -0
  71. package/src/providers/llamacpp/index.ts +95 -0
  72. package/src/providers/llamacpp/native.ts +51 -0
  73. package/src/providers/llamacpp/options.ts +117 -0
  74. package/src/providers/llamacpp/runtime-store.ts +139 -0
  75. package/src/providers/llamacpp/runtime.ts +214 -0
  76. package/src/providers/llamacpp/server.ts +286 -0
  77. package/src/providers/llamacpp/strings.ts +230 -0
  78. package/src/providers/openai-responses-compat/config.ts +20 -0
  79. package/src/providers/openai-responses-compat/index.ts +89 -0
  80. package/src/providers/openai-responses-compat/native.ts +127 -0
  81. package/src/providers/openai-responses-compat/strings.ts +19 -0
  82. package/src/providers/pricebook.ts +75 -0
  83. package/src/providers/registry.ts +137 -0
  84. package/src/providers/strings.ts +71 -0
  85. package/src/providers/transport/chat.ts +156 -0
  86. package/src/providers/transport/errors.ts +48 -0
  87. package/src/providers/transport/history.ts +68 -0
  88. package/src/providers/transport/native-input.ts +83 -0
  89. package/src/providers/transport/native-types.ts +13 -0
  90. package/src/providers/transport/response-assembly.ts +191 -0
  91. package/src/providers/transport/response-http.ts +196 -0
  92. package/src/providers/transport/response-meters.ts +27 -0
  93. package/src/providers/transport/responses-input.ts +56 -0
  94. package/src/web/README.md +84 -0
  95. package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
  96. package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
  97. package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
  98. package/src/web/client/console-pages/builtins.ts +10 -0
  99. package/src/web/client/console-pages/context.ts +135 -0
  100. package/src/web/client/console-pages/host.ts +633 -0
  101. package/src/web/client/console-pages/loader.ts +132 -0
  102. package/src/web/client/console-pages/strings.ts +90 -0
  103. package/src/web/client/console-pages/tools/strings.ts +49 -0
  104. package/src/web/client/console-pages/tools/view.ts +215 -0
  105. package/src/web/client/core/api.ts +200 -0
  106. package/src/web/client/core/language.ts +56 -0
  107. package/src/web/client/core/lifecycle.ts +132 -0
  108. package/src/web/client/core/router.ts +199 -0
  109. package/src/web/client/core/stream.ts +157 -0
  110. package/src/web/client/core/websocket.ts +82 -0
  111. package/src/web/client/features/appearance/index.ts +333 -0
  112. package/src/web/client/features/appearance/strings.ts +87 -0
  113. package/src/web/client/features/config/index.ts +11 -0
  114. package/src/web/client/features/config/strings.ts +41 -0
  115. package/src/web/client/features/config/view.ts +428 -0
  116. package/src/web/client/features/core/events.ts +197 -0
  117. package/src/web/client/features/core/index.ts +254 -0
  118. package/src/web/client/features/core/run.ts +56 -0
  119. package/src/web/client/features/core/runlog.ts +214 -0
  120. package/src/web/client/features/core/sessions.ts +53 -0
  121. package/src/web/client/features/core/strings.ts +157 -0
  122. package/src/web/client/features/extensions/index.ts +422 -0
  123. package/src/web/client/features/extensions/strings.ts +164 -0
  124. package/src/web/client/features/feature.ts +93 -0
  125. package/src/web/client/features/live/context.ts +280 -0
  126. package/src/web/client/features/live/fork.ts +156 -0
  127. package/src/web/client/features/live/index.ts +392 -0
  128. package/src/web/client/features/live/onboarding.ts +113 -0
  129. package/src/web/client/features/live/protocol.ts +122 -0
  130. package/src/web/client/features/live/sessions.ts +71 -0
  131. package/src/web/client/features/live/status.ts +68 -0
  132. package/src/web/client/features/live/strings.ts +252 -0
  133. package/src/web/client/features/live/timeline.ts +590 -0
  134. package/src/web/client/features/prompts/editor.ts +292 -0
  135. package/src/web/client/features/prompts/index.ts +346 -0
  136. package/src/web/client/features/prompts/strings.ts +136 -0
  137. package/src/web/client/features/prompts/view.ts +256 -0
  138. package/src/web/client/features/providers/index.ts +100 -0
  139. package/src/web/client/features/providers/strings.ts +23 -0
  140. package/src/web/client/features/settings/general.ts +28 -0
  141. package/src/web/client/features/settings/index.ts +82 -0
  142. package/src/web/client/features/settings/strings.ts +33 -0
  143. package/src/web/client/features/storage/index.ts +1 -0
  144. package/src/web/client/features/storage/strings.ts +45 -0
  145. package/src/web/client/features/storage/view.ts +154 -0
  146. package/src/web/client/features/usage/chart.ts +486 -0
  147. package/src/web/client/features/usage/index.ts +407 -0
  148. package/src/web/client/features/usage/labels.ts +46 -0
  149. package/src/web/client/features/usage/range.ts +36 -0
  150. package/src/web/client/features/usage/state.ts +37 -0
  151. package/src/web/client/features/usage/strings.ts +191 -0
  152. package/src/web/client/features/usage/tooltip.ts +86 -0
  153. package/src/web/client/features/usage/types.ts +96 -0
  154. package/src/web/client/features/worlds/index.ts +424 -0
  155. package/src/web/client/features/worlds/strings.ts +121 -0
  156. package/src/web/client/main.ts +273 -0
  157. package/src/web/client/shell/avatar.ts +233 -0
  158. package/src/web/client/shell/index.ts +549 -0
  159. package/src/web/client/shell/strings.ts +89 -0
  160. package/src/web/client/theme/handoff.ts +43 -0
  161. package/src/web/client/theme/palette.ts +109 -0
  162. package/src/web/client/theme/registry.ts +130 -0
  163. package/src/web/client/theme/storage.ts +72 -0
  164. package/src/web/client/theme/strings.ts +123 -0
  165. package/src/web/client/theme/studio.ts +366 -0
  166. package/src/web/client/ui/actions.ts +109 -0
  167. package/src/web/client/ui/data.ts +185 -0
  168. package/src/web/client/ui/dom.ts +23 -0
  169. package/src/web/client/ui/fields.ts +191 -0
  170. package/src/web/client/ui/format.ts +75 -0
  171. package/src/web/client/ui/icons.ts +183 -0
  172. package/src/web/client/ui/images.ts +91 -0
  173. package/src/web/client/ui/index.ts +89 -0
  174. package/src/web/client/ui/lamp.ts +125 -0
  175. package/src/web/client/ui/log.ts +98 -0
  176. package/src/web/client/ui/overlay.ts +184 -0
  177. package/src/web/client/ui/page.ts +8 -0
  178. package/src/web/client/ui/prompt-input.tsx +311 -0
  179. package/src/web/client/ui/sheet.ts +131 -0
  180. package/src/web/client/ui/strings.ts +71 -0
  181. package/src/web/console-pages.ts +367 -0
  182. package/src/web/files.ts +107 -0
  183. package/src/web/path-picker.ts +296 -0
  184. package/src/web/public/index.html +17 -0
  185. package/src/web/public/styles.css +1715 -0
  186. package/src/web/server.ts +1883 -0
  187. package/src/web/shared/client-panel.ts +603 -0
  188. package/src/web/shared/console-protocol.ts +560 -0
  189. package/src/web/shared/css.d.ts +1 -0
  190. package/src/web/shared/path-picker.ts +23 -0
  191. package/src/web/shared/theme.ts +256 -0
  192. package/src/web/theme-store.ts +37 -0
  193. package/src/world.ts +449 -0
  194. package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
  195. package/src/worlds/bilibili/README.md +176 -0
  196. package/src/worlds/bilibili/audience-admission.ts +995 -0
  197. package/src/worlds/bilibili/client.ts +528 -0
  198. package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
  199. package/src/worlds/bilibili/config.ts +185 -0
  200. package/src/worlds/bilibili/console/client.ts +202 -0
  201. package/src/worlds/bilibili/definition.ts +35 -0
  202. package/src/worlds/bilibili/gift-frame.ts +131 -0
  203. package/src/worlds/bilibili/normalize.ts +452 -0
  204. package/src/worlds/bilibili/overlay/announcement.ts +76 -0
  205. package/src/worlds/bilibili/overlay/assets.ts +82 -0
  206. package/src/worlds/bilibili/overlay/model.ts +494 -0
  207. package/src/worlds/bilibili/overlay/project.ts +202 -0
  208. package/src/worlds/bilibili/overlay/server.ts +286 -0
  209. package/src/worlds/bilibili/overlay/types.ts +217 -0
  210. package/src/worlds/bilibili/overlay/web/app.js +690 -0
  211. package/src/worlds/bilibili/overlay/web/editor.css +297 -0
  212. package/src/worlds/bilibili/overlay/web/editor.html +102 -0
  213. package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
  214. package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
  215. package/src/worlds/bilibili/overlay/web/styles.css +258 -0
  216. package/src/worlds/bilibili/protobuf.ts +123 -0
  217. package/src/worlds/bilibili/wire.ts +94 -0
  218. package/src/worlds/bilibili/world.ts +1389 -0
  219. package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
  220. package/src/worlds/console-fixture/console/client.ts +72 -0
  221. package/src/worlds/console-fixture/world.ts +73 -0
  222. package/src/worlds/index.ts +15 -0
  223. package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
  224. package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
  225. package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
  226. package/src/worlds/minecraft/README.md +917 -0
  227. package/src/worlds/minecraft/blueprint-plan.ts +915 -0
  228. package/src/worlds/minecraft/blueprint-registry.ts +566 -0
  229. package/src/worlds/minecraft/blueprint-repair.ts +236 -0
  230. package/src/worlds/minecraft/blueprint-resource.ts +272 -0
  231. package/src/worlds/minecraft/blueprint.ts +492 -0
  232. package/src/worlds/minecraft/body-lease.ts +340 -0
  233. package/src/worlds/minecraft/bridge.ts +749 -0
  234. package/src/worlds/minecraft/check.ts +651 -0
  235. package/src/worlds/minecraft/chests.ts +292 -0
  236. package/src/worlds/minecraft/client-launch.ts +272 -0
  237. package/src/worlds/minecraft/client-options.ts +85 -0
  238. package/src/worlds/minecraft/client-skins.ts +164 -0
  239. package/src/worlds/minecraft/client-window.ps1 +91 -0
  240. package/src/worlds/minecraft/client.ts +423 -0
  241. package/src/worlds/minecraft/combat-context.ts +109 -0
  242. package/src/worlds/minecraft/combat.ts +1552 -0
  243. package/src/worlds/minecraft/config.ts +499 -0
  244. package/src/worlds/minecraft/console/access.ts +218 -0
  245. package/src/worlds/minecraft/console/client.ts +244 -0
  246. package/src/worlds/minecraft/console/log.ts +110 -0
  247. package/src/worlds/minecraft/console/mount.ts +420 -0
  248. package/src/worlds/minecraft/console/skin.ts +257 -0
  249. package/src/worlds/minecraft/console/style.css +154 -0
  250. package/src/worlds/minecraft/console/world.ts +449 -0
  251. package/src/worlds/minecraft/deaths.ts +135 -0
  252. package/src/worlds/minecraft/definition.ts +18 -0
  253. package/src/worlds/minecraft/engine-child.ts +274 -0
  254. package/src/worlds/minecraft/engine-ipc.ts +98 -0
  255. package/src/worlds/minecraft/entity-facts.ts +132 -0
  256. package/src/worlds/minecraft/escape.ts +407 -0
  257. package/src/worlds/minecraft/executor.ts +14494 -0
  258. package/src/worlds/minecraft/explored.ts +156 -0
  259. package/src/worlds/minecraft/geometry.ts +205 -0
  260. package/src/worlds/minecraft/goal-plan.ts +647 -0
  261. package/src/worlds/minecraft/item-break.ts +126 -0
  262. package/src/worlds/minecraft/item-facts.ts +146 -0
  263. package/src/worlds/minecraft/item-pick.ts +67 -0
  264. package/src/worlds/minecraft/level-dat.ts +176 -0
  265. package/src/worlds/minecraft/log.ts +132 -0
  266. package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
  267. package/src/worlds/minecraft/names.ts +377 -0
  268. package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
  269. package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
  270. package/src/worlds/minecraft/piglin.ts +49 -0
  271. package/src/worlds/minecraft/policy.ts +547 -0
  272. package/src/worlds/minecraft/precheck.ts +655 -0
  273. package/src/worlds/minecraft/proxy.ts +445 -0
  274. package/src/worlds/minecraft/ranged.ts +635 -0
  275. package/src/worlds/minecraft/readouts.ts +102 -0
  276. package/src/worlds/minecraft/round.ts +26 -0
  277. package/src/worlds/minecraft/search-observation.ts +92 -0
  278. package/src/worlds/minecraft/server-config.ts +467 -0
  279. package/src/worlds/minecraft/server.ts +658 -0
  280. package/src/worlds/minecraft/show.ts +84 -0
  281. package/src/worlds/minecraft/skills.ts +1763 -0
  282. package/src/worlds/minecraft/terrain.ts +1735 -0
  283. package/src/worlds/minecraft/window.ts +96 -0
  284. package/src/worlds/minecraft/works.ts +179 -0
  285. package/src/worlds/minecraft/world.ts +6438 -0
  286. package/src/worlds/qq/ENV_PROMPT.md +5 -0
  287. package/src/worlds/qq/config.ts +108 -0
  288. package/src/worlds/qq/console/client.ts +126 -0
  289. package/src/worlds/qq/console/events.ts +155 -0
  290. package/src/worlds/qq/console/gate.ts +232 -0
  291. package/src/worlds/qq/console/roster.ts +203 -0
  292. package/src/worlds/qq/console/style.css +65 -0
  293. package/src/worlds/qq/conversation.ts +28 -0
  294. package/src/worlds/qq/definition.ts +62 -0
  295. package/src/worlds/qq/driver.ts +375 -0
  296. package/src/worlds/qq/history-tools.ts +209 -0
  297. package/src/worlds/qq/normalize.ts +323 -0
  298. package/src/worlds/qq/qface-map.ts +282 -0
  299. package/src/worlds/qq/vision-prompt.ts +27 -0
  300. package/src/worlds/qq/vision.ts +642 -0
  301. package/src/worlds/qq/vlm.ts +142 -0
  302. package/src/worlds/qq/world.ts +1503 -0
  303. package/src/worlds/terminal/ENV_PROMPT.md +11 -0
  304. package/src/worlds/terminal/config.ts +9 -0
  305. package/src/worlds/terminal/definition.ts +14 -0
  306. package/src/worlds/terminal/world.ts +700 -0
  307. package/src/worlds/websearch/ENV_PROMPT.md +1 -0
  308. package/src/worlds/websearch/brave-client.ts +261 -0
  309. package/src/worlds/websearch/config.ts +75 -0
  310. package/src/worlds/websearch/definition.ts +20 -0
  311. package/src/worlds/websearch/world.ts +171 -0
@@ -0,0 +1,796 @@
1
+ /**
2
+ * mineflayer-pathfinder 的运行时补丁,涵盖搜索缓存与移动约束。
3
+ *
4
+ * 被替换的 Movements 方法(getNeighbors、getMoveJumpUp、getMoveForward 等)以
5
+ * mineflayer-pathfinder 2.4.5 的 lib/movements.js 为底本改写。上游为 MIT 许可,
6
+ * Copyright (c) 2020 Karang;许可全文见同目录 LICENSE-mineflayer-pathfinder.txt。
7
+ *
8
+ * 1. 单次 AStar 搜索内缓存方块读数,按 stateId 构造特征;仅在计算 digTime 时物化完整 Block。
9
+ * 2. 搜索期间工具与效果不变,挖掘成本按方块类型缓存。
10
+ * 3. 空实体索引直接返回,safeToBreak 按位置缓存;compute 之外保留原始读取逻辑。
11
+ * 4. 浅水允许垫塔,深水表面允许游上一格高的岸。
12
+ * 5. 脚在液体且脚下无实底时不生成放置动作。
13
+ * 6. 工地范围禁止寻路放置,走与挖不受限;工地建材在垫脚候选中排最后。
14
+ * 7. 前行与跳上一格的整个放置分支同样受脚下实底约束。
15
+ * 8. 三分钟内被拒且方块未变的落点进入放置黑名单。
16
+ *
17
+ * 缺失区块列须返回不可走;已加载列的越界 y 按空气处理。getMoveJumpUp 的高度调整使用局部值,避免污染缓存。
18
+ */
19
+ import { createRequire } from 'node:module';
20
+ import { Movements } from 'mineflayer-pathfinder';
21
+ import AStar from 'mineflayer-pathfinder/lib/astar.js';
22
+ import type { Logger } from '../../core/types.ts';
23
+
24
+ // prismarine-nbt / vec3 是 mineflayer-pathfinder 的依赖而非本包的,要顺它的依赖链拿
25
+ const req = createRequire(import.meta.url);
26
+ const pfRequire = createRequire(req.resolve('mineflayer-pathfinder/package.json'));
27
+ const nbt = pfRequire('prismarine-nbt') as { simplify(n: unknown): { Enchantments: unknown[] } };
28
+ const { Vec3 } = pfRequire('vec3') as { Vec3: new (x: number, y: number, z: number) => unknown };
29
+ const Move = pfRequire('./lib/move.js') as new (
30
+ x: number, y: number, z: number, remainingBlocks: number, cost: number, toBreak: unknown[], toPlace: unknown[],
31
+ ) => unknown;
32
+ // prismarine-block 顺 mineflayer 的依赖链拿(pathfinder 自己不直接依赖它)
33
+ const mfRequire = createRequire(req.resolve('mineflayer/package.json'));
34
+ const prismarineBlock = mfRequire('prismarine-block') as (registry: unknown) => {
35
+ fromStateId(stateId: number, biomeId: number): {
36
+ type: number; boundingBox: string; shapes?: number[][];
37
+ getProperties?(): Record<string, unknown>;
38
+ } | null;
39
+ };
40
+
41
+ /**
42
+ * 按 stateId 预计算的方块特征(每 registry 建一次,~20ms):搜索只消费这些,
43
+ * 不必每次读块都物化完整 Block。建表走 Block.fromStateId 逐状态物化一遍,
44
+ * 与原版装饰读到的值按构造相等。
45
+ */
46
+ interface StateTables {
47
+ typeId: Uint32Array;
48
+ bbEmpty: Uint8Array;
49
+ bbBlock: Uint8Array;
50
+ /** 高度增量 = max(0, 形状最高点):原版 height = y 再与 y+shape[4] 取 max */
51
+ delta: Float64Array;
52
+ shapes: (number[][] | undefined)[];
53
+ names: string[];
54
+ maxType: number;
55
+ /**
56
+ * 门与栅栏门按 stateId 记录开合状态,0 表示其他方块。
57
+ * 注册表的类型级 boundingBox 不区分开合,寻路可通行性须读取 open 状态。
58
+ */
59
+ doorState: Uint8Array;
60
+ }
61
+
62
+ /** 这一格是开着的门/栅栏门:人走得过去(开门后的碰撞盒只占边上 3/16 格) */
63
+ const DOOR_OPEN = 1;
64
+ /** 关着、而且手能开的(木门、栅栏门):寻路器可以「用一下再过去」 */
65
+ const DOOR_SHUT_HAND = 2;
66
+ /** 关着、手开不动的(铁门只吃红石):继续当墙,不假装能过 */
67
+ const DOOR_SHUT_LOCKED = 3;
68
+ /** 门的上半扇(`half=upper`)。它只当头顶净空,永远不许当落脚格 */
69
+ const DOOR_UPPER = 4;
70
+ const DOOR_KIND = 3;
71
+
72
+ /**
73
+ * 门类逐状态判词。只认竖着挡路的两族:
74
+ *
75
+ * - `*_door`:两半共用一个 `open`,开下半扇上半扇跟着开,所以两半都照同一条判;
76
+ * - `*_fence_gate`:上游 `canOpenDoors` 本来就是为它写的(开着时 shapes 是空的,
77
+ * 可它 `boundingBox` 仍是 `block`,照样被当墙 —— 开着的栅栏门今天也走不过去)。
78
+ *
79
+ * **活板门不进来**:它是横着的地板,「开一下再过去」等于把脚下的地板打开。
80
+ * 开着的活板门当空气是对的,但那属于另一个判断(掉下去),这里不顺手做。
81
+ */
82
+ export function doorStateOf(name: string, props: Record<string, unknown>): number {
83
+ const isDoor = name.endsWith('_door');
84
+ if (!isDoor && !name.endsWith('_fence_gate')) return 0;
85
+ const open = props.open === true || props.open === 'true';
86
+ // 铁门(以及万一有的铁栅栏门)手上没有开关的办法
87
+ const kind = open ? DOOR_OPEN
88
+ : isDoor && name.startsWith('iron_') ? DOOR_SHUT_LOCKED
89
+ : DOOR_SHUT_HAND;
90
+ return kind | (props.half === 'upper' ? DOOR_UPPER : 0);
91
+ }
92
+
93
+ /**
94
+ * 把门的判词落到一次读块结果上(`b` 是本模块自己拼的轻量块,原地改)。
95
+ *
96
+ * - **开着的门**:可穿过,而且**撤掉 openable** —— 上游那条 `shapes.length !== 0`
97
+ * 的判据会把开着的门也「用一下」,而那一下是把门关上(这正是上游把 canOpenDoors
98
+ * 默认关掉、注释写「causes issues」的原因之一:它只对栅栏门成立,开着的栅栏门
99
+ * shapes 是空的,开着的门不是)。
100
+ * - **关着、手能开的**:标成可穿过 + openable,由 getMoveForward 那条 useOne 分支
101
+ * 在走到门口时 activateBlock 一下。
102
+ * - **关着的铁门**:一个字不改,继续当墙。
103
+ *
104
+ * 两种可穿过的门都把 `height` 压回本格地面高度:门不是站得住的台面,留着
105
+ * shapes 算出来的 1.0 会让上下坡判据以为这儿垫高了一格。
106
+ */
107
+ export function applyDoorState(
108
+ b: { safe: boolean; physical: boolean; openable: boolean; height: number },
109
+ door: number,
110
+ y: number,
111
+ canOpenDoors: boolean,
112
+ ): void {
113
+ const kind = door & DOOR_KIND;
114
+ if (kind === DOOR_OPEN) {
115
+ b.safe = true;
116
+ b.physical = false;
117
+ b.openable = false;
118
+ b.height = y;
119
+ return;
120
+ }
121
+ if (kind === DOOR_SHUT_HAND && canOpenDoors) {
122
+ b.safe = true;
123
+ b.physical = false;
124
+ b.openable = true;
125
+ b.height = y;
126
+ }
127
+ }
128
+
129
+ const tablesByRegistry = new WeakMap<object, StateTables>();
130
+
131
+ function tablesFor(registry: object): StateTables {
132
+ let t = tablesByRegistry.get(registry);
133
+ if (t) return t;
134
+ const blocksArray = (registry as { blocksArray: Array<{ id: number; maxStateId: number; name: string }> }).blocksArray;
135
+ let maxState = 0;
136
+ let maxType = 0;
137
+ for (const b of blocksArray) {
138
+ if (b.maxStateId > maxState) maxState = b.maxStateId;
139
+ if (b.id > maxType) maxType = b.id;
140
+ }
141
+ const Block = prismarineBlock(registry);
142
+ t = {
143
+ typeId: new Uint32Array(maxState + 1),
144
+ bbEmpty: new Uint8Array(maxState + 1),
145
+ bbBlock: new Uint8Array(maxState + 1),
146
+ delta: new Float64Array(maxState + 1),
147
+ shapes: new Array<number[][] | undefined>(maxState + 1),
148
+ names: new Array<string>(maxType + 1),
149
+ maxType,
150
+ doorState: new Uint8Array(maxState + 1),
151
+ };
152
+ for (const b of blocksArray) t.names[b.id] = b.name;
153
+ for (let s = 0; s <= maxState; s++) {
154
+ const b = Block.fromStateId(s, 0);
155
+ if (!b) continue;
156
+ t.typeId[s] = b.type;
157
+ t.bbEmpty[s] = b.boundingBox === 'empty' ? 1 : 0;
158
+ t.bbBlock[s] = b.boundingBox === 'block' ? 1 : 0;
159
+ let d = 0;
160
+ for (const sh of b.shapes ?? []) if (sh[4] > d) d = sh[4];
161
+ t.delta[s] = d;
162
+ t.shapes[s] = b.shapes;
163
+ const name = t.names[b.type];
164
+ // getProperties 只在建表时调一遍(每 registry 一次),搜索热路径上读的是这张表
165
+ if (name !== undefined && (name.endsWith('_door') || name.endsWith('_fence_gate'))) {
166
+ t.doorState[s] = doorStateOf(name, b.getProperties?.() ?? {});
167
+ }
168
+ }
169
+ tablesByRegistry.set(registry, t);
170
+ return t;
171
+ }
172
+
173
+ /** movements 各分类 Set 折成按方块类型索引的位表;每次搜索重建,热改 Set 也能跟上 */
174
+ const BIT_CLIMB = 1, BIT_CARPET = 2, BIT_AVOID = 4, BIT_FENCE = 8,
175
+ BIT_REPLACE = 16, BIT_LIQUID = 32, BIT_GRAVITY = 64, BIT_OPEN = 128;
176
+
177
+ function buildBits(m: PatchedMovements, maxType: number): Uint8Array {
178
+ const bits = new Uint8Array(maxType + 1);
179
+ for (let id = 0; id <= maxType; id++) {
180
+ bits[id] = (m.climbables.has(id) ? BIT_CLIMB : 0)
181
+ | (m.carpets.has(id) ? BIT_CARPET : 0)
182
+ | (m.blocksToAvoid.has(id) ? BIT_AVOID : 0)
183
+ | (m.fences.has(id) ? BIT_FENCE : 0)
184
+ | (m.replaceables.has(id) ? BIT_REPLACE : 0)
185
+ | (m.liquids.has(id) ? BIT_LIQUID : 0)
186
+ | (m.gravityBlocks.has(id) ? BIT_GRAVITY : 0)
187
+ | (m.openable.has(id) ? BIT_OPEN : 0);
188
+ }
189
+ return bits;
190
+ }
191
+
192
+ /**
193
+ * 一片在建工地。包围盒含端点两格,寻路器对它有两条规矩:区内不生成放置动作
194
+ * (走、挖不受限),区里的建材在垫脚候选里排到最后。
195
+ */
196
+ export interface SiteZone {
197
+ /** 日志与受阻文案里怎么称呼这片工地 */
198
+ key: string;
199
+ min: readonly [number, number, number];
200
+ max: readonly [number, number, number];
201
+ /** 这片工地要用的物品名;垫脚挑料时降到最低优先级 */
202
+ materials: readonly string[];
203
+ }
204
+
205
+ /**
206
+ * 给一份 movements 装上工地取数口;`null` 撤销。搜索开工与每次挑垫脚料时现取,
207
+ * 所以工地绑定/完工下一次寻路就生效,不必重装 movements。
208
+ */
209
+ export function setSiteZones(
210
+ movements: Movements,
211
+ zones: (() => readonly SiteZone[]) | null,
212
+ ): void {
213
+ (movements as unknown as PatchedMovements).__siteZonesFn = zones;
214
+ }
215
+
216
+ /**
217
+ * 注册按成果坐标禁止头顶垫脚的判据,null 撤销。
218
+ * 此稀疏点集与 setSiteZones 的工地体积约束独立。
219
+ */
220
+ export function setNoPlaceCells(
221
+ movements: Movements,
222
+ blocked: ((x: number, y: number, z: number) => boolean) | null,
223
+ ): void {
224
+ (movements as unknown as PatchedMovements).__noPlaceFn = blocked;
225
+ }
226
+
227
+ /**
228
+ * 给一份 movements 装上「这一格此刻挖不动」的取数口;`null` 撤销。判据由调用方给
229
+ * (bridge 的挖掘失败退避),这里只负责让 `safeToBreak` 认它 —— 挖不动的格子不再
230
+ * 进 `toBreak`,A* 自然绕开,而不是每个物理刻把同一条路重算一遍。
231
+ */
232
+ export function setDigBackoff(
233
+ movements: Movements,
234
+ blocked: ((x: number, y: number, z: number) => boolean) | null,
235
+ ): void {
236
+ (movements as unknown as PatchedMovements).__digBackoffFn = blocked;
237
+ }
238
+
239
+ function inZones(zones: readonly SiteZone[], x: number, y: number, z: number): boolean {
240
+ for (const zone of zones) {
241
+ if (x >= zone.min[0] && x <= zone.max[0]
242
+ && y >= zone.min[1] && y <= zone.max[1]
243
+ && z >= zone.min[2] && z <= zone.max[2]) return true;
244
+ }
245
+ return false;
246
+ }
247
+
248
+ interface SliceCache {
249
+ blocks: Map<number, unknown>;
250
+ labor: Map<number, number>;
251
+ /** safeToBreak 按位置记忆化:纯世界函数,同一堵墙会被相邻节点反复评估 */
252
+ breakable: Map<number, boolean>;
253
+ /** 特征表与位表;bot 上没有 registry 时为 null,未命中退回原始读块 */
254
+ tables: StateTables | null;
255
+ bits: Uint8Array | null;
256
+ /** chunk 是否已加载,按 chunk 记一次,免得每次空气读都去查列 */
257
+ chunkKnown: Map<number, boolean>;
258
+ /** 这一场搜索认的工地;取一次,partial 续算不重取 */
259
+ zones: readonly SiteZone[] | null;
260
+ }
261
+
262
+ interface PatchedMovements {
263
+ __sliceCache?: SliceCache | null;
264
+ __entityIdxEmpty?: boolean;
265
+ __siteZonesFn?: (() => readonly SiteZone[]) | null;
266
+ __digBackoffFn?: ((x: number, y: number, z: number) => boolean) | null;
267
+ __noPlaceFn?: ((x: number, y: number, z: number) => boolean) | null;
268
+ /** 本次搜索的工地快照;compute 之外为 null(路径跟随不判包围盒) */
269
+ __siteZones?: readonly SiteZone[] | null;
270
+ entityIntersections: Record<string, number>;
271
+ exclusionStep(block: unknown): number;
272
+ exclusionBreak(block: unknown): number;
273
+ getNumEntitiesAt(pos: unknown, dx: number, dy: number, dz: number): number;
274
+ safeToBreak(block: unknown): boolean;
275
+ entityCost: number;
276
+ digCost: number;
277
+ /** 上游默认 false(注释写「causes issues」);见 applyDoorState 的说明 */
278
+ canOpenDoors: boolean;
279
+ climbables: Set<number>;
280
+ carpets: Set<number>;
281
+ blocksToAvoid: Set<number>;
282
+ fences: Set<number>;
283
+ replaceables: Set<number>;
284
+ liquids: Set<number>;
285
+ gravityBlocks: Set<number>;
286
+ openable: Set<number>;
287
+ bot: {
288
+ registry?: object;
289
+ world?: {
290
+ getBlockStateId(pos: unknown): number | undefined;
291
+ getColumn(cx: number, cz: number): unknown;
292
+ };
293
+ pathfinder: { bestHarvestTool(block: unknown): { type: number; nbt: unknown } | null };
294
+ entity: { effects: unknown };
295
+ };
296
+ }
297
+
298
+ interface BlockLike {
299
+ type: number;
300
+ safe: boolean;
301
+ physical: boolean;
302
+ position: unknown;
303
+ /** 特征表 POJO 上没有;要用时按需物化真 Block */
304
+ digTime?(tool: number | null, a: boolean, b: boolean, c: boolean, enchants: unknown[], effects: unknown): number;
305
+ }
306
+
307
+ /**
308
+ * 一个候选移动里的放置清单。`x/y/z` 是**参照方块**,加上面向量才是落点;
309
+ * `useOne` 那种是"对着这一格用一下"(开栅门),不是放置。
310
+ */
311
+ interface PlacingMove {
312
+ toPlace?: Array<{ x: number; y: number; z: number; dx: number; dy: number; dz: number; useOne?: boolean }>;
313
+ }
314
+
315
+ /** 20+20+10 位打包进一个安全整数;坐标按 2^20 环绕,单次搜索半径内不会撞 */
316
+ function packPos(x: number, y: number, z: number): number {
317
+ return (x & 0xFFFFF) * 0x40000000 + (z & 0xFFFFF) * 0x400 + (y & 0x3FF);
318
+ }
319
+
320
+ /**
321
+ * 被服务端三连拒的放置格在搜索里避让多久。太短挡不住「drop 目标→重算→又选同一步」
322
+ * 的循环(一轮约 2s);太长会把世界已经变过的老账当禁区 —— 黑名单同时比对
323
+ * 记录时的方块名,世界变了立即放行,这个时长只兜「什么都没变」的重试。
324
+ */
325
+ export const PLACE_MISS_TTL_MS = 3 * 60_000;
326
+
327
+ let installed = false;
328
+
329
+ /** 幂等;进程内装一次,对所有 Movements/AStar 实例生效 */
330
+ export function installPathfinderPerf(log?: Logger): void {
331
+ if (installed) return;
332
+ installed = true;
333
+
334
+ const mProto = Movements.prototype as unknown as PatchedMovements & {
335
+ getBlock(pos: { x: number; y: number; z: number } | null, dx: number, dy: number, dz: number): unknown;
336
+ safeOrBreak(block: BlockLike, toBreak: unknown[]): number;
337
+ clearCollisionIndex(): void;
338
+ updateCollisionIndex(): void;
339
+ getNeighbors(node: unknown): PlacingMove[];
340
+ getScaffoldingItem(): unknown;
341
+ scafoldingBlocks: number[];
342
+ };
343
+ const origGetBlock = mProto.getBlock;
344
+
345
+ mProto.getBlock = function (this: PatchedMovements & { getBlock: unknown }, pos, dx, dy, dz) {
346
+ const cache = this.__sliceCache;
347
+ if (!cache || !pos) return origGetBlock.call(this, pos, dx, dy, dz);
348
+ const wx = Math.floor(pos.x) + dx;
349
+ const wy = Math.floor(pos.y) + dy;
350
+ const wz = Math.floor(pos.z) + dz;
351
+ const key = packPos(wx, wy, wz);
352
+ let b = cache.blocks.get(key);
353
+ if (b !== undefined) return b;
354
+
355
+ const { tables, bits } = cache;
356
+ const world = this.bot.world;
357
+ if (!tables || !bits || !world) {
358
+ // 没有 registry/world 的非常规 bot:退回原始读块,只保留缓存
359
+ b = origGetBlock.call(this, pos, dx, dy, dz);
360
+ cache.blocks.set(key, b);
361
+ return b;
362
+ }
363
+ const vec = new (Vec3 as new (x: number, y: number, z: number) => { x: number; y: number; z: number })(wx, wy, wz);
364
+ const stateId = world.getBlockStateId(vec) ?? 0;
365
+ if (stateId === 0) {
366
+ // getBlockStateId 对缺列也回 0:必须区分"空气"与"区块没加载"(后者不可走)
367
+ const ck = (wx >> 4) * 0x100000 + (wz >> 4);
368
+ let loaded = cache.chunkKnown.get(ck);
369
+ if (loaded === undefined) {
370
+ loaded = world.getColumn(wx >> 4, wz >> 4) !== undefined;
371
+ cache.chunkKnown.set(ck, loaded);
372
+ }
373
+ if (!loaded) {
374
+ // 与原版缺块兜底逐字一致(height=dy 是相对量,随调用方变,不能缓存)
375
+ return { replaceable: false, canFall: false, safe: false, physical: false, liquid: false, climbable: false, height: dy, openable: false };
376
+ }
377
+ }
378
+ const type = tables.typeId[stateId];
379
+ const tb = bits[type];
380
+ const climbable = (tb & BIT_CLIMB) !== 0;
381
+ const physical = tables.bbBlock[stateId] === 1 && (tb & BIT_FENCE) === 0;
382
+ const built = {
383
+ type,
384
+ name: tables.names[type],
385
+ position: vec,
386
+ shapes: tables.shapes[stateId],
387
+ climbable,
388
+ safe: (tables.bbEmpty[stateId] === 1 || climbable || (tb & BIT_CARPET) !== 0) && (tb & BIT_AVOID) === 0,
389
+ physical,
390
+ replaceable: (tb & BIT_REPLACE) !== 0 && !physical,
391
+ liquid: (tb & BIT_LIQUID) !== 0,
392
+ canFall: (tb & BIT_GRAVITY) !== 0,
393
+ openable: (tb & BIT_OPEN) !== 0,
394
+ height: wy + tables.delta[stateId],
395
+ // 0 = 不是门;非 0 的含义见 DOOR_*。挂在块上是给移动生成器用的(上半扇不许落脚、
396
+ // 斜着不进门框),字段常在于是块的形状稳定,不会因为门而多出一种隐藏类。
397
+ door: tables.doorState[stateId],
398
+ };
399
+ if (built.door !== 0) applyDoorState(built, built.door, wy, this.canOpenDoors === true);
400
+ b = built;
401
+ cache.blocks.set(key, b);
402
+ return b;
403
+ };
404
+
405
+ // 空实体索引占了热路径近两成:原版对空索引也逐次拼 `x,y,z` 字符串键去查。
406
+ // 空/非空在 clear/update 时就已确定,空索引直接短路成 0。
407
+ const origGetNumEntitiesAt = mProto.getNumEntitiesAt;
408
+ mProto.getNumEntitiesAt = function (this: PatchedMovements, pos, dx, dy, dz) {
409
+ if (this.__entityIdxEmpty === true) return 0;
410
+ return origGetNumEntitiesAt.call(this, pos, dx, dy, dz);
411
+ };
412
+ const origClearCollisionIndex = mProto.clearCollisionIndex;
413
+ mProto.clearCollisionIndex = function (this: PatchedMovements) {
414
+ origClearCollisionIndex.call(this);
415
+ this.__entityIdxEmpty = true;
416
+ };
417
+ const origUpdateCollisionIndex = mProto.updateCollisionIndex;
418
+ mProto.updateCollisionIndex = function (this: PatchedMovements) {
419
+ origUpdateCollisionIndex.call(this);
420
+ for (const _ in this.entityIntersections) { this.__entityIdxEmpty = false; return; }
421
+ this.__entityIdxEmpty = true;
422
+ };
423
+
424
+ // safeToBreak 是纯世界判断(液体邻查 5 次读块),同一堵墙被相邻节点反复评估,按位置记忆化
425
+ const origSafeToBreak = mProto.safeToBreak;
426
+ mProto.safeToBreak = function (this: PatchedMovements, block: BlockLike & { position: { x: number; y: number; z: number } }) {
427
+ const cache = this.__sliceCache;
428
+ // 退避判据带时效,排在记忆化之前:过期之后同一格立刻恢复可挖
429
+ const backoff = this.__digBackoffFn;
430
+ if (backoff && block.position
431
+ && backoff(block.position.x, block.position.y, block.position.z)) return false;
432
+ if (!cache || !block.position) return origSafeToBreak.call(this, block);
433
+ const key = packPos(block.position.x, block.position.y, block.position.z);
434
+ let ok = cache.breakable.get(key);
435
+ if (ok === undefined) {
436
+ ok = origSafeToBreak.call(this, block) as boolean;
437
+ cache.breakable.set(key, ok);
438
+ }
439
+ return ok;
440
+ };
441
+
442
+ // 逐字复刻原版 safeOrBreak,仅把「选工具 + digTime → laborCost」按 block.type 记忆化
443
+ mProto.safeOrBreak = function (this: PatchedMovements, block: BlockLike, toBreak: unknown[]) {
444
+ let cost = 0;
445
+ cost += this.exclusionStep(block);
446
+ cost += this.getNumEntitiesAt(block.position, 0, 0, 0) * this.entityCost;
447
+ if (block.safe) return cost;
448
+ if (!this.safeToBreak(block)) return 100;
449
+ toBreak.push(block.position);
450
+ if (block.physical) cost += this.getNumEntitiesAt(block.position, 0, 1, 0) * this.entityCost;
451
+
452
+ const cache = this.__sliceCache;
453
+ let labor = cache?.labor.get(block.type);
454
+ if (labor === undefined) {
455
+ // 特征表 POJO 没有 digTime 方法,这里按需物化真 Block(每类型每搜索只走一次)
456
+ const real = (typeof block.digTime === 'function'
457
+ ? block
458
+ : origGetBlock.call(this, block.position as { x: number; y: number; z: number }, 0, 0, 0)) as BlockLike;
459
+ const tool = this.bot.pathfinder.bestHarvestTool(real);
460
+ const enchants = (tool && tool.nbt) ? nbt.simplify(tool.nbt).Enchantments : [];
461
+ const digTime = real.digTime!(tool ? tool.type : null, false, false, false, enchants, this.bot.entity.effects);
462
+ labor = (1 + 3 * digTime / 1000) * this.digCost;
463
+ cache?.labor.set(block.type, labor);
464
+ }
465
+ cost += labor;
466
+ return cost;
467
+ };
468
+
469
+ // 上游全库唯一一处 block 对象原地突变在 getMoveJumpUp:`blockC.height += 1`
470
+ // (模拟垫块后的高度)。原版每次 getBlock 都是新实例,改完即弃;有了缓存这个
471
+ // 突变会留在共享实例里越加越高,污染整个搜索。逐字复刻,突变改局部变量。
472
+ type MoveBlock = { physical: boolean; replaceable: boolean; liquid: boolean; height: number; position: { x: number; y: number; z: number } };
473
+ (mProto as unknown as {
474
+ getMoveJumpUp(node: { x: number; y: number; z: number; remainingBlocks: number }, dir: { x: number; z: number }, neighbors: unknown[]): void;
475
+ }).getMoveJumpUp = function (
476
+ this: PatchedMovements & {
477
+ getBlock(pos: unknown, dx: number, dy: number, dz: number): MoveBlock;
478
+ exclusionPlace(block: unknown): number;
479
+ safeOrBreak(block: unknown, toBreak: unknown[]): number;
480
+ placeCost: number;
481
+ },
482
+ node, dir, neighbors,
483
+ ) {
484
+ const blockA = this.getBlock(node, 0, 2, 0);
485
+ const blockH = this.getBlock(node, dir.x, 2, dir.z);
486
+ const blockB = this.getBlock(node, dir.x, 1, dir.z);
487
+ const blockC = this.getBlock(node, dir.x, 0, dir.z);
488
+
489
+ let cost = 2; // move cost (move+jump)
490
+ const toBreak: unknown[] = [];
491
+ const toPlace: unknown[] = [];
492
+
493
+ if (blockA.physical && this.getNumEntitiesAt(blockA.position, 0, 1, 0) > 0) return;
494
+ if (blockH.physical && this.getNumEntitiesAt(blockH.position, 0, 1, 0) > 0) return;
495
+ if (blockB.physical && !blockH.physical && !blockC.physical && this.getNumEntitiesAt(blockB.position, 0, 1, 0) > 0) return;
496
+
497
+ let landHeight = blockC.height; // 原版此处直改 blockC.height,这里用局部量
498
+ if (!blockC.physical) {
499
+ if (node.remainingBlocks === 0) return;
500
+ /* 脚在液体且脚下无实底时禁止生成放置动作;有实底的浅水允许放置。 */
501
+ if (this.getBlock(node, 0, 0, 0).liquid && !this.getBlock(node, 0, -1, 0).physical) return;
502
+ if (this.getNumEntitiesAt(blockC.position, 0, 0, 0) > 0) return;
503
+ const blockD = this.getBlock(node, dir.x, -1, dir.z);
504
+ if (!blockD.physical) {
505
+ if (node.remainingBlocks === 1) return;
506
+ if (this.getNumEntitiesAt(blockD.position, 0, 0, 0) > 0) return;
507
+ if (!blockD.replaceable) {
508
+ if (!this.safeToBreak(blockD)) return;
509
+ cost += this.exclusionBreak(blockD);
510
+ toBreak.push(blockD.position);
511
+ }
512
+ cost += this.exclusionPlace(blockD);
513
+ toPlace.push({ x: node.x, y: node.y - 1, z: node.z, dx: dir.x, dy: 0, dz: dir.z, returnPos: new Vec3(node.x, node.y, node.z) });
514
+ cost += this.placeCost;
515
+ }
516
+ if (!blockC.replaceable) {
517
+ if (!this.safeToBreak(blockC)) return;
518
+ cost += this.exclusionBreak(blockC);
519
+ toBreak.push(blockC.position);
520
+ }
521
+ cost += this.exclusionPlace(blockC);
522
+ toPlace.push({ x: node.x + dir.x, y: node.y - 1, z: node.z + dir.z, dx: 0, dy: 1, dz: 0 });
523
+ cost += this.placeCost;
524
+
525
+ landHeight += 1;
526
+ }
527
+
528
+ const block0 = this.getBlock(node, 0, -1, 0);
529
+ // 深水中脚下还是水,它的坐标比水面节点低一格;用它量岸高会把
530
+ // 普通一格岸误判为两格跳。游泳起跳面是当前节点高度。
531
+ const takeoffHeight = block0.liquid ? node.y : block0.height;
532
+ if (landHeight - takeoffHeight > 1.2) return;
533
+
534
+ cost += this.safeOrBreak(blockA, toBreak);
535
+ if (cost > 100) return;
536
+ cost += this.safeOrBreak(blockH, toBreak);
537
+ if (cost > 100) return;
538
+ cost += this.safeOrBreak(blockB, toBreak);
539
+ if (cost > 100) return;
540
+
541
+ neighbors.push(new Move(blockB.position.x, blockB.position.y, blockB.position.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace));
542
+ };
543
+
544
+ // 允许从有实底的浅水垫塔上升,并计入 liquidCost。
545
+ // 执行侧 placingBlock.jump 会持续上浮,越过脚格后放置支撑。
546
+ type UpBlock = {
547
+ liquid: boolean; climbable: boolean; replaceable: boolean; physical: boolean;
548
+ height: number; position: { x: number; y: number; z: number };
549
+ };
550
+ (mProto as unknown as {
551
+ getMoveUp(node: { x: number; y: number; z: number; remainingBlocks: number }, neighbors: unknown[]): void;
552
+ }).getMoveUp = function (
553
+ this: PatchedMovements & {
554
+ getBlock(pos: unknown, dx: number, dy: number, dz: number): UpBlock;
555
+ exclusionPlace(block: unknown): number;
556
+ safeOrBreak(block: unknown, toBreak: unknown[]): number;
557
+ allow1by1towers: boolean;
558
+ placeCost: number;
559
+ liquidCost: number;
560
+ },
561
+ node, neighbors,
562
+ ) {
563
+ const block1 = this.getBlock(node, 0, 0, 0);
564
+ if (this.getNumEntitiesAt(node, 0, 0, 0) > 0) return; // 建造位上压着别的实体
565
+
566
+ const block2 = this.getBlock(node, 0, 2, 0);
567
+
568
+ let cost = 1; // move cost
569
+ const toBreak: unknown[] = [];
570
+ const toPlace: unknown[] = [];
571
+ cost += this.safeOrBreak(block2, toBreak);
572
+ if (cost > 100) return;
573
+
574
+ if (!block1.climbable) {
575
+ if (!this.allow1by1towers || node.remainingBlocks === 0) return; // 垫脚方块不够
576
+ if (!block1.replaceable) {
577
+ if (!this.safeToBreak(block1)) return;
578
+ toBreak.push(block1.position);
579
+ }
580
+ const block0 = this.getBlock(node, 0, -1, 0);
581
+ if (block0.physical && block0.height - node.y < -0.2) return; // 半砖上跳不起来
582
+ // 垫脚落点预检(见模块头第 5 条):脚下那一格就是这一放的参照,水里没有面可贴
583
+ if (block1.liquid && !block0.physical) return;
584
+ cost += this.exclusionPlace(block1);
585
+ toPlace.push({ x: node.x, y: node.y - 1, z: node.z, dx: 0, dy: 1, dz: 0, jump: true });
586
+ cost += this.placeCost;
587
+ if (block1.liquid) cost += this.liquidCost;
588
+ }
589
+
590
+ if (cost > 100) return;
591
+
592
+ neighbors.push(new Move(node.x, node.y + 1, node.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace));
593
+ };
594
+
595
+ // 搭桥同样要求脚下有实底;游泳状态下缺少可贴附的支撑面。
596
+ type FwdBlock = {
597
+ physical: boolean; replaceable: boolean; liquid: boolean; safe: boolean;
598
+ openable: boolean; shapes?: number[][]; height: number;
599
+ position: { x: number; y: number; z: number };
600
+ /** 见 DOOR_*;非本模块拼的块(退回原始读块那条)没有这一项 */
601
+ door?: number;
602
+ };
603
+ (mProto as unknown as {
604
+ getMoveForward(node: { x: number; y: number; z: number; remainingBlocks: number }, dir: { x: number; z: number }, neighbors: unknown[]): void;
605
+ }).getMoveForward = function (
606
+ this: PatchedMovements & {
607
+ getBlock(pos: unknown, dx: number, dy: number, dz: number): FwdBlock;
608
+ exclusionStep(block: unknown): number;
609
+ exclusionBreak(block: unknown): number;
610
+ exclusionPlace(block: unknown): number;
611
+ safeOrBreak(block: unknown, toBreak: unknown[]): number;
612
+ canOpenDoors: boolean;
613
+ placeCost: number;
614
+ liquidCost: number;
615
+ },
616
+ node, dir, neighbors,
617
+ ) {
618
+ const blockB = this.getBlock(node, dir.x, 1, dir.z);
619
+ const blockC = this.getBlock(node, dir.x, 0, dir.z);
620
+ const blockD = this.getBlock(node, dir.x, -1, dir.z);
621
+
622
+ let cost = 1; // move cost
623
+ cost += this.exclusionStep(blockC);
624
+
625
+ const toBreak: unknown[] = [];
626
+ const toPlace: unknown[] = [];
627
+
628
+ if (!blockD.physical && !blockC.liquid) {
629
+ if (node.remainingBlocks === 0) return; // not enough blocks to place
630
+ // 垫脚落点预检(见模块头第 5/7 条):搭桥参照脚下那一格,游着没有面可贴
631
+ if (this.getBlock(node, 0, 0, 0).liquid && !this.getBlock(node, 0, -1, 0).physical) return;
632
+ if (this.getNumEntitiesAt(blockD.position, 0, 0, 0) > 0) return; // D intersects an entity hitbox
633
+ if (!blockD.replaceable) {
634
+ if (!this.safeToBreak(blockD)) return;
635
+ cost += this.exclusionBreak(blockD);
636
+ toBreak.push(blockD.position);
637
+ }
638
+ cost += this.exclusionPlace(blockC);
639
+ toPlace.push({ x: node.x, y: node.y - 1, z: node.z, dx: dir.x, dy: 0, dz: dir.z });
640
+ cost += this.placeCost; // additional cost for placing a block
641
+ }
642
+
643
+ cost += this.safeOrBreak(blockB, toBreak);
644
+ if (cost > 100) return;
645
+
646
+ // Open fence gates
647
+ if (this.canOpenDoors && blockC.openable && blockC.shapes && blockC.shapes.length !== 0) {
648
+ toPlace.push({ x: node.x + dir.x, y: node.y, z: node.z + dir.z, dx: 0, dy: 0, dz: 0, useOne: true });
649
+ } else {
650
+ cost += this.safeOrBreak(blockC, toBreak);
651
+ if (cost > 100) return;
652
+ }
653
+
654
+ if (this.getBlock(node, 0, 0, 0).liquid) cost += this.liquidCost;
655
+
656
+ // 门的上半扇只当头顶净空,不能当落脚格:那一格的地板正是下半扇门,踩上去等于
657
+ // 站在门里。applyDoorState 把两半都标成可穿过(否则 blockB 会被算成要挖的墙),
658
+ // 落脚这一头就必须在这里挡住,不然 A* 会生出"顺着门爬上去"的假路。
659
+ if (((blockC.door ?? 0) & DOOR_UPPER) !== 0) return;
660
+
661
+ neighbors.push(new Move(blockC.position.x, blockC.position.y, blockC.position.z, node.remainingBlocks - toPlace.length, cost, toBreak, toPlace));
662
+ };
663
+
664
+ /**
665
+ * 斜着不进门框。
666
+ *
667
+ * getMoveDiagonal 里没有 useOne 那条分支(上游只给 getMoveForward 写了开门),
668
+ * 而 applyDoorState 把关着的木门也标成了"可穿过" —— 两下一凑,A* 会生出一条
669
+ * 斜着穿过关着的门的路,走到那儿门还关着,人就撞在门板上。开着的门也一样按
670
+ * 正面进:开门后的碰撞盒占着边上 3/16 格,斜切进去会刮在门板上。
671
+ *
672
+ * 门口本来就该正面进,绕成"先站到门正前方"只多 1 格代价。
673
+ */
674
+ const origGetMoveDiagonal = (mProto as unknown as {
675
+ getMoveDiagonal(node: unknown, dir: unknown, neighbors: unknown[]): void;
676
+ }).getMoveDiagonal;
677
+ (mProto as unknown as {
678
+ getMoveDiagonal(node: unknown, dir: unknown, neighbors: unknown[]): void;
679
+ }).getMoveDiagonal = function (
680
+ this: PatchedMovements & {
681
+ getBlock(pos: unknown, dx: number, dy: number, dz: number): { door?: number };
682
+ },
683
+ node, dir, neighbors,
684
+ ) {
685
+ const before = neighbors.length;
686
+ origGetMoveDiagonal.call(this, node, dir, neighbors);
687
+ for (let i = neighbors.length - 1; i >= before; i--) {
688
+ const mv = neighbors[i] as { x: number; y: number; z: number };
689
+ if (((this.getBlock(mv, 0, 0, 0).door ?? 0) & DOOR_KIND) !== 0) neighbors.splice(i, 1);
690
+ }
691
+ };
692
+
693
+ /**
694
+ * 禁垫区(见模块头第 6 条)在**候选移动**这一层筛,不在各 getMoveX 里逐个判:
695
+ * 上游有五处 `toPlace.push`,分散在四个生成器里,而 `exclusionAreasPlace` 这个
696
+ * 上游钩子在 getMoveForward 里传的是落点上面那一格(blockC),按包围盒判会漏掉
697
+ * 贴着区顶那一层的搭路。落点一律是"参照方块 + 面向量",在这里算得准且只算一次。
698
+ */
699
+ const origGetNeighbors = mProto.getNeighbors;
700
+ mProto.getNeighbors = function (
701
+ this: PatchedMovements & {
702
+ getBlock(pos: { x: number; y: number; z: number } | null, dx: number, dy: number, dz: number): { physical: boolean; name?: string };
703
+ bot: { placeMisses?: Array<{ was: string; x: number; y: number; z: number; at?: number }> };
704
+ },
705
+ node,
706
+ ) {
707
+ const moves = origGetNeighbors.call(this, node);
708
+ const zones = this.__siteZones;
709
+ const noPlace = this.__noPlaceFn;
710
+ // 服务端刚三连拒过的放置,同格同块短期内还会拒:进搜索黑名单,逼 A* 换条路
711
+ // (没有这条,drop 目标 → 重算 → 又选中同一个最便宜的放置,循环一整个小时)
712
+ const now = Date.now();
713
+ let misses: Map<string, string> | null = null;
714
+ for (const miss of this.bot?.placeMisses ?? []) {
715
+ if (miss.at === undefined || now - miss.at > PLACE_MISS_TTL_MS) continue;
716
+ (misses ??= new Map()).set(`${miss.x},${miss.y},${miss.z}`, miss.was);
717
+ }
718
+ if ((zones === null || zones === undefined || zones.length === 0) && !noPlace && !misses) return moves;
719
+ return moves.filter((mv) => {
720
+ // 注意这里不做"参照格必须实心"的静态预检:A* 的跨步放置合法地踩在
721
+ // 前几步刚垫的块上,搜索时世界里那一格还是水/空气(垫塔第二步的参照
722
+ // 就是第一步的落点)。执行必败的那几类由各生成器的点 5 预检按语义挡。
723
+ for (const p of mv.toPlace ?? []) {
724
+ if (p.useOne === true) continue;
725
+ const [x, y, z] = [p.x + p.dx, p.y + p.dy, p.z + p.dz];
726
+ // 落点自己在成果登记上、或它正压在一格登记的东西头顶,两种都不生成这个放置
727
+ if (zones !== null && zones !== undefined && zones.length > 0 && inZones(zones, x, y, z)) return false;
728
+ if (noPlace !== null && noPlace !== undefined && (noPlace(x, y, z) || noPlace(x, y - 1, z))) return false;
729
+ if (misses) {
730
+ const was = misses.get(`${x},${y},${z}`);
731
+ // 那一格还是被拒时的那种方块 → 同一结局;世界变过了就放行重试
732
+ if (was !== undefined && this.getBlock({ x, y, z }, 0, 0, 0).name === was) return false;
733
+ }
734
+ }
735
+ return true;
736
+ });
737
+ };
738
+
739
+ /**
740
+ * 工地建材在垫脚候选中排最后,背包只剩建材时仍可使用。
741
+ * 上游按 scafoldingBlocks 顺序选物品,此处临时重排优先级。
742
+ */
743
+ const origGetScaffoldingItem = mProto.getScaffoldingItem;
744
+ mProto.getScaffoldingItem = function (this: PatchedMovements & {
745
+ getScaffoldingItem(): unknown; scafoldingBlocks: number[];
746
+ }) {
747
+ const zones = this.__siteZonesFn?.();
748
+ if (!zones || zones.length === 0) return origGetScaffoldingItem.call(this);
749
+ const byName = (this.bot.registry as { itemsByName?: Record<string, { id: number } | undefined> } | undefined)
750
+ ?.itemsByName;
751
+ if (!byName) return origGetScaffoldingItem.call(this);
752
+ const last = new Set<number>();
753
+ for (const zone of zones) for (const name of zone.materials) {
754
+ const id = byName[name]?.id;
755
+ if (id !== undefined) last.add(id);
756
+ }
757
+ if (last.size === 0) return origGetScaffoldingItem.call(this);
758
+ const saved = this.scafoldingBlocks;
759
+ this.scafoldingBlocks = [...saved.filter((id) => !last.has(id)), ...saved.filter((id) => last.has(id))];
760
+ try {
761
+ return origGetScaffoldingItem.call(this);
762
+ } finally {
763
+ this.scafoldingBlocks = saved;
764
+ }
765
+ };
766
+
767
+ const aProto = (AStar as unknown as { prototype: { compute(): unknown; movements: unknown } }).prototype;
768
+ const origCompute = aProto.compute;
769
+ aProto.compute = function (this: { movements: PatchedMovements; __searchCache?: SliceCache }) {
770
+ // 缓存随单次搜索生老病死:pathfinder 对相关方块变化本就整个重开搜索(新 AStar),
771
+ // 旧缓存跟着旧搜索一起被丢;compute 之外(路径跟随)读到的永远是新鲜世界。
772
+ if (!this.__searchCache) {
773
+ const m = this.movements;
774
+ const tables = m.bot.registry && m.bot.world ? tablesFor(m.bot.registry) : null;
775
+ this.__searchCache = {
776
+ blocks: new Map(),
777
+ labor: new Map(),
778
+ breakable: new Map(),
779
+ tables,
780
+ bits: tables ? buildBits(m, tables.maxType) : null,
781
+ chunkKnown: new Map(),
782
+ zones: m.__siteZonesFn?.() ?? null,
783
+ };
784
+ }
785
+ this.movements.__sliceCache = this.__searchCache;
786
+ this.movements.__siteZones = this.__searchCache.zones;
787
+ try {
788
+ return origCompute.call(this);
789
+ } finally {
790
+ this.movements.__sliceCache = null;
791
+ this.movements.__siteZones = null;
792
+ }
793
+ };
794
+
795
+ log?.info('寻路性能补丁已装上:特征表读块 + 搜索级缓存 + 挖掘成本记忆化');
796
+ }