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.
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/package.json +43 -0
- package/src/boot.ts +46 -0
- package/src/bot.ts +1342 -0
- package/src/config-file.ts +41 -0
- package/src/core/README.md +92 -0
- package/src/core/billing.ts +26 -0
- package/src/core/blobs.ts +106 -0
- package/src/core/bus.ts +311 -0
- package/src/core/config-schema.ts +201 -0
- package/src/core/config.ts +262 -0
- package/src/core/core.ts +699 -0
- package/src/core/cost.ts +244 -0
- package/src/core/event-store.ts +304 -0
- package/src/core/fork.ts +113 -0
- package/src/core/generation.ts +118 -0
- package/src/core/instance-lock.ts +215 -0
- package/src/core/ipc-logger.ts +105 -0
- package/src/core/language.ts +62 -0
- package/src/core/log-context.ts +38 -0
- package/src/core/loop.ts +1858 -0
- package/src/core/markers.ts +48 -0
- package/src/core/prefix.ts +121 -0
- package/src/core/run.ts +95 -0
- package/src/core/secrets.ts +18 -0
- package/src/core/session.ts +56 -0
- package/src/core/sessions.ts +261 -0
- package/src/core/state.ts +104 -0
- package/src/core/template.ts +72 -0
- package/src/core/timers.ts +129 -0
- package/src/core/tool-log.ts +135 -0
- package/src/core/transcript.ts +92 -0
- package/src/core/truncate.ts +121 -0
- package/src/core/types.ts +1140 -0
- package/src/core/usage-log.ts +79 -0
- package/src/core/util.ts +386 -0
- package/src/deploy-listing.ts +160 -0
- package/src/deploy.ts +162 -0
- package/src/extensions/README.md +72 -0
- package/src/extensions/dry-mount.ts +390 -0
- package/src/extensions/manifest.ts +153 -0
- package/src/extensions/runtime.ts +42 -0
- package/src/extensions.ts +519 -0
- package/src/launcher.ts +285 -0
- package/src/paths.ts +155 -0
- package/src/protocol/open-responses/LICENSE +201 -0
- package/src/protocol/open-responses/README.md +14 -0
- package/src/protocol/open-responses/context-helpers.ts +27 -0
- package/src/protocol/open-responses/context-log.ts +59 -0
- package/src/protocol/open-responses/context.ts +89 -0
- package/src/protocol/open-responses/generated.ts +115 -0
- package/src/protocol/open-responses/index.ts +33 -0
- package/src/protocol/open-responses/openapi.json +4230 -0
- package/src/protocol/open-responses/stream.ts +146 -0
- package/src/protocol/open-responses/tokens.ts +31 -0
- package/src/providers/README.md +86 -0
- package/src/providers/base.ts +111 -0
- package/src/providers/configuration.ts +97 -0
- package/src/providers/console/config.ts +21 -0
- package/src/providers/console/settings.ts +443 -0
- package/src/providers/console/strings.ts +74 -0
- package/src/providers/console/types.ts +11 -0
- package/src/providers/llamacpp/archive.ts +148 -0
- package/src/providers/llamacpp/catalog.ts +247 -0
- package/src/providers/llamacpp/config.ts +36 -0
- package/src/providers/llamacpp/console/client.ts +6 -0
- package/src/providers/llamacpp/console/models-panel.ts +120 -0
- package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
- package/src/providers/llamacpp/console/server.ts +153 -0
- package/src/providers/llamacpp/index.ts +95 -0
- package/src/providers/llamacpp/native.ts +51 -0
- package/src/providers/llamacpp/options.ts +117 -0
- package/src/providers/llamacpp/runtime-store.ts +139 -0
- package/src/providers/llamacpp/runtime.ts +214 -0
- package/src/providers/llamacpp/server.ts +286 -0
- package/src/providers/llamacpp/strings.ts +230 -0
- package/src/providers/openai-responses-compat/config.ts +20 -0
- package/src/providers/openai-responses-compat/index.ts +89 -0
- package/src/providers/openai-responses-compat/native.ts +127 -0
- package/src/providers/openai-responses-compat/strings.ts +19 -0
- package/src/providers/pricebook.ts +75 -0
- package/src/providers/registry.ts +137 -0
- package/src/providers/strings.ts +71 -0
- package/src/providers/transport/chat.ts +156 -0
- package/src/providers/transport/errors.ts +48 -0
- package/src/providers/transport/history.ts +68 -0
- package/src/providers/transport/native-input.ts +83 -0
- package/src/providers/transport/native-types.ts +13 -0
- package/src/providers/transport/response-assembly.ts +191 -0
- package/src/providers/transport/response-http.ts +196 -0
- package/src/providers/transport/response-meters.ts +27 -0
- package/src/providers/transport/responses-input.ts +56 -0
- package/src/web/README.md +84 -0
- package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
- package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
- package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
- package/src/web/client/console-pages/builtins.ts +10 -0
- package/src/web/client/console-pages/context.ts +135 -0
- package/src/web/client/console-pages/host.ts +633 -0
- package/src/web/client/console-pages/loader.ts +132 -0
- package/src/web/client/console-pages/strings.ts +90 -0
- package/src/web/client/console-pages/tools/strings.ts +49 -0
- package/src/web/client/console-pages/tools/view.ts +215 -0
- package/src/web/client/core/api.ts +200 -0
- package/src/web/client/core/language.ts +56 -0
- package/src/web/client/core/lifecycle.ts +132 -0
- package/src/web/client/core/router.ts +199 -0
- package/src/web/client/core/stream.ts +157 -0
- package/src/web/client/core/websocket.ts +82 -0
- package/src/web/client/features/appearance/index.ts +333 -0
- package/src/web/client/features/appearance/strings.ts +87 -0
- package/src/web/client/features/config/index.ts +11 -0
- package/src/web/client/features/config/strings.ts +41 -0
- package/src/web/client/features/config/view.ts +428 -0
- package/src/web/client/features/core/events.ts +197 -0
- package/src/web/client/features/core/index.ts +254 -0
- package/src/web/client/features/core/run.ts +56 -0
- package/src/web/client/features/core/runlog.ts +214 -0
- package/src/web/client/features/core/sessions.ts +53 -0
- package/src/web/client/features/core/strings.ts +157 -0
- package/src/web/client/features/extensions/index.ts +422 -0
- package/src/web/client/features/extensions/strings.ts +164 -0
- package/src/web/client/features/feature.ts +93 -0
- package/src/web/client/features/live/context.ts +280 -0
- package/src/web/client/features/live/fork.ts +156 -0
- package/src/web/client/features/live/index.ts +392 -0
- package/src/web/client/features/live/onboarding.ts +113 -0
- package/src/web/client/features/live/protocol.ts +122 -0
- package/src/web/client/features/live/sessions.ts +71 -0
- package/src/web/client/features/live/status.ts +68 -0
- package/src/web/client/features/live/strings.ts +252 -0
- package/src/web/client/features/live/timeline.ts +590 -0
- package/src/web/client/features/prompts/editor.ts +292 -0
- package/src/web/client/features/prompts/index.ts +346 -0
- package/src/web/client/features/prompts/strings.ts +136 -0
- package/src/web/client/features/prompts/view.ts +256 -0
- package/src/web/client/features/providers/index.ts +100 -0
- package/src/web/client/features/providers/strings.ts +23 -0
- package/src/web/client/features/settings/general.ts +28 -0
- package/src/web/client/features/settings/index.ts +82 -0
- package/src/web/client/features/settings/strings.ts +33 -0
- package/src/web/client/features/storage/index.ts +1 -0
- package/src/web/client/features/storage/strings.ts +45 -0
- package/src/web/client/features/storage/view.ts +154 -0
- package/src/web/client/features/usage/chart.ts +486 -0
- package/src/web/client/features/usage/index.ts +407 -0
- package/src/web/client/features/usage/labels.ts +46 -0
- package/src/web/client/features/usage/range.ts +36 -0
- package/src/web/client/features/usage/state.ts +37 -0
- package/src/web/client/features/usage/strings.ts +191 -0
- package/src/web/client/features/usage/tooltip.ts +86 -0
- package/src/web/client/features/usage/types.ts +96 -0
- package/src/web/client/features/worlds/index.ts +424 -0
- package/src/web/client/features/worlds/strings.ts +121 -0
- package/src/web/client/main.ts +273 -0
- package/src/web/client/shell/avatar.ts +233 -0
- package/src/web/client/shell/index.ts +549 -0
- package/src/web/client/shell/strings.ts +89 -0
- package/src/web/client/theme/handoff.ts +43 -0
- package/src/web/client/theme/palette.ts +109 -0
- package/src/web/client/theme/registry.ts +130 -0
- package/src/web/client/theme/storage.ts +72 -0
- package/src/web/client/theme/strings.ts +123 -0
- package/src/web/client/theme/studio.ts +366 -0
- package/src/web/client/ui/actions.ts +109 -0
- package/src/web/client/ui/data.ts +185 -0
- package/src/web/client/ui/dom.ts +23 -0
- package/src/web/client/ui/fields.ts +191 -0
- package/src/web/client/ui/format.ts +75 -0
- package/src/web/client/ui/icons.ts +183 -0
- package/src/web/client/ui/images.ts +91 -0
- package/src/web/client/ui/index.ts +89 -0
- package/src/web/client/ui/lamp.ts +125 -0
- package/src/web/client/ui/log.ts +98 -0
- package/src/web/client/ui/overlay.ts +184 -0
- package/src/web/client/ui/page.ts +8 -0
- package/src/web/client/ui/prompt-input.tsx +311 -0
- package/src/web/client/ui/sheet.ts +131 -0
- package/src/web/client/ui/strings.ts +71 -0
- package/src/web/console-pages.ts +367 -0
- package/src/web/files.ts +107 -0
- package/src/web/path-picker.ts +296 -0
- package/src/web/public/index.html +17 -0
- package/src/web/public/styles.css +1715 -0
- package/src/web/server.ts +1883 -0
- package/src/web/shared/client-panel.ts +603 -0
- package/src/web/shared/console-protocol.ts +560 -0
- package/src/web/shared/css.d.ts +1 -0
- package/src/web/shared/path-picker.ts +23 -0
- package/src/web/shared/theme.ts +256 -0
- package/src/web/theme-store.ts +37 -0
- package/src/world.ts +449 -0
- package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
- package/src/worlds/bilibili/README.md +176 -0
- package/src/worlds/bilibili/audience-admission.ts +995 -0
- package/src/worlds/bilibili/client.ts +528 -0
- package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
- package/src/worlds/bilibili/config.ts +185 -0
- package/src/worlds/bilibili/console/client.ts +202 -0
- package/src/worlds/bilibili/definition.ts +35 -0
- package/src/worlds/bilibili/gift-frame.ts +131 -0
- package/src/worlds/bilibili/normalize.ts +452 -0
- package/src/worlds/bilibili/overlay/announcement.ts +76 -0
- package/src/worlds/bilibili/overlay/assets.ts +82 -0
- package/src/worlds/bilibili/overlay/model.ts +494 -0
- package/src/worlds/bilibili/overlay/project.ts +202 -0
- package/src/worlds/bilibili/overlay/server.ts +286 -0
- package/src/worlds/bilibili/overlay/types.ts +217 -0
- package/src/worlds/bilibili/overlay/web/app.js +690 -0
- package/src/worlds/bilibili/overlay/web/editor.css +297 -0
- package/src/worlds/bilibili/overlay/web/editor.html +102 -0
- package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
- package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
- package/src/worlds/bilibili/overlay/web/styles.css +258 -0
- package/src/worlds/bilibili/protobuf.ts +123 -0
- package/src/worlds/bilibili/wire.ts +94 -0
- package/src/worlds/bilibili/world.ts +1389 -0
- package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
- package/src/worlds/console-fixture/console/client.ts +72 -0
- package/src/worlds/console-fixture/world.ts +73 -0
- package/src/worlds/index.ts +15 -0
- package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
- package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
- package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
- package/src/worlds/minecraft/README.md +917 -0
- package/src/worlds/minecraft/blueprint-plan.ts +915 -0
- package/src/worlds/minecraft/blueprint-registry.ts +566 -0
- package/src/worlds/minecraft/blueprint-repair.ts +236 -0
- package/src/worlds/minecraft/blueprint-resource.ts +272 -0
- package/src/worlds/minecraft/blueprint.ts +492 -0
- package/src/worlds/minecraft/body-lease.ts +340 -0
- package/src/worlds/minecraft/bridge.ts +749 -0
- package/src/worlds/minecraft/check.ts +651 -0
- package/src/worlds/minecraft/chests.ts +292 -0
- package/src/worlds/minecraft/client-launch.ts +272 -0
- package/src/worlds/minecraft/client-options.ts +85 -0
- package/src/worlds/minecraft/client-skins.ts +164 -0
- package/src/worlds/minecraft/client-window.ps1 +91 -0
- package/src/worlds/minecraft/client.ts +423 -0
- package/src/worlds/minecraft/combat-context.ts +109 -0
- package/src/worlds/minecraft/combat.ts +1552 -0
- package/src/worlds/minecraft/config.ts +499 -0
- package/src/worlds/minecraft/console/access.ts +218 -0
- package/src/worlds/minecraft/console/client.ts +244 -0
- package/src/worlds/minecraft/console/log.ts +110 -0
- package/src/worlds/minecraft/console/mount.ts +420 -0
- package/src/worlds/minecraft/console/skin.ts +257 -0
- package/src/worlds/minecraft/console/style.css +154 -0
- package/src/worlds/minecraft/console/world.ts +449 -0
- package/src/worlds/minecraft/deaths.ts +135 -0
- package/src/worlds/minecraft/definition.ts +18 -0
- package/src/worlds/minecraft/engine-child.ts +274 -0
- package/src/worlds/minecraft/engine-ipc.ts +98 -0
- package/src/worlds/minecraft/entity-facts.ts +132 -0
- package/src/worlds/minecraft/escape.ts +407 -0
- package/src/worlds/minecraft/executor.ts +14494 -0
- package/src/worlds/minecraft/explored.ts +156 -0
- package/src/worlds/minecraft/geometry.ts +205 -0
- package/src/worlds/minecraft/goal-plan.ts +647 -0
- package/src/worlds/minecraft/item-break.ts +126 -0
- package/src/worlds/minecraft/item-facts.ts +146 -0
- package/src/worlds/minecraft/item-pick.ts +67 -0
- package/src/worlds/minecraft/level-dat.ts +176 -0
- package/src/worlds/minecraft/log.ts +132 -0
- package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
- package/src/worlds/minecraft/names.ts +377 -0
- package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
- package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
- package/src/worlds/minecraft/piglin.ts +49 -0
- package/src/worlds/minecraft/policy.ts +547 -0
- package/src/worlds/minecraft/precheck.ts +655 -0
- package/src/worlds/minecraft/proxy.ts +445 -0
- package/src/worlds/minecraft/ranged.ts +635 -0
- package/src/worlds/minecraft/readouts.ts +102 -0
- package/src/worlds/minecraft/round.ts +26 -0
- package/src/worlds/minecraft/search-observation.ts +92 -0
- package/src/worlds/minecraft/server-config.ts +467 -0
- package/src/worlds/minecraft/server.ts +658 -0
- package/src/worlds/minecraft/show.ts +84 -0
- package/src/worlds/minecraft/skills.ts +1763 -0
- package/src/worlds/minecraft/terrain.ts +1735 -0
- package/src/worlds/minecraft/window.ts +96 -0
- package/src/worlds/minecraft/works.ts +179 -0
- package/src/worlds/minecraft/world.ts +6438 -0
- package/src/worlds/qq/ENV_PROMPT.md +5 -0
- package/src/worlds/qq/config.ts +108 -0
- package/src/worlds/qq/console/client.ts +126 -0
- package/src/worlds/qq/console/events.ts +155 -0
- package/src/worlds/qq/console/gate.ts +232 -0
- package/src/worlds/qq/console/roster.ts +203 -0
- package/src/worlds/qq/console/style.css +65 -0
- package/src/worlds/qq/conversation.ts +28 -0
- package/src/worlds/qq/definition.ts +62 -0
- package/src/worlds/qq/driver.ts +375 -0
- package/src/worlds/qq/history-tools.ts +209 -0
- package/src/worlds/qq/normalize.ts +323 -0
- package/src/worlds/qq/qface-map.ts +282 -0
- package/src/worlds/qq/vision-prompt.ts +27 -0
- package/src/worlds/qq/vision.ts +642 -0
- package/src/worlds/qq/vlm.ts +142 -0
- package/src/worlds/qq/world.ts +1503 -0
- package/src/worlds/terminal/ENV_PROMPT.md +11 -0
- package/src/worlds/terminal/config.ts +9 -0
- package/src/worlds/terminal/definition.ts +14 -0
- package/src/worlds/terminal/world.ts +700 -0
- package/src/worlds/websearch/ENV_PROMPT.md +1 -0
- package/src/worlds/websearch/brave-client.ts +261 -0
- package/src/worlds/websearch/config.ts +75 -0
- package/src/worlds/websearch/definition.ts +20 -0
- package/src/worlds/websearch/world.ts +171 -0
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import type { Bot } from 'mineflayer';
|
|
2
|
+
import { Vec3 } from 'vec3';
|
|
3
|
+
import { readDurability, type ItemLike } from './item-facts.ts';
|
|
4
|
+
|
|
5
|
+
/** 未交战时允许进入远程候选池的最大距离。感知命中本身不触发开火。 */
|
|
6
|
+
export const RANGED_ENTER_RANGE = 32;
|
|
7
|
+
/** 已进入远程交战后允许继续追踪的最大距离,避免目标在边界来回抖动。 */
|
|
8
|
+
export const RANGED_EXIT_RANGE = 40;
|
|
9
|
+
const HYBRID_RANGED_AT = 8;
|
|
10
|
+
export const HYBRID_MELEE_AT = 5.5;
|
|
11
|
+
export const KITE_MIN_RANGE = 8;
|
|
12
|
+
export const KITE_MAX_RANGE = 14;
|
|
13
|
+
const BOW_FULL_DRAW_MS = 1_000;
|
|
14
|
+
export const BOW_HIT_WINDOW_MS = 1_500;
|
|
15
|
+
|
|
16
|
+
const AIM_STEP_MS = 50;
|
|
17
|
+
const ARROW_SPEED_PER_TICK = 3;
|
|
18
|
+
const ARROW_GRAVITY_PER_TICK = 0.05;
|
|
19
|
+
const MOTION_STALE_MS = 250;
|
|
20
|
+
const MOTION_DECAY_MS = 250;
|
|
21
|
+
const MOTION_MAX_BLOCKS_PER_TICK = 1.5;
|
|
22
|
+
const MOTION_SMOOTHING = 0.5;
|
|
23
|
+
|
|
24
|
+
type InventoryItem = ReturnType<Bot['inventory']['items']>[number];
|
|
25
|
+
|
|
26
|
+
interface Point3 {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
z: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface RangedTarget {
|
|
33
|
+
id: number;
|
|
34
|
+
position: Point3;
|
|
35
|
+
height?: number;
|
|
36
|
+
width?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface PositionVelocityEstimate {
|
|
40
|
+
position: Point3;
|
|
41
|
+
velocityPerTick: Point3;
|
|
42
|
+
ageMs: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface PositionVelocityTrackerOptions {
|
|
46
|
+
staleMs?: number;
|
|
47
|
+
decayMs?: number;
|
|
48
|
+
maxBlocksPerTick?: number;
|
|
49
|
+
smoothing?: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface MotionState {
|
|
53
|
+
position: Point3;
|
|
54
|
+
velocityPerTick: Point3;
|
|
55
|
+
at: number;
|
|
56
|
+
measured: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 从实体位置事件推导速度;协议的 entity.velocity 不代表普通行走速度。 */
|
|
60
|
+
export class PositionVelocityTracker {
|
|
61
|
+
private readonly states = new Map<number, MotionState>();
|
|
62
|
+
private readonly staleMs: number;
|
|
63
|
+
private readonly decayMs: number;
|
|
64
|
+
private readonly maxBlocksPerTick: number;
|
|
65
|
+
private readonly smoothing: number;
|
|
66
|
+
|
|
67
|
+
constructor(opts: PositionVelocityTrackerOptions = {}) {
|
|
68
|
+
this.staleMs = opts.staleMs ?? MOTION_STALE_MS;
|
|
69
|
+
this.decayMs = opts.decayMs ?? MOTION_DECAY_MS;
|
|
70
|
+
this.maxBlocksPerTick = opts.maxBlocksPerTick ?? MOTION_MAX_BLOCKS_PER_TICK;
|
|
71
|
+
this.smoothing = opts.smoothing ?? MOTION_SMOOTHING;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
observe(id: number, position: Point3, at: number): void {
|
|
75
|
+
const next = copyPoint(position);
|
|
76
|
+
const previous = this.states.get(id);
|
|
77
|
+
if (!previous) {
|
|
78
|
+
this.states.set(id, { position: next, velocityPerTick: zeroPoint(), at, measured: false });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const elapsedMs = at - previous.at;
|
|
82
|
+
if (elapsedMs <= 0) return;
|
|
83
|
+
if (pointDistance(next, previous.position) < 1e-6) return;
|
|
84
|
+
const scale = 50 / elapsedMs;
|
|
85
|
+
const measured = {
|
|
86
|
+
x: (next.x - previous.position.x) * scale,
|
|
87
|
+
y: (next.y - previous.position.y) * scale,
|
|
88
|
+
z: (next.z - previous.position.z) * scale,
|
|
89
|
+
};
|
|
90
|
+
if (pointNorm(measured) > this.maxBlocksPerTick) {
|
|
91
|
+
this.states.set(id, { position: next, velocityPerTick: zeroPoint(), at, measured: false });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const velocityPerTick = previous.measured
|
|
95
|
+
? mixPoint(previous.velocityPerTick, measured, this.smoothing)
|
|
96
|
+
: measured;
|
|
97
|
+
this.states.set(id, { position: next, velocityPerTick, at, measured: true });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
estimate(id: number, at: number): PositionVelocityEstimate | null {
|
|
101
|
+
const state = this.states.get(id);
|
|
102
|
+
if (!state) return null;
|
|
103
|
+
const ageMs = Math.max(0, at - state.at);
|
|
104
|
+
const decay = ageMs <= this.staleMs
|
|
105
|
+
? 1
|
|
106
|
+
: this.decayMs <= 0 ? 0 : Math.max(0, 1 - (ageMs - this.staleMs) / this.decayMs);
|
|
107
|
+
return {
|
|
108
|
+
position: copyPoint(state.position),
|
|
109
|
+
velocityPerTick: scalePoint(state.velocityPerTick, decay),
|
|
110
|
+
ageMs,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
forget(id: number): void {
|
|
115
|
+
this.states.delete(id);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export type HybridWeapon = 'melee' | 'ranged';
|
|
120
|
+
|
|
121
|
+
/** 仅支持首期的弓和普通箭;弩、药箭和光灵箭不隐式混入。 */
|
|
122
|
+
export function bestRangedWeapon(bot: Bot): InventoryItem | null {
|
|
123
|
+
let best: InventoryItem | null = null;
|
|
124
|
+
let bestLeft = 0;
|
|
125
|
+
for (const item of bot.inventory.items()) {
|
|
126
|
+
if (!usableBow(item)) continue;
|
|
127
|
+
const left = readDurability(item)?.left ?? 0;
|
|
128
|
+
if (left > bestLeft) {
|
|
129
|
+
best = item;
|
|
130
|
+
bestLeft = left;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return best;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function hasUsableArrows(bot: Bot): boolean {
|
|
137
|
+
return bot.inventory.items().some((item) => item.name === 'arrow' && item.count > 0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** 近远武器切换带 2.5 格滞回;中间区间保持当前武器。 */
|
|
141
|
+
export function chooseHybridWeapon(
|
|
142
|
+
current: HybridWeapon,
|
|
143
|
+
distance: number,
|
|
144
|
+
rangedReady: boolean,
|
|
145
|
+
): HybridWeapon {
|
|
146
|
+
if (!rangedReady) return 'melee';
|
|
147
|
+
if (current === 'ranged') return distance <= HYBRID_MELEE_AT ? 'melee' : 'ranged';
|
|
148
|
+
return distance >= HYBRID_RANGED_AT ? 'ranged' : 'melee';
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function inKiteRange(distance: number): boolean {
|
|
152
|
+
return distance >= KITE_MIN_RANGE && distance <= KITE_MAX_RANGE;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function usableBow(item: ItemLike | null | undefined): boolean {
|
|
156
|
+
return item != null && item.name === 'bow' && item.count !== 0 && (readDurability(item)?.left ?? 0) > 0;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function targetCenter(target: RangedTarget): Point3 {
|
|
160
|
+
return {
|
|
161
|
+
x: target.position.x,
|
|
162
|
+
y: target.position.y + (target.height ?? 1.8) * 0.55,
|
|
163
|
+
z: target.position.z,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** 满弓箭的低弧瞄点;速度由位置采样得到,单位为格/tick。 */
|
|
168
|
+
export function lowArcAimPoint(
|
|
169
|
+
eye: Point3,
|
|
170
|
+
target: RangedTarget,
|
|
171
|
+
targetVelocity: Point3 = zeroPoint(),
|
|
172
|
+
shooterVelocity: Point3 = zeroPoint(),
|
|
173
|
+
): Vec3 | null {
|
|
174
|
+
const velocity = subtractPoint(targetVelocity, shooterVelocity);
|
|
175
|
+
const center = targetCenter(target);
|
|
176
|
+
let flightTicks = pointDistance(center, eye) / ARROW_SPEED_PER_TICK;
|
|
177
|
+
let aim = new Vec3(center.x, center.y, center.z);
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i < 3; i += 1) {
|
|
180
|
+
const predicted = {
|
|
181
|
+
x: center.x + velocity.x * flightTicks,
|
|
182
|
+
y: center.y + velocity.y * flightTicks,
|
|
183
|
+
z: center.z + velocity.z * flightTicks,
|
|
184
|
+
};
|
|
185
|
+
const horizontal = Math.hypot(predicted.x - eye.x, predicted.z - eye.z);
|
|
186
|
+
const speed2 = ARROW_SPEED_PER_TICK ** 2;
|
|
187
|
+
const dy = predicted.y - eye.y;
|
|
188
|
+
if (horizontal < 1e-6) {
|
|
189
|
+
const vertical = verticalFlight(dy);
|
|
190
|
+
if (vertical === null) return null;
|
|
191
|
+
flightTicks = vertical.ticks;
|
|
192
|
+
aim = new Vec3(predicted.x, eye.y + vertical.direction, predicted.z);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const disc = speed2 ** 2 - ARROW_GRAVITY_PER_TICK * (
|
|
196
|
+
ARROW_GRAVITY_PER_TICK * horizontal ** 2 + 2 * dy * speed2
|
|
197
|
+
);
|
|
198
|
+
if (disc < 0) return null;
|
|
199
|
+
const tan = (speed2 - Math.sqrt(disc)) / (ARROW_GRAVITY_PER_TICK * horizontal);
|
|
200
|
+
const cos = 1 / Math.sqrt(1 + tan ** 2);
|
|
201
|
+
flightTicks = horizontal / (ARROW_SPEED_PER_TICK * cos);
|
|
202
|
+
aim = new Vec3(predicted.x, eye.y + horizontal * tan, predicted.z);
|
|
203
|
+
}
|
|
204
|
+
return new Vec3(aim.x, aim.y, aim.z);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function verticalFlight(dy: number): { ticks: number; direction: 1 | -1 } | null {
|
|
208
|
+
if (Math.abs(dy) < 1e-6) return { ticks: 0, direction: 1 };
|
|
209
|
+
const direction = dy > 0 ? 1 : -1;
|
|
210
|
+
const discriminant = ARROW_SPEED_PER_TICK ** 2 - 2 * ARROW_GRAVITY_PER_TICK * dy;
|
|
211
|
+
if (discriminant < 0) return null;
|
|
212
|
+
const ticks = direction > 0
|
|
213
|
+
? (ARROW_SPEED_PER_TICK - Math.sqrt(discriminant)) / ARROW_GRAVITY_PER_TICK
|
|
214
|
+
: (-ARROW_SPEED_PER_TICK + Math.sqrt(discriminant)) / ARROW_GRAVITY_PER_TICK;
|
|
215
|
+
return ticks >= 0 ? { ticks, direction } : null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** 眼睛到目标半身高的方块射线;读不到世界射线时保守地视为遮挡。 */
|
|
219
|
+
export function hasRangedLos(bot: Bot, target: RangedTarget): boolean {
|
|
220
|
+
try {
|
|
221
|
+
const entity = bot.entity as Bot['entity'] & { eyeHeight?: number };
|
|
222
|
+
const eye = new Vec3(
|
|
223
|
+
entity.position.x,
|
|
224
|
+
entity.position.y + (entity.eyeHeight ?? 1.62),
|
|
225
|
+
entity.position.z,
|
|
226
|
+
);
|
|
227
|
+
const center = targetCenter(target);
|
|
228
|
+
const delta = new Vec3(center.x - eye.x, center.y - eye.y, center.z - eye.z);
|
|
229
|
+
const distance = delta.norm();
|
|
230
|
+
if (distance < 0.5) return true;
|
|
231
|
+
const hit = (bot.world as unknown as {
|
|
232
|
+
raycast(
|
|
233
|
+
from: Vec3,
|
|
234
|
+
direction: Vec3,
|
|
235
|
+
range: number,
|
|
236
|
+
matcher?: (block: unknown) => boolean,
|
|
237
|
+
): unknown;
|
|
238
|
+
}).raycast(
|
|
239
|
+
eye,
|
|
240
|
+
delta.scaled(1 / distance),
|
|
241
|
+
distance,
|
|
242
|
+
(block) => (block as { boundingBox?: string } | null)?.boundingBox === 'block',
|
|
243
|
+
);
|
|
244
|
+
return hit == null;
|
|
245
|
+
} catch {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
type BowBlockedReason = 'no_arrow' | 'no_los' | 'no_solution' | 'too_close' | 'aborted';
|
|
251
|
+
export type BowAbortCause =
|
|
252
|
+
| 'abort'
|
|
253
|
+
| 'death'
|
|
254
|
+
| 'lease'
|
|
255
|
+
| 'bow_lost'
|
|
256
|
+
| 'bot_lost'
|
|
257
|
+
| 'target_lost'
|
|
258
|
+
| 'equip'
|
|
259
|
+
| 'activate'
|
|
260
|
+
| 'release'
|
|
261
|
+
| 'cancel_slot'
|
|
262
|
+
| 'busy';
|
|
263
|
+
|
|
264
|
+
interface BowBlocked {
|
|
265
|
+
kind: 'blocked';
|
|
266
|
+
reason: BowBlockedReason;
|
|
267
|
+
cause?: BowAbortCause;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
interface BowReleased {
|
|
271
|
+
kind: 'released';
|
|
272
|
+
shotId: number;
|
|
273
|
+
targetId: number;
|
|
274
|
+
ownerToken: unknown;
|
|
275
|
+
at: number;
|
|
276
|
+
aim: Vec3;
|
|
277
|
+
hitWindow: { from: number; until: number };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
interface BowHit {
|
|
281
|
+
kind: 'hit';
|
|
282
|
+
shotId: number;
|
|
283
|
+
targetId: number;
|
|
284
|
+
ownerToken: unknown;
|
|
285
|
+
at: number;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export type BowShotResult = BowBlocked | BowReleased;
|
|
289
|
+
export type BowEvent = BowShotResult | BowHit;
|
|
290
|
+
|
|
291
|
+
interface BowControllerOptions {
|
|
292
|
+
getBot: () => Bot | null;
|
|
293
|
+
resolveTarget?: (id: number) => RangedTarget | null;
|
|
294
|
+
motion?: PositionVelocityTracker;
|
|
295
|
+
now?: () => number;
|
|
296
|
+
sleep?: (ms: number) => Promise<void>;
|
|
297
|
+
hasLos?: (bot: Bot, target: RangedTarget) => boolean;
|
|
298
|
+
leaseValid?: (token: unknown) => boolean;
|
|
299
|
+
emit?: (event: BowEvent) => void;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
interface Drawing {
|
|
303
|
+
bot: Bot;
|
|
304
|
+
targetId: number;
|
|
305
|
+
leaseToken: unknown;
|
|
306
|
+
bowType: number;
|
|
307
|
+
cancelSlot: number;
|
|
308
|
+
activated: boolean;
|
|
309
|
+
abortCause: BowAbortCause | null;
|
|
310
|
+
cancelWait: Promise<boolean> | null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
interface TrackedTarget {
|
|
314
|
+
target: RangedTarget;
|
|
315
|
+
velocityPerTick: Point3;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** 单次满弓控制器;身体租约、死亡和显式取消都在 release 事件之前复核。 */
|
|
319
|
+
export class BowController {
|
|
320
|
+
private readonly now: () => number;
|
|
321
|
+
private readonly sleep: (ms: number) => Promise<void>;
|
|
322
|
+
private readonly hasLos: (bot: Bot, target: RangedTarget) => boolean;
|
|
323
|
+
private readonly motion: PositionVelocityTracker;
|
|
324
|
+
private drawing: Drawing | null = null;
|
|
325
|
+
private pending: BowReleased[] = [];
|
|
326
|
+
private nextShotId = 1;
|
|
327
|
+
|
|
328
|
+
constructor(private readonly opts: BowControllerOptions) {
|
|
329
|
+
this.now = opts.now ?? Date.now;
|
|
330
|
+
this.sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
331
|
+
this.hasLos = opts.hasLos ?? hasRangedLos;
|
|
332
|
+
this.motion = opts.motion ?? new PositionVelocityTracker();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
get active(): boolean { return this.drawing !== null; }
|
|
336
|
+
|
|
337
|
+
abort(): void { this.cancel('abort'); }
|
|
338
|
+
|
|
339
|
+
onDeath(): void { this.cancel('death'); }
|
|
340
|
+
|
|
341
|
+
onBotLost(): void { this.cancel('bot_lost'); }
|
|
342
|
+
|
|
343
|
+
async shoot(target: RangedTarget, leaseToken?: unknown): Promise<BowShotResult> {
|
|
344
|
+
if (this.drawing) return this.blocked('aborted', 'busy');
|
|
345
|
+
const bot = this.opts.getBot();
|
|
346
|
+
if (!bot) return this.blocked('aborted', 'bot_lost');
|
|
347
|
+
if (bot.usingHeldItem) return this.blocked('aborted', 'busy');
|
|
348
|
+
const bow = bestRangedWeapon(bot);
|
|
349
|
+
if (!bow) return this.blocked('aborted', 'bow_lost');
|
|
350
|
+
if (!hasUsableArrows(bot)) return this.blocked('no_arrow');
|
|
351
|
+
if (rangedTargetDistance(bot, target) <= HYBRID_MELEE_AT) return this.blocked('too_close');
|
|
352
|
+
if (!this.hasLos(bot, target)) return this.blocked('no_los');
|
|
353
|
+
if (!this.validLease(leaseToken)) return this.blocked('aborted', 'lease');
|
|
354
|
+
|
|
355
|
+
const drawing: Drawing = {
|
|
356
|
+
bot,
|
|
357
|
+
targetId: target.id,
|
|
358
|
+
leaseToken,
|
|
359
|
+
bowType: bow.type,
|
|
360
|
+
cancelSlot: -1,
|
|
361
|
+
activated: false,
|
|
362
|
+
abortCause: null,
|
|
363
|
+
cancelWait: null,
|
|
364
|
+
};
|
|
365
|
+
this.drawing = drawing;
|
|
366
|
+
try {
|
|
367
|
+
try {
|
|
368
|
+
await bot.equip(bow, 'hand');
|
|
369
|
+
} catch {
|
|
370
|
+
return await this.finishBlocked(drawing, 'aborted', 'equip');
|
|
371
|
+
}
|
|
372
|
+
drawing.cancelSlot = findSafeCancelSlot(bot, drawing.bowType);
|
|
373
|
+
if (drawing.cancelSlot < 0) return await this.finishBlocked(drawing, 'aborted', 'cancel_slot');
|
|
374
|
+
let stopped = this.validateOwner(drawing);
|
|
375
|
+
if (stopped) return await this.finishBlocked(drawing, stopped.reason, stopped.cause);
|
|
376
|
+
let current = this.trackTarget(drawing, target);
|
|
377
|
+
if (!current) return await this.finishBlocked(drawing, 'aborted', 'target_lost');
|
|
378
|
+
stopped = this.validate(drawing, current.target);
|
|
379
|
+
if (stopped) return await this.finishBlocked(drawing, stopped.reason, stopped.cause);
|
|
380
|
+
|
|
381
|
+
let aim = this.aim(bot, current);
|
|
382
|
+
if (!aim) return await this.finishBlocked(drawing, 'no_solution');
|
|
383
|
+
await bot.lookAt(aim, true);
|
|
384
|
+
stopped = this.validateOwner(drawing);
|
|
385
|
+
if (stopped) return await this.finishBlocked(drawing, stopped.reason, stopped.cause);
|
|
386
|
+
current = this.trackTarget(drawing, target);
|
|
387
|
+
if (!current) return await this.finishBlocked(drawing, 'aborted', 'target_lost');
|
|
388
|
+
stopped = this.validate(drawing, current.target);
|
|
389
|
+
if (stopped) return await this.finishBlocked(drawing, stopped.reason, stopped.cause);
|
|
390
|
+
try {
|
|
391
|
+
bot.activateItem();
|
|
392
|
+
} catch {
|
|
393
|
+
return await this.finishBlocked(drawing, 'aborted', 'activate');
|
|
394
|
+
}
|
|
395
|
+
drawing.activated = true;
|
|
396
|
+
|
|
397
|
+
const startedAt = this.now();
|
|
398
|
+
while (this.now() - startedAt < BOW_FULL_DRAW_MS) {
|
|
399
|
+
await this.sleep(Math.min(AIM_STEP_MS, BOW_FULL_DRAW_MS - (this.now() - startedAt)));
|
|
400
|
+
stopped = this.validateOwner(drawing);
|
|
401
|
+
if (stopped) return await this.finishBlocked(drawing, stopped.reason, stopped.cause);
|
|
402
|
+
current = this.trackTarget(drawing, target);
|
|
403
|
+
if (!current) return await this.finishBlocked(drawing, 'aborted', 'target_lost');
|
|
404
|
+
stopped = this.validate(drawing, current.target);
|
|
405
|
+
if (stopped) return await this.finishBlocked(drawing, stopped.reason, stopped.cause);
|
|
406
|
+
aim = this.aim(bot, current);
|
|
407
|
+
if (!aim) return await this.finishBlocked(drawing, 'no_solution');
|
|
408
|
+
await bot.lookAt(aim, true);
|
|
409
|
+
stopped = this.validateOwner(drawing);
|
|
410
|
+
if (stopped) return await this.finishBlocked(drawing, stopped.reason, stopped.cause);
|
|
411
|
+
current = this.trackTarget(drawing, target);
|
|
412
|
+
if (!current) return await this.finishBlocked(drawing, 'aborted', 'target_lost');
|
|
413
|
+
stopped = this.validate(drawing, current.target);
|
|
414
|
+
if (stopped) return await this.finishBlocked(drawing, stopped.reason, stopped.cause);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!this.commitRelease(drawing)) return await this.finishBlocked(drawing, 'aborted', 'release');
|
|
418
|
+
const at = this.now();
|
|
419
|
+
const released: BowReleased = {
|
|
420
|
+
kind: 'released',
|
|
421
|
+
shotId: this.nextShotId,
|
|
422
|
+
targetId: target.id,
|
|
423
|
+
ownerToken: leaseToken,
|
|
424
|
+
at,
|
|
425
|
+
aim,
|
|
426
|
+
hitWindow: { from: at, until: at + BOW_HIT_WINDOW_MS },
|
|
427
|
+
};
|
|
428
|
+
this.nextShotId += 1;
|
|
429
|
+
this.pending.push(released);
|
|
430
|
+
this.pruneHits(at);
|
|
431
|
+
this.opts.emit?.(released);
|
|
432
|
+
return released;
|
|
433
|
+
} finally {
|
|
434
|
+
if (drawing.activated) {
|
|
435
|
+
const cause = drawing.abortCause
|
|
436
|
+
?? (this.opts.getBot() !== drawing.bot ? 'bot_lost' : 'abort');
|
|
437
|
+
await this.cancelDrawing(drawing, cause);
|
|
438
|
+
} else if (drawing.cancelWait) {
|
|
439
|
+
await drawing.cancelWait;
|
|
440
|
+
}
|
|
441
|
+
if (this.drawing === drawing) this.drawing = null;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** 实体受伤事件落在放箭窗口内时归到最早一支未结算的箭。 */
|
|
446
|
+
noteHit(targetId: number, at = this.now()): BowHit | null {
|
|
447
|
+
this.pruneHits(at);
|
|
448
|
+
const index = this.pending.findIndex((shot) => (
|
|
449
|
+
shot.targetId === targetId && at >= shot.hitWindow.from && at <= shot.hitWindow.until
|
|
450
|
+
));
|
|
451
|
+
if (index < 0) return null;
|
|
452
|
+
const [shot] = this.pending.splice(index, 1);
|
|
453
|
+
const hit: BowHit = {
|
|
454
|
+
kind: 'hit', shotId: shot.shotId, targetId, ownerToken: shot.ownerToken, at,
|
|
455
|
+
};
|
|
456
|
+
this.opts.emit?.(hit);
|
|
457
|
+
return hit;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
private aim(bot: Bot, tracked: TrackedTarget): Vec3 | null {
|
|
461
|
+
const entity = bot.entity as Bot['entity'] & { eyeHeight?: number };
|
|
462
|
+
return lowArcAimPoint({
|
|
463
|
+
x: entity.position.x,
|
|
464
|
+
y: entity.position.y + (entity.eyeHeight ?? 1.62),
|
|
465
|
+
z: entity.position.z,
|
|
466
|
+
}, tracked.target, tracked.velocityPerTick, entity.velocity ?? zeroPoint());
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private validate(drawing: Drawing, target: RangedTarget): BowBlocked | null {
|
|
470
|
+
const owner = this.validateOwner(drawing);
|
|
471
|
+
if (owner) return owner;
|
|
472
|
+
if (!usableBow(drawing.bot.heldItem as InventoryItem | null)) {
|
|
473
|
+
return { kind: 'blocked', reason: 'aborted', cause: 'bow_lost' };
|
|
474
|
+
}
|
|
475
|
+
if (!hasUsableArrows(drawing.bot)) return { kind: 'blocked', reason: 'no_arrow' };
|
|
476
|
+
if (rangedTargetDistance(drawing.bot, target) <= HYBRID_MELEE_AT) {
|
|
477
|
+
return { kind: 'blocked', reason: 'too_close' };
|
|
478
|
+
}
|
|
479
|
+
if (!this.hasLos(drawing.bot, target)) return { kind: 'blocked', reason: 'no_los' };
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private validateOwner(drawing: Drawing): BowBlocked | null {
|
|
484
|
+
if (drawing.abortCause) return { kind: 'blocked', reason: 'aborted', cause: drawing.abortCause };
|
|
485
|
+
if (this.opts.getBot() !== drawing.bot) return { kind: 'blocked', reason: 'aborted', cause: 'bot_lost' };
|
|
486
|
+
if (!this.validLease(drawing.leaseToken)) return { kind: 'blocked', reason: 'aborted', cause: 'lease' };
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private trackTarget(drawing: Drawing, fallback: RangedTarget): TrackedTarget | null {
|
|
491
|
+
const resolved = this.opts.resolveTarget
|
|
492
|
+
? this.opts.resolveTarget(drawing.targetId)
|
|
493
|
+
: fallback;
|
|
494
|
+
if (!resolved || resolved.id !== drawing.targetId) return null;
|
|
495
|
+
const at = this.now();
|
|
496
|
+
this.motion.observe(resolved.id, resolved.position, at);
|
|
497
|
+
const estimate = this.motion.estimate(resolved.id, at);
|
|
498
|
+
return {
|
|
499
|
+
target: {
|
|
500
|
+
id: resolved.id,
|
|
501
|
+
position: estimate?.position ?? copyPoint(resolved.position),
|
|
502
|
+
...(resolved.height === undefined ? {} : { height: resolved.height }),
|
|
503
|
+
...(resolved.width === undefined ? {} : { width: resolved.width }),
|
|
504
|
+
},
|
|
505
|
+
velocityPerTick: estimate?.velocityPerTick ?? zeroPoint(),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
private validLease(token: unknown): boolean {
|
|
510
|
+
return this.opts.leaseValid?.(token) ?? true;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
private cancel(cause: BowAbortCause): void {
|
|
514
|
+
const drawing = this.drawing;
|
|
515
|
+
if (!drawing || drawing.abortCause) return;
|
|
516
|
+
drawing.abortCause = cause;
|
|
517
|
+
void this.cancelDrawing(drawing, cause);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private commitRelease(drawing: Drawing): boolean {
|
|
521
|
+
if (!drawing.activated) return false;
|
|
522
|
+
try {
|
|
523
|
+
drawing.bot.deactivateItem();
|
|
524
|
+
} catch {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
drawing.activated = false;
|
|
528
|
+
return true;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private cancelDrawing(drawing: Drawing, cause: BowAbortCause): Promise<boolean> {
|
|
532
|
+
if (drawing.cancelWait) return drawing.cancelWait;
|
|
533
|
+
if (!drawing.activated) return Promise.resolve(true);
|
|
534
|
+
drawing.activated = false;
|
|
535
|
+
if (cause === 'death' || cause === 'bot_lost') return Promise.resolve(true);
|
|
536
|
+
const slot = findSafeCancelSlot(drawing.bot, drawing.bowType, drawing.cancelSlot);
|
|
537
|
+
if (slot < 0) return Promise.resolve(false);
|
|
538
|
+
drawing.cancelSlot = slot;
|
|
539
|
+
try {
|
|
540
|
+
drawing.bot.setQuickBarSlot(slot);
|
|
541
|
+
} catch {
|
|
542
|
+
return Promise.resolve(false);
|
|
543
|
+
}
|
|
544
|
+
drawing.cancelWait = this.sleep(AIM_STEP_MS).then(() => true);
|
|
545
|
+
return drawing.cancelWait;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
private async finishBlocked(
|
|
549
|
+
drawing: Drawing,
|
|
550
|
+
reason: BowBlockedReason,
|
|
551
|
+
cause?: BowAbortCause,
|
|
552
|
+
): Promise<BowBlocked> {
|
|
553
|
+
const cancelled = await this.cancelDrawing(drawing, cause ?? 'abort');
|
|
554
|
+
if (!cancelled) return this.blocked('aborted', 'cancel_slot');
|
|
555
|
+
return this.blocked(reason, cause);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private blocked(reason: BowBlockedReason, cause?: BowAbortCause): BowBlocked {
|
|
559
|
+
const event: BowBlocked = cause === undefined
|
|
560
|
+
? { kind: 'blocked', reason }
|
|
561
|
+
: { kind: 'blocked', reason, cause };
|
|
562
|
+
this.opts.emit?.(event);
|
|
563
|
+
return event;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private pruneHits(at: number): void {
|
|
567
|
+
this.pending = this.pending.filter((shot) => shot.hitWindow.until >= at);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/** 玩家眼睛到目标碰撞箱最近点的三维距离。 */
|
|
572
|
+
export function rangedTargetDistance(bot: Bot, target: RangedTarget): number {
|
|
573
|
+
const entity = bot.entity as Bot['entity'] & { eyeHeight?: number };
|
|
574
|
+
const eye = {
|
|
575
|
+
x: entity.position.x,
|
|
576
|
+
y: entity.position.y + (entity.eyeHeight ?? 1.62),
|
|
577
|
+
z: entity.position.z,
|
|
578
|
+
};
|
|
579
|
+
const halfWidth = (target.width ?? 0.6) / 2;
|
|
580
|
+
const nearest = {
|
|
581
|
+
x: clamp(eye.x, target.position.x - halfWidth, target.position.x + halfWidth),
|
|
582
|
+
y: clamp(eye.y, target.position.y, target.position.y + (target.height ?? 1.8)),
|
|
583
|
+
z: clamp(eye.z, target.position.z - halfWidth, target.position.z + halfWidth),
|
|
584
|
+
};
|
|
585
|
+
return pointDistance(eye, nearest);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function findSafeCancelSlot(bot: Bot, bowType: number, preferred = -1): number {
|
|
589
|
+
const start = bot.inventory.hotbarStart;
|
|
590
|
+
const safe = (slot: number): boolean => {
|
|
591
|
+
if (slot < 0 || slot > 8 || slot === bot.quickBarSlot) return false;
|
|
592
|
+
return bot.inventory.slots[start + slot]?.type !== bowType;
|
|
593
|
+
};
|
|
594
|
+
if (safe(preferred)) return preferred;
|
|
595
|
+
for (let slot = 0; slot < 9; slot += 1) {
|
|
596
|
+
if (safe(slot)) return slot;
|
|
597
|
+
}
|
|
598
|
+
return -1;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function clamp(value: number, lo: number, hi: number): number {
|
|
602
|
+
return Math.max(lo, Math.min(hi, value));
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function zeroPoint(): Point3 {
|
|
606
|
+
return { x: 0, y: 0, z: 0 };
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function copyPoint(point: Point3): Point3 {
|
|
610
|
+
return { x: point.x, y: point.y, z: point.z };
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function subtractPoint(a: Point3, b: Point3): Point3 {
|
|
614
|
+
return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function scalePoint(point: Point3, scale: number): Point3 {
|
|
618
|
+
return { x: point.x * scale, y: point.y * scale, z: point.z * scale };
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function mixPoint(a: Point3, b: Point3, weight: number): Point3 {
|
|
622
|
+
return {
|
|
623
|
+
x: a.x * (1 - weight) + b.x * weight,
|
|
624
|
+
y: a.y * (1 - weight) + b.y * weight,
|
|
625
|
+
z: a.z * (1 - weight) + b.z * weight,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function pointDistance(a: Point3, b: Point3): number {
|
|
630
|
+
return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function pointNorm(point: Point3): number {
|
|
634
|
+
return Math.hypot(point.x, point.y, point.z);
|
|
635
|
+
}
|