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,126 @@
|
|
|
1
|
+
import type { Bot } from 'mineflayer';
|
|
2
|
+
import { readDurability, type Durability, type ItemLike } from './item-facts.ts';
|
|
3
|
+
|
|
4
|
+
type ItemBreakSlot = 'mainhand' | 'offhand' | 'head' | 'chest' | 'legs' | 'feet';
|
|
5
|
+
|
|
6
|
+
export interface ItemBreakFact {
|
|
7
|
+
kind: 'item-break';
|
|
8
|
+
slot: ItemBreakSlot;
|
|
9
|
+
name: string;
|
|
10
|
+
/** 事件前最近一次装备快照中的剩余耐久。 */
|
|
11
|
+
durability: Durability;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface EntityStatusPacket {
|
|
15
|
+
entityId: number;
|
|
16
|
+
entityStatus: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ItemSnapshot = Omit<ItemBreakFact, 'kind'> & {
|
|
20
|
+
/** 槽位先清空时给同一批服务端损坏事件留出的短暂关联窗口。 */
|
|
21
|
+
validUntil?: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const CLEARED_SNAPSHOT_MS = 1_000;
|
|
25
|
+
|
|
26
|
+
const STATUS_SLOT = new Map<number, ItemBreakSlot>([
|
|
27
|
+
[47, 'mainhand'],
|
|
28
|
+
[48, 'offhand'],
|
|
29
|
+
[49, 'head'],
|
|
30
|
+
[50, 'chest'],
|
|
31
|
+
[51, 'legs'],
|
|
32
|
+
[52, 'feet'],
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const INVENTORY_SLOT = new Map<number, ItemBreakSlot>([
|
|
36
|
+
[45, 'offhand'],
|
|
37
|
+
[5, 'head'],
|
|
38
|
+
[6, 'chest'],
|
|
39
|
+
[7, 'legs'],
|
|
40
|
+
[8, 'feet'],
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
/** 从本玩家的 entity_status 47–52 读取物品损坏事件;槽位变化只更新关联用的装备快照。 */
|
|
44
|
+
export class ItemBreakDecoder {
|
|
45
|
+
private attached = false;
|
|
46
|
+
private readonly snapshots = new Map<ItemBreakSlot, ItemSnapshot>();
|
|
47
|
+
|
|
48
|
+
private readonly onHeldItemChanged = (item: ItemLike | null): void => {
|
|
49
|
+
this.remember('mainhand', item);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
private readonly onInventorySlot = (
|
|
53
|
+
index: number,
|
|
54
|
+
_oldItem: ItemLike | null,
|
|
55
|
+
newItem: ItemLike | null,
|
|
56
|
+
): void => {
|
|
57
|
+
const slot = INVENTORY_SLOT.get(index);
|
|
58
|
+
if (slot) this.remember(slot, newItem);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
private readonly onEntityStatus = (packet: EntityStatusPacket): void => {
|
|
62
|
+
if (packet.entityId !== this.bot.entity.id) return;
|
|
63
|
+
const slot = STATUS_SLOT.get(packet.entityStatus);
|
|
64
|
+
if (!slot) return;
|
|
65
|
+
const snapshot = this.snapshots.get(slot);
|
|
66
|
+
if (!snapshot) return;
|
|
67
|
+
this.snapshots.delete(slot);
|
|
68
|
+
if (snapshot.validUntil !== undefined && Date.now() > snapshot.validUntil) return;
|
|
69
|
+
this.onBreak({
|
|
70
|
+
kind: 'item-break',
|
|
71
|
+
slot: snapshot.slot,
|
|
72
|
+
name: snapshot.name,
|
|
73
|
+
durability: { ...snapshot.durability },
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
constructor(
|
|
78
|
+
private readonly bot: Bot,
|
|
79
|
+
private readonly onBreak: (fact: ItemBreakFact) => void,
|
|
80
|
+
) {}
|
|
81
|
+
|
|
82
|
+
get active(): boolean { return this.attached; }
|
|
83
|
+
|
|
84
|
+
attach(): void {
|
|
85
|
+
if (this.attached) return;
|
|
86
|
+
this.attached = true;
|
|
87
|
+
this.captureAll();
|
|
88
|
+
this.bot.on('heldItemChanged', this.onHeldItemChanged as never);
|
|
89
|
+
this.bot.inventory.on('updateSlot', this.onInventorySlot as never);
|
|
90
|
+
this.bot._client.on('entity_status', this.onEntityStatus as never);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
detach(): void {
|
|
94
|
+
if (!this.attached) return;
|
|
95
|
+
this.attached = false;
|
|
96
|
+
this.bot.off('heldItemChanged', this.onHeldItemChanged as never);
|
|
97
|
+
this.bot.inventory.off('updateSlot', this.onInventorySlot as never);
|
|
98
|
+
this.bot._client.off('entity_status', this.onEntityStatus as never);
|
|
99
|
+
this.snapshots.clear();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private captureAll(): void {
|
|
103
|
+
this.snapshots.clear();
|
|
104
|
+
this.remember('mainhand', this.bot.heldItem);
|
|
105
|
+
for (const [index, slot] of INVENTORY_SLOT) {
|
|
106
|
+
this.remember(slot, this.bot.inventory.slots[index] as ItemLike | null | undefined);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private remember(slot: ItemBreakSlot, item: ItemLike | null | undefined): void {
|
|
111
|
+
if (!item) {
|
|
112
|
+
const previous = this.snapshots.get(slot);
|
|
113
|
+
if (previous && previous.validUntil === undefined) {
|
|
114
|
+
this.snapshots.set(slot, { ...previous, validUntil: Date.now() + CLEARED_SNAPSHOT_MS });
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const durability = readDurability(item);
|
|
119
|
+
if (!durability) return;
|
|
120
|
+
this.snapshots.set(slot, {
|
|
121
|
+
slot,
|
|
122
|
+
name: item.name,
|
|
123
|
+
durability: { ...durability },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 读取物品的附魔与耐久,支持组件数据和 NBT。
|
|
3
|
+
* 当前 1.20.6 注册表的 maxDurability 数据不完整,耐久上限使用下方原版公式表。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** 附魔 ID 去掉 minecraft: 前缀;level 为数值等级。 */
|
|
7
|
+
export interface ItemEnchant {
|
|
8
|
+
name: string;
|
|
9
|
+
level: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** 剩余耐久与上限,单位是原版的「点」 */
|
|
13
|
+
export interface Durability {
|
|
14
|
+
left: number;
|
|
15
|
+
max: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ItemLike {
|
|
19
|
+
name: string;
|
|
20
|
+
count?: number;
|
|
21
|
+
componentMap?: Map<string, { type?: string; data?: unknown }>;
|
|
22
|
+
nbt?: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface EnchantRegistry {
|
|
26
|
+
enchantments?: Record<number, { name?: string } | undefined>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const TOOL_MAX: Record<string, number> = {
|
|
30
|
+
wooden: 59, stone: 131, iron: 250, golden: 32, diamond: 1561, netherite: 2031,
|
|
31
|
+
};
|
|
32
|
+
const TOOL_KIND = new Set(['pickaxe', 'axe', 'shovel', 'hoe', 'sword']);
|
|
33
|
+
|
|
34
|
+
/** 盔甲耐久 = 材质基数 × 部位系数(原版公式) */
|
|
35
|
+
const ARMOR_BASE: Record<string, number> = {
|
|
36
|
+
leather: 5, chainmail: 15, iron: 15, golden: 7, diamond: 33, netherite: 37,
|
|
37
|
+
};
|
|
38
|
+
const ARMOR_SLOT: Record<string, number> = {
|
|
39
|
+
helmet: 11, chestplate: 16, leggings: 15, boots: 13,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** 不按材质×部位算的那几件 */
|
|
43
|
+
const WHOLE_MAX: Record<string, number> = {
|
|
44
|
+
shield: 336, bow: 384, crossbow: 465, trident: 250, elytra: 432,
|
|
45
|
+
fishing_rod: 64, flint_and_steel: 64, shears: 238, brush: 64,
|
|
46
|
+
carrot_on_a_stick: 25, warped_fungus_on_a_stick: 100,
|
|
47
|
+
turtle_helmet: 275,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** 这件东西的耐久上限;不带耐久的物品返回 null */
|
|
51
|
+
export function maxDurabilityOf(id: string): number | null {
|
|
52
|
+
const name = id.replace(/^minecraft:/, '');
|
|
53
|
+
const whole = WHOLE_MAX[name];
|
|
54
|
+
if (whole !== undefined) return whole;
|
|
55
|
+
const cut = name.lastIndexOf('_');
|
|
56
|
+
if (cut < 0) return null;
|
|
57
|
+
const head = name.slice(0, cut);
|
|
58
|
+
const tail = name.slice(cut + 1);
|
|
59
|
+
if (TOOL_KIND.has(tail) && TOOL_MAX[head] !== undefined) return TOOL_MAX[head];
|
|
60
|
+
const slot = ARMOR_SLOT[tail];
|
|
61
|
+
const base = ARMOR_BASE[head];
|
|
62
|
+
if (slot !== undefined && base !== undefined) return base * slot;
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function componentData(item: ItemLike, type: string): unknown {
|
|
67
|
+
return item.componentMap?.get(type)?.data;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** NBT compound 里取一个键的裸值;层级对不上就返回 undefined */
|
|
71
|
+
function nbtValue(node: unknown, key: string): unknown {
|
|
72
|
+
const v = (node as { value?: Record<string, { value?: unknown }> } | undefined)?.value;
|
|
73
|
+
return v?.[key]?.value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** 已损耗的耐久点数;读不到返回 null(与「没损耗」是两回事) */
|
|
77
|
+
export function readDamage(item: ItemLike): number | null {
|
|
78
|
+
const comp = componentData(item, 'damage');
|
|
79
|
+
if (typeof comp === 'number') return comp;
|
|
80
|
+
const raw = nbtValue(item.nbt, 'Damage');
|
|
81
|
+
return typeof raw === 'number' ? raw : null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 剩余耐久;不带耐久的物品返回 null。缺失 damage 组件或 NBT Damage 字段按零损耗处理。 */
|
|
85
|
+
export function readDurability(item: ItemLike): Durability | null {
|
|
86
|
+
const max = maxDurabilityOf(item.name);
|
|
87
|
+
if (max === null) return null;
|
|
88
|
+
return { left: Math.max(0, max - (readDamage(item) ?? 0)), max };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** 从 stored_enchantments 和 enchantments 读取附魔。 */
|
|
92
|
+
export function readEnchants(item: ItemLike, registry?: EnchantRegistry | null): ItemEnchant[] {
|
|
93
|
+
const out: ItemEnchant[] = [];
|
|
94
|
+
for (const type of ['enchantments', 'stored_enchantments']) {
|
|
95
|
+
const data = componentData(item, type) as { enchantments?: Array<{ id?: unknown; level?: unknown }> } | undefined;
|
|
96
|
+
for (const e of data?.enchantments ?? []) {
|
|
97
|
+
const name = enchantName(e.id, registry);
|
|
98
|
+
if (name !== null && typeof e.level === 'number') out.push({ name, level: e.level });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (out.length > 0) return out;
|
|
102
|
+
for (const key of ['Enchantments', 'StoredEnchantments']) {
|
|
103
|
+
const list = nbtValue(item.nbt, key) as { value?: Array<Record<string, { value?: unknown }>> } | undefined;
|
|
104
|
+
for (const e of list?.value ?? []) {
|
|
105
|
+
const name = enchantName(e.id?.value, registry);
|
|
106
|
+
const lvl = e.lvl?.value;
|
|
107
|
+
if (name !== null && typeof lvl === 'number') out.push({ name, level: lvl });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** 附魔 id 可能是字符串(NBT 时代)或注册表序号(组件时代);查不出名字就不报这一条 */
|
|
114
|
+
function enchantName(id: unknown, registry?: EnchantRegistry | null): string | null {
|
|
115
|
+
if (typeof id === 'string') return id.replace(/^minecraft:/, '');
|
|
116
|
+
if (typeof id !== 'number') return null;
|
|
117
|
+
return registry?.enchantments?.[id]?.name ?? null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 读取药水内容的注册表序号;缺失时返回 null。
|
|
122
|
+
* 当前 minecraft-data 不含药水注册表,此处不翻译序号。
|
|
123
|
+
*/
|
|
124
|
+
export function readPotionId(item: ItemLike): number | null {
|
|
125
|
+
const data = componentData(item, 'potion_contents') as { potionId?: unknown } | undefined;
|
|
126
|
+
return typeof data?.potionId === 'number' ? data.potionId : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** 一样能喝下去、但不给饱食度的东西 */
|
|
130
|
+
interface Drinkable {
|
|
131
|
+
/** 回执里的量词说法:「喝了一桶奶」 */
|
|
132
|
+
label: string;
|
|
133
|
+
/** 喝完剩在包里的空容器物品 id */
|
|
134
|
+
empty: string;
|
|
135
|
+
/** 消耗后的效果说明。 */
|
|
136
|
+
effect: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** 补充 foods 表未包含、但 Mineflayer consume() 支持的无饱食度饮品。 */
|
|
140
|
+
export const DRINKABLES: Readonly<Record<string, Drinkable>> = {
|
|
141
|
+
milk_bucket: {
|
|
142
|
+
label: '一桶奶',
|
|
143
|
+
empty: 'bucket',
|
|
144
|
+
effect: '身上的状态效果被清光(增益也一起清掉)',
|
|
145
|
+
},
|
|
146
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 按物品的显示标签、英文附魔 ID 和数值等级做子串匹配。
|
|
3
|
+
* 同一挑选词可匹配多件物品,操作数量由 count 指定;未匹配时返回候选清单。
|
|
4
|
+
*/
|
|
5
|
+
import { roman, zhEnchant, zhName } from './names.ts';
|
|
6
|
+
import { readEnchants, type EnchantRegistry, type ItemEnchant, type ItemLike } from './item-facts.ts';
|
|
7
|
+
import { enchantSuffix } from './terrain.ts';
|
|
8
|
+
|
|
9
|
+
export interface PickTarget {
|
|
10
|
+
name: string;
|
|
11
|
+
enchantments?: readonly ItemEnchant[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function pickLabel(it: PickTarget): string {
|
|
15
|
+
return `${zhName(it.name)}${enchantSuffix(it.enchantments)}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function pickTargetOf(item: ItemLike, registry?: EnchantRegistry | null): PickTarget {
|
|
19
|
+
return { name: item.name, enchantments: readEnchants(item, registry) };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function normalize(s: string): string {
|
|
23
|
+
return s.toLowerCase().replace(/\s+/g, '');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function facets(it: PickTarget): string[] {
|
|
27
|
+
const out = [zhName(it.name), it.name, pickLabel(it)];
|
|
28
|
+
for (const e of it.enchantments ?? []) {
|
|
29
|
+
const zh = zhEnchant(e.name);
|
|
30
|
+
out.push(zh, `${zh}${roman(e.level)}`, `${zh}${e.level}`, e.name, `${e.name}${e.level}`);
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 空挑选词不筛选。 */
|
|
36
|
+
export function matchesPick(pick: string | undefined, it: PickTarget): boolean {
|
|
37
|
+
if (!pick) return true;
|
|
38
|
+
const q = normalize(pick);
|
|
39
|
+
return q.length > 0 && facets(it).some((f) => normalize(f).includes(q));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function itemMatchesPick(
|
|
43
|
+
pick: string | undefined,
|
|
44
|
+
item: ItemLike,
|
|
45
|
+
registry?: EnchantRegistry | null,
|
|
46
|
+
): boolean {
|
|
47
|
+
return pick ? matchesPick(pick, pickTargetOf(item, registry)) : true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 无附魔时显示“没有附魔”。 */
|
|
51
|
+
function pickFacts(it: PickTarget): string {
|
|
52
|
+
return enchantSuffix(it.enchantments).replace(/[()]/g, '') || '没有附魔';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function pickMissText(
|
|
56
|
+
where: string,
|
|
57
|
+
id: string,
|
|
58
|
+
pick: string,
|
|
59
|
+
sameId: readonly PickTarget[],
|
|
60
|
+
): string {
|
|
61
|
+
return `${where}有 ${sameId.length} 件${zhName(id)},没有一件带「${pick}」:`
|
|
62
|
+
+ `${sameId.map(pickFacts).join(' / ')}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function pickedText(picked: readonly PickTarget[]): string {
|
|
66
|
+
return [...new Set(picked.map(pickLabel))].join('、');
|
|
67
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 读取存档 level.dat 的 NBT 元数据;同时支持 gzip 压缩和未压缩内容。
|
|
3
|
+
* 解析失败返回 null。存档元数据与下次启动的 server.properties 配置分别显示。
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
6
|
+
import { gunzipSync } from 'node:zlib';
|
|
7
|
+
|
|
8
|
+
/** 存档元数据;缺失字段为 null。 */
|
|
9
|
+
export interface LevelDatInfo {
|
|
10
|
+
/** 存档自己记的显示名,可能与目录名不同 */
|
|
11
|
+
levelName: string | null;
|
|
12
|
+
/** 上次游玩时刻,Unix 毫秒。 */
|
|
13
|
+
lastPlayed: number | null;
|
|
14
|
+
/** 世界种子;1.16+ 在 WorldGenSettings 下 */
|
|
15
|
+
seed: string | null;
|
|
16
|
+
/** 0=生存 1=创造 2=冒险 3=旁观 */
|
|
17
|
+
gameType: number | null;
|
|
18
|
+
/** 0=和平 1=简单 2=普通 3=困难 */
|
|
19
|
+
difficulty: number | null;
|
|
20
|
+
hardcore: boolean | null;
|
|
21
|
+
/** 存档 Version.Name 字段。 */
|
|
22
|
+
version: string | null;
|
|
23
|
+
/** 主世界生成器:flat / amplified / large_biomes / normal;认不出为 null */
|
|
24
|
+
generator: string | null;
|
|
25
|
+
/** 世界内时间(tick);/ 24000 即天数 */
|
|
26
|
+
dayTime: number | null;
|
|
27
|
+
/**
|
|
28
|
+
* Data/GameRules 的原始字符串值;null 表示读不到,空对象表示读到但没有规则。
|
|
29
|
+
* Paper/Bukkit 各维度单独存储,必须读取对应维度的 level.dat。
|
|
30
|
+
*/
|
|
31
|
+
gameRules: Record<string, string> | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type NbtValue = number | bigint | string | NbtValue[] | { [key: string]: NbtValue };
|
|
35
|
+
|
|
36
|
+
const TAG_END = 0;
|
|
37
|
+
const TAG_BYTE = 1;
|
|
38
|
+
const TAG_SHORT = 2;
|
|
39
|
+
const TAG_INT = 3;
|
|
40
|
+
const TAG_LONG = 4;
|
|
41
|
+
const TAG_FLOAT = 5;
|
|
42
|
+
const TAG_DOUBLE = 6;
|
|
43
|
+
const TAG_BYTE_ARRAY = 7;
|
|
44
|
+
const TAG_STRING = 8;
|
|
45
|
+
const TAG_LIST = 9;
|
|
46
|
+
const TAG_COMPOUND = 10;
|
|
47
|
+
const TAG_INT_ARRAY = 11;
|
|
48
|
+
const TAG_LONG_ARRAY = 12;
|
|
49
|
+
|
|
50
|
+
class Cursor {
|
|
51
|
+
private i = 0;
|
|
52
|
+
constructor(private readonly buf: Buffer) {}
|
|
53
|
+
|
|
54
|
+
u8(): number {
|
|
55
|
+
if (this.i >= this.buf.length) throw new Error('nbt eof');
|
|
56
|
+
return this.buf[this.i++];
|
|
57
|
+
}
|
|
58
|
+
i16(): number { const v = this.buf.readInt16BE(this.i); this.i += 2; return v; }
|
|
59
|
+
i32(): number { const v = this.buf.readInt32BE(this.i); this.i += 4; return v; }
|
|
60
|
+
i64(): bigint { const v = this.buf.readBigInt64BE(this.i); this.i += 8; return v; }
|
|
61
|
+
f32(): number { const v = this.buf.readFloatBE(this.i); this.i += 4; return v; }
|
|
62
|
+
f64(): number { const v = this.buf.readDoubleBE(this.i); this.i += 8; return v; }
|
|
63
|
+
skip(n: number): void { this.i += n; }
|
|
64
|
+
str(): string {
|
|
65
|
+
const n = this.buf.readUInt16BE(this.i);
|
|
66
|
+
this.i += 2;
|
|
67
|
+
const s = this.buf.subarray(this.i, this.i + n).toString('utf8');
|
|
68
|
+
this.i += n;
|
|
69
|
+
return s;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readPayload(c: Cursor, type: number): NbtValue {
|
|
74
|
+
switch (type) {
|
|
75
|
+
case TAG_BYTE: return c.u8();
|
|
76
|
+
case TAG_SHORT: return c.i16();
|
|
77
|
+
case TAG_INT: return c.i32();
|
|
78
|
+
case TAG_LONG: return c.i64();
|
|
79
|
+
case TAG_FLOAT: return c.f32();
|
|
80
|
+
case TAG_DOUBLE: return c.f64();
|
|
81
|
+
case TAG_BYTE_ARRAY: { const n = c.i32(); c.skip(n); return []; }
|
|
82
|
+
case TAG_STRING: return c.str();
|
|
83
|
+
case TAG_LIST: {
|
|
84
|
+
const inner = c.u8();
|
|
85
|
+
const n = c.i32();
|
|
86
|
+
const out: NbtValue[] = [];
|
|
87
|
+
for (let i = 0; i < n; i++) out.push(inner === TAG_END ? 0 : readPayload(c, inner));
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
case TAG_COMPOUND: return readCompound(c);
|
|
91
|
+
case TAG_INT_ARRAY: { const n = c.i32(); c.skip(n * 4); return []; }
|
|
92
|
+
case TAG_LONG_ARRAY: { const n = c.i32(); c.skip(n * 8); return []; }
|
|
93
|
+
default: throw new Error(`nbt type ${type}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readCompound(c: Cursor): Record<string, NbtValue> {
|
|
98
|
+
const out: Record<string, NbtValue> = {};
|
|
99
|
+
for (;;) {
|
|
100
|
+
const type = c.u8();
|
|
101
|
+
if (type === TAG_END) return out;
|
|
102
|
+
const name = c.str();
|
|
103
|
+
out[name] = readPayload(c, type);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function obj(v: NbtValue | undefined): Record<string, NbtValue> | null {
|
|
108
|
+
return v !== undefined && typeof v === 'object' && !Array.isArray(v) ? v as Record<string, NbtValue> : null;
|
|
109
|
+
}
|
|
110
|
+
function num(v: NbtValue | undefined): number | null {
|
|
111
|
+
if (typeof v === 'number' && Number.isFinite(v)) return v;
|
|
112
|
+
if (typeof v === 'bigint') return Number(v);
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
function str(v: NbtValue | undefined): string | null {
|
|
116
|
+
return typeof v === 'string' ? v : null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 生成器 → 界面词表用的短名。1.16+ 是 dimensions 下每个维度各自的 generator。 */
|
|
120
|
+
function generatorOf(gen: Record<string, NbtValue> | null): string | null {
|
|
121
|
+
const overworld = obj(obj(gen?.dimensions)?.['minecraft:overworld']);
|
|
122
|
+
const generator = obj(overworld?.generator);
|
|
123
|
+
const type = str(generator?.type);
|
|
124
|
+
if (type === null) return null;
|
|
125
|
+
if (type.endsWith('flat')) return 'flat';
|
|
126
|
+
const settings = str(generator?.settings);
|
|
127
|
+
if (settings === null) return 'normal';
|
|
128
|
+
return settings.replace(/^minecraft:/, '');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** 只保留 GameRules 中的字符串值。 */
|
|
132
|
+
function gameRulesOf(rules: Record<string, NbtValue> | null): Record<string, string> | null {
|
|
133
|
+
if (!rules) return null;
|
|
134
|
+
const out: Record<string, string> = {};
|
|
135
|
+
for (const [key, value] of Object.entries(rules)) {
|
|
136
|
+
if (typeof value === 'string') out[key] = value;
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function parseLevelDat(buf: Buffer): LevelDatInfo | null {
|
|
142
|
+
try {
|
|
143
|
+
const raw = buf.length >= 2 && buf[0] === 0x1f && buf[1] === 0x8b ? gunzipSync(buf) : buf;
|
|
144
|
+
const c = new Cursor(raw);
|
|
145
|
+
if (c.u8() !== TAG_COMPOUND) return null;
|
|
146
|
+
c.str();
|
|
147
|
+
const data = obj(readCompound(c).Data);
|
|
148
|
+
if (!data) return null;
|
|
149
|
+
const gen = obj(data.WorldGenSettings);
|
|
150
|
+
const seed = gen?.seed;
|
|
151
|
+
const hardcore = num(data.hardcore);
|
|
152
|
+
return {
|
|
153
|
+
levelName: str(data.LevelName),
|
|
154
|
+
lastPlayed: num(data.LastPlayed),
|
|
155
|
+
seed: typeof seed === 'bigint' ? seed.toString() : num(seed) !== null ? String(num(seed)) : null,
|
|
156
|
+
gameType: num(data.GameType),
|
|
157
|
+
difficulty: num(data.Difficulty),
|
|
158
|
+
hardcore: hardcore === null ? null : hardcore !== 0,
|
|
159
|
+
version: str(obj(data.Version)?.Name),
|
|
160
|
+
generator: generatorOf(gen),
|
|
161
|
+
dayTime: num(data.DayTime),
|
|
162
|
+
gameRules: gameRulesOf(obj(data.GameRules)),
|
|
163
|
+
};
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function readLevelDat(path: string): LevelDatInfo | null {
|
|
170
|
+
if (!existsSync(path)) return null;
|
|
171
|
+
try {
|
|
172
|
+
return parseLevelDat(readFileSync(path));
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minecraft 日志保存到 data/runs/<run>/log.jsonl,area 为 worlds.minecraft。
|
|
3
|
+
* 引擎子进程的日志上下文补充轮次和工具调用 ID;环形缓冲供控制台按 seq 增量读取。
|
|
4
|
+
*/
|
|
5
|
+
import type { Logger, LogLevel } from '../../core/types.ts';
|
|
6
|
+
import { nowIso, nullLogger } from '../../core/util.ts';
|
|
7
|
+
|
|
8
|
+
type MinecraftLane =
|
|
9
|
+
| 'tool'
|
|
10
|
+
| 'event'
|
|
11
|
+
| 'task'
|
|
12
|
+
| 'skill'
|
|
13
|
+
| 'craft'
|
|
14
|
+
| 'inventory'
|
|
15
|
+
| 'path'
|
|
16
|
+
| 'body'
|
|
17
|
+
| 'reflex'
|
|
18
|
+
| 'combat'
|
|
19
|
+
| 'link'
|
|
20
|
+
| 'world';
|
|
21
|
+
|
|
22
|
+
export const LANE_ZH: Record<MinecraftLane, string> = {
|
|
23
|
+
tool: '工具',
|
|
24
|
+
event: '投递',
|
|
25
|
+
task: '任务',
|
|
26
|
+
skill: '技能',
|
|
27
|
+
craft: '合成',
|
|
28
|
+
inventory: '物品',
|
|
29
|
+
path: '寻路',
|
|
30
|
+
body: '身体',
|
|
31
|
+
reflex: '反射',
|
|
32
|
+
combat: '战斗',
|
|
33
|
+
link: '连接',
|
|
34
|
+
world: '世界',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const LANE_LEVEL: Record<MinecraftLane, LogLevel> = {
|
|
38
|
+
tool: 'info',
|
|
39
|
+
event: 'info',
|
|
40
|
+
task: 'info',
|
|
41
|
+
skill: 'debug',
|
|
42
|
+
craft: 'debug',
|
|
43
|
+
inventory: 'debug',
|
|
44
|
+
path: 'debug',
|
|
45
|
+
body: 'trace',
|
|
46
|
+
reflex: 'info',
|
|
47
|
+
combat: 'info',
|
|
48
|
+
link: 'info',
|
|
49
|
+
world: 'info',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const EVENT_LEVEL: Record<string, LogLevel> = {
|
|
53
|
+
'craft/slot-in': 'trace',
|
|
54
|
+
'craft/click-out': 'trace',
|
|
55
|
+
'craft/items-in': 'trace',
|
|
56
|
+
'craft/place-out': 'trace',
|
|
57
|
+
'craft/grid-clear': 'trace',
|
|
58
|
+
'craft/result-slot': 'trace',
|
|
59
|
+
'craft/stateid-rewrite': 'trace',
|
|
60
|
+
'path/update': 'trace',
|
|
61
|
+
'reflex/drown-submerged': 'trace',
|
|
62
|
+
'reflex/drown-breath': 'trace',
|
|
63
|
+
'skill/dig-ground-wait': 'trace',
|
|
64
|
+
'link/spectate': 'debug',
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function laneLevel(lane: MinecraftLane, event: string): LogLevel {
|
|
68
|
+
return EVENT_LEVEL[`${lane}/${event}`] ?? LANE_LEVEL[lane];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface MinecraftLogInput {
|
|
72
|
+
lane: MinecraftLane;
|
|
73
|
+
event: string;
|
|
74
|
+
msg: string;
|
|
75
|
+
/** 关联的任务号;没有归属的不填 */
|
|
76
|
+
taskId?: number;
|
|
77
|
+
durMs?: number;
|
|
78
|
+
data?: Record<string, unknown>;
|
|
79
|
+
/** 未指定时按日志类别和事件类型选择级别。 */
|
|
80
|
+
level?: LogLevel;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface MinecraftLogEntry extends MinecraftLogInput {
|
|
84
|
+
seq: number;
|
|
85
|
+
ts: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
interface MinecraftLogOptions {
|
|
89
|
+
/** World 的 logger(区域 worlds.minecraft);不给就只进环形缓冲 */
|
|
90
|
+
log?: Logger;
|
|
91
|
+
timezone?: string;
|
|
92
|
+
/** 环形缓冲容量(控制台面板读它) */
|
|
93
|
+
ring?: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export class MinecraftLog {
|
|
97
|
+
private readonly log: Logger;
|
|
98
|
+
private readonly timezone: string;
|
|
99
|
+
private readonly capacity: number;
|
|
100
|
+
private ring: MinecraftLogEntry[] = [];
|
|
101
|
+
private seq = 0;
|
|
102
|
+
|
|
103
|
+
constructor(opts: MinecraftLogOptions = {}) {
|
|
104
|
+
this.log = opts.log ?? nullLogger();
|
|
105
|
+
this.timezone = opts.timezone ?? 'Asia/Shanghai';
|
|
106
|
+
this.capacity = opts.ring ?? 2_000;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
write(input: MinecraftLogInput): void {
|
|
110
|
+
const entry: MinecraftLogEntry = { seq: ++this.seq, ts: nowIso(this.timezone), ...input };
|
|
111
|
+
this.ring.push(entry);
|
|
112
|
+
if (this.ring.length > this.capacity) this.ring.splice(0, this.ring.length - this.capacity);
|
|
113
|
+
this.log.child(input.lane).emit(input.level ?? laneLevel(input.lane, input.event), input.msg, {
|
|
114
|
+
event: input.event,
|
|
115
|
+
...(input.taskId !== undefined ? { task: input.taskId } : {}),
|
|
116
|
+
...(input.durMs !== undefined ? { durMs: input.durMs } : {}),
|
|
117
|
+
...(input.data !== undefined ? { data: input.data } : {}),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** 面板增量轮询:seq 大于 after 的那些 */
|
|
122
|
+
after(seq: number): MinecraftLogEntry[] {
|
|
123
|
+
return this.ring.filter((e) => e.seq > seq);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 清空缓冲;序号继续往下走,面板的 after 游标不会因清空而回头 */
|
|
127
|
+
clear(): string {
|
|
128
|
+
const had = this.ring.length;
|
|
129
|
+
this.ring = [];
|
|
130
|
+
return `面板缓冲已清空(${had} 条);落盘的运行日志不受影响`;
|
|
131
|
+
}
|
|
132
|
+
}
|