mioku-plugin-mc 2.1.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.md +157 -3
- package/index.ts +61 -9
- package/package.json +8 -3
- package/play/actions/registry.ts +196 -0
- package/play/ai/context-builder.ts +29 -0
- package/play/ai/debug-log.ts +135 -0
- package/play/ai/main-loop.ts +306 -0
- package/play/ai/main-tools.ts +230 -0
- package/play/ai/prompt.ts +207 -0
- package/play/ai/work-subroutine.ts +487 -0
- package/play/behavior/base-behavior.ts +82 -0
- package/play/behavior/catalog/approach-player.ts +65 -0
- package/play/behavior/catalog/defend.ts +68 -0
- package/play/behavior/catalog/explore.ts +72 -0
- package/play/behavior/catalog/factory.ts +26 -0
- package/play/behavior/catalog/farm-mobs.ts +43 -0
- package/play/behavior/catalog/follow.ts +80 -0
- package/play/behavior/catalog/gather.ts +135 -0
- package/play/behavior/catalog/idle.ts +130 -0
- package/play/behavior/catalog/seek-shelter.ts +85 -0
- package/play/behavior/engine.ts +243 -0
- package/play/behavior/survival/auto-eat.ts +46 -0
- package/play/behavior/survival/escape-lava.ts +44 -0
- package/play/behavior/survival/escape-water.ts +35 -0
- package/play/behavior/survival/flee-creeper.ts +76 -0
- package/play/behavior/survival/mlg-fall.ts +56 -0
- package/play/bot/bot-controller.ts +276 -0
- package/play/bot/play-bus.ts +48 -0
- package/play/combat/combat.ts +225 -0
- package/play/combat/index.ts +1 -0
- package/play/config.ts +39 -0
- package/play/context.ts +26 -0
- package/play/debug/README.md +74 -0
- package/play/debug/commands.ts +289 -0
- package/play/index.ts +247 -0
- package/play/mineflayer-shims.d.ts +22 -0
- package/play/missions/bundles/approach-player.ts +26 -0
- package/play/missions/bundles/explore.ts +24 -0
- package/play/missions/bundles/farm-mobs.ts +24 -0
- package/play/missions/bundles/follow-player.ts +40 -0
- package/play/missions/bundles/gather-resource.ts +37 -0
- package/play/missions/bundles/idle-wander.ts +24 -0
- package/play/missions/bundles/seek-shelter.ts +18 -0
- package/play/missions/mission-controller.ts +296 -0
- package/play/missions/registry.ts +107 -0
- package/play/path-engine/astar.ts +514 -0
- package/play/path-engine/goals.ts +84 -0
- package/play/path-engine/index.ts +4 -0
- package/play/path-engine/movements.ts +67 -0
- package/play/path-engine/path-engine.ts +540 -0
- package/play/session.ts +486 -0
- package/play/state/cooldowns.ts +40 -0
- package/play/state/event-journal.ts +72 -0
- package/play/state/memory-bus.ts +115 -0
- package/play/state/mode.ts +57 -0
- package/play/state/sensors/entity-scanner.ts +275 -0
- package/play/state/snapshot.ts +360 -0
- package/play/types.ts +284 -0
- package/play/util/async.ts +11 -0
- package/play/util/endpoint.ts +38 -0
- package/play/util/entities.ts +135 -0
- package/play/util/inventory.ts +75 -0
- package/skills/mc.ts +58 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Vec3 } from "vec3";
|
|
2
|
+
import { Behavior, type BehaviorContext } from "../base-behavior";
|
|
3
|
+
import { GoalNear } from "../../path-engine";
|
|
4
|
+
|
|
5
|
+
export class SeekShelterBehavior extends Behavior {
|
|
6
|
+
readonly name = "seek_shelter";
|
|
7
|
+
readonly category = "movement" as const;
|
|
8
|
+
private moving = false;
|
|
9
|
+
private attempted = false;
|
|
10
|
+
|
|
11
|
+
onTick(ctx: BehaviorContext): void {
|
|
12
|
+
if (this.moving || this.attempted) return;
|
|
13
|
+
this.attempted = true;
|
|
14
|
+
const target = findShelteredPosition(ctx.bot);
|
|
15
|
+
if (!target) {
|
|
16
|
+
this.mission?.block("resource_not_found", "附近没有找到可到达的遮蔽位置");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const engine = ctx.bot.pathEngine;
|
|
20
|
+
if (!engine) {
|
|
21
|
+
this.mission?.fail("path_unreachable", "寻路引擎不可用");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
this.moving = true;
|
|
25
|
+
engine
|
|
26
|
+
.goto(new GoalNear(target.x, target.y, target.z, 1))
|
|
27
|
+
.then(() => this.mission?.succeed("已到达遮蔽位置", target))
|
|
28
|
+
.catch((error: unknown) =>
|
|
29
|
+
this.mission?.fail("path_unreachable", String(error), target),
|
|
30
|
+
)
|
|
31
|
+
.finally(() => {
|
|
32
|
+
this.moving = false;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onStop(ctx: BehaviorContext): void {
|
|
37
|
+
try {
|
|
38
|
+
ctx.bot.pathEngine?.stop();
|
|
39
|
+
} catch {
|
|
40
|
+
// ignore
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function findShelteredPosition(
|
|
46
|
+
bot: any,
|
|
47
|
+
): { x: number; y: number; z: number } | null {
|
|
48
|
+
const position = bot.entity?.position;
|
|
49
|
+
if (!position) return null;
|
|
50
|
+
const y = Math.floor(position.y);
|
|
51
|
+
for (let radius = 0; radius <= 12; radius++) {
|
|
52
|
+
for (let dx = -radius; dx <= radius; dx++) {
|
|
53
|
+
for (let dz = -radius; dz <= radius; dz++) {
|
|
54
|
+
if (radius > 0 && Math.abs(dx) !== radius && Math.abs(dz) !== radius)
|
|
55
|
+
continue;
|
|
56
|
+
const x = Math.floor(position.x) + dx;
|
|
57
|
+
const z = Math.floor(position.z) + dz;
|
|
58
|
+
const floor = bot.blockAt(new Vec3(x, y - 1, z), false);
|
|
59
|
+
const feet = bot.blockAt(new Vec3(x, y, z), false);
|
|
60
|
+
const head = bot.blockAt(new Vec3(x, y + 1, z), false);
|
|
61
|
+
const roof = bot.blockAt(new Vec3(x, y + 2, z), false);
|
|
62
|
+
if (isSolid(floor) && isAir(feet) && isAir(head) && isSolid(roof))
|
|
63
|
+
return { x, y, z };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isAir(block: any): boolean {
|
|
71
|
+
return (
|
|
72
|
+
!block ||
|
|
73
|
+
block.boundingBox === "empty" ||
|
|
74
|
+
block.name === "air" ||
|
|
75
|
+
block.name === "cave_air"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isSolid(block: any): boolean {
|
|
80
|
+
return (
|
|
81
|
+
!!block &&
|
|
82
|
+
block.boundingBox !== "empty" &&
|
|
83
|
+
!/^(water|lava|fire)$/.test(String(block.name))
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import type { Behavior, BehaviorContext } from "./base-behavior";
|
|
2
|
+
import type { MovementInit } from "../types";
|
|
3
|
+
import { createBehavior } from "./catalog/factory";
|
|
4
|
+
import type { BehaviorMode, ModeState } from "../state/mode";
|
|
5
|
+
|
|
6
|
+
export interface BehaviorEngineOptions {
|
|
7
|
+
ctxBuilder: () => BehaviorContext | null;
|
|
8
|
+
tickInterval: number;
|
|
9
|
+
survival: Behavior[];
|
|
10
|
+
overlays: Behavior[];
|
|
11
|
+
initialMovement?: MovementInit;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface BehaviorStateInfo {
|
|
15
|
+
name: string;
|
|
16
|
+
category: string;
|
|
17
|
+
priority: number;
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
active: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class BehaviorEngine {
|
|
23
|
+
private readonly survival: Behavior[];
|
|
24
|
+
private readonly overlays: Behavior[];
|
|
25
|
+
private movement: Behavior | null = null;
|
|
26
|
+
private missionId: string | null = null;
|
|
27
|
+
private missionBehaviors: Behavior[] = [];
|
|
28
|
+
private timer?: NodeJS.Timeout;
|
|
29
|
+
private current: Behavior | null = null;
|
|
30
|
+
private lastMode: BehaviorMode = "IDLE";
|
|
31
|
+
private readonly lastSwitch: ModeState["lastSwitch"] = {
|
|
32
|
+
from: null,
|
|
33
|
+
to: "IDLE",
|
|
34
|
+
reason: "init",
|
|
35
|
+
at: Date.now(),
|
|
36
|
+
};
|
|
37
|
+
private readonly ctxBuilder: () => BehaviorContext | null;
|
|
38
|
+
private readonly tickInterval: number;
|
|
39
|
+
|
|
40
|
+
constructor(opts: BehaviorEngineOptions) {
|
|
41
|
+
this.ctxBuilder = opts.ctxBuilder;
|
|
42
|
+
this.tickInterval = opts.tickInterval;
|
|
43
|
+
this.survival = opts.survival;
|
|
44
|
+
this.overlays = opts.overlays;
|
|
45
|
+
if (opts.initialMovement) {
|
|
46
|
+
this.movement = createBehavior(opts.initialMovement);
|
|
47
|
+
this.movement.enabled = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
start(): void {
|
|
52
|
+
if (this.timer) return;
|
|
53
|
+
this.timer = setInterval(() => this.tick(), this.tickInterval);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
stop(): void {
|
|
57
|
+
if (this.timer) {
|
|
58
|
+
clearInterval(this.timer);
|
|
59
|
+
this.timer = undefined;
|
|
60
|
+
}
|
|
61
|
+
const ctx = this.ctxBuilder();
|
|
62
|
+
if (this.current && ctx) {
|
|
63
|
+
try {
|
|
64
|
+
this.current.onStop(ctx);
|
|
65
|
+
} catch {
|
|
66
|
+
// ignore
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
this.current = null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setMissionBehaviors(missionId: string, behaviors: Behavior[]): void {
|
|
73
|
+
this.missionId = missionId;
|
|
74
|
+
for (const b of behaviors) b.enabled = true;
|
|
75
|
+
this.missionBehaviors = behaviors;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
removeMissionBehaviors(missionId: string): void {
|
|
79
|
+
if (this.missionId === missionId) {
|
|
80
|
+
this.missionBehaviors = [];
|
|
81
|
+
this.missionId = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
toggleOverlay(name: string, enabled: boolean, params?: Record<string, string>): boolean {
|
|
86
|
+
const b = this.overlays.find((o) => o.name === name);
|
|
87
|
+
if (!b) return false;
|
|
88
|
+
if (params) b.configure(params);
|
|
89
|
+
b.enabled = enabled;
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
isOverlayEnabled(name: string): boolean {
|
|
94
|
+
const b = this.overlays.find((o) => o.name === name);
|
|
95
|
+
return b?.enabled ?? false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
clear(): void {
|
|
99
|
+
for (const o of this.overlays) o.enabled = false;
|
|
100
|
+
this.movement = createBehavior({ name: "idle", params: {} });
|
|
101
|
+
this.movement.enabled = true;
|
|
102
|
+
this.missionBehaviors = [];
|
|
103
|
+
this.missionId = null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
stopMission(): void {
|
|
107
|
+
this.movement = createBehavior({ name: "idle", params: {} });
|
|
108
|
+
this.movement.enabled = true;
|
|
109
|
+
this.missionBehaviors = [];
|
|
110
|
+
this.missionId = null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
currentLabel(): string | null {
|
|
114
|
+
return this.current?.name ?? null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
getStates(ctx: BehaviorContext): BehaviorStateInfo[] {
|
|
118
|
+
const all = this.getAllBehaviors();
|
|
119
|
+
return all.map((b) => ({
|
|
120
|
+
name: b.name,
|
|
121
|
+
category: b.category,
|
|
122
|
+
priority: b.priority,
|
|
123
|
+
enabled: b.effectivelyEnabled,
|
|
124
|
+
active: b.effectivelyEnabled && this.safeActive(b, ctx),
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getAllBehaviors(): Behavior[] {
|
|
129
|
+
const list: Behavior[] = [];
|
|
130
|
+
list.push(...this.survival);
|
|
131
|
+
list.push(...this.overlays);
|
|
132
|
+
for (const b of this.missionBehaviors) list.push(b);
|
|
133
|
+
if (this.movement) list.push(this.movement);
|
|
134
|
+
return list;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
modeState(): ModeState {
|
|
138
|
+
const next = this.computeMode();
|
|
139
|
+
if (next !== this.lastMode) {
|
|
140
|
+
this.lastSwitch.from = this.lastMode;
|
|
141
|
+
this.lastSwitch.to = next;
|
|
142
|
+
this.lastSwitch.reason = next === "EMERGENCY" ? "emergency_engaged" : "emergency_cleared";
|
|
143
|
+
this.lastSwitch.at = Date.now();
|
|
144
|
+
this.lastMode = next;
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
current: this.lastMode,
|
|
148
|
+
mission: null,
|
|
149
|
+
lastSwitch: { ...this.lastSwitch },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private tick(): void {
|
|
154
|
+
const ctx = this.ctxBuilder();
|
|
155
|
+
if (!ctx) return;
|
|
156
|
+
|
|
157
|
+
const candidates: Behavior[] = [];
|
|
158
|
+
for (const b of this.survival) candidates.push(b);
|
|
159
|
+
for (const b of this.overlays) candidates.push(b);
|
|
160
|
+
for (const b of this.missionBehaviors) candidates.push(b);
|
|
161
|
+
if (!this.missionHasActive(ctx) && this.movement) candidates.push(this.movement);
|
|
162
|
+
|
|
163
|
+
let winner: Behavior | null = null;
|
|
164
|
+
for (const b of candidates) {
|
|
165
|
+
if (!b.effectivelyEnabled) continue;
|
|
166
|
+
if (!this.safeActive(b, ctx)) continue;
|
|
167
|
+
if (!winner || b.priority > winner.priority) winner = b;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (this.current !== winner) {
|
|
171
|
+
if (this.current) {
|
|
172
|
+
try {
|
|
173
|
+
this.current.onStop(ctx);
|
|
174
|
+
} catch (e) {
|
|
175
|
+
ctx.log(`行为 onStop 失败(${this.current.name}): ${e}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
this.current = winner;
|
|
179
|
+
if (this.current) {
|
|
180
|
+
try {
|
|
181
|
+
this.current.onStart(ctx);
|
|
182
|
+
} catch (e) {
|
|
183
|
+
ctx.log(`行为 onStart 失败(${this.current.name}): ${e}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (this.current) {
|
|
189
|
+
try {
|
|
190
|
+
this.current.onTick(ctx);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
ctx.log(`行为 onTick 失败(${this.current.name}): ${e}`);
|
|
193
|
+
}
|
|
194
|
+
if (this.current.isFinished()) {
|
|
195
|
+
try {
|
|
196
|
+
this.current.onStop(ctx);
|
|
197
|
+
} catch {
|
|
198
|
+
// ignore
|
|
199
|
+
}
|
|
200
|
+
this.current = null;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private safeActive(b: Behavior, ctx: BehaviorContext): boolean {
|
|
206
|
+
try {
|
|
207
|
+
return b.isActive(ctx);
|
|
208
|
+
} catch {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private missionHasActive(ctx: BehaviorContext): boolean {
|
|
214
|
+
for (const b of this.missionBehaviors) {
|
|
215
|
+
if (!b.effectivelyEnabled) continue;
|
|
216
|
+
if (this.safeActive(b, ctx)) return true;
|
|
217
|
+
}
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private computeMode(): BehaviorMode {
|
|
222
|
+
const ctx = this.ctxBuilder();
|
|
223
|
+
if (!ctx) return this.lastMode;
|
|
224
|
+
const all = [...this.survival, ...this.overlays, ...this.missionBehaviors];
|
|
225
|
+
if (!this.missionHasActive(ctx) && this.movement) all.push(this.movement);
|
|
226
|
+
let emergency = false;
|
|
227
|
+
let mission = false;
|
|
228
|
+
for (const b of all) {
|
|
229
|
+
if (!b.effectivelyEnabled) continue;
|
|
230
|
+
if (!this.safeActive(b, ctx)) continue;
|
|
231
|
+
if (b.category === "survival") {
|
|
232
|
+
emergency = true;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
if (b.category === "movement" || b.category === "combat") {
|
|
236
|
+
mission = true;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (emergency) return "EMERGENCY";
|
|
240
|
+
if (mission) return "MISSION";
|
|
241
|
+
return "IDLE";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Behavior, type BehaviorContext } from "../base-behavior";
|
|
2
|
+
import { nearestHostile } from "../../util/entities";
|
|
3
|
+
import { eatFood, findFood } from "../../util/inventory";
|
|
4
|
+
|
|
5
|
+
const HUNGER_THRESHOLD = 10;
|
|
6
|
+
const COMBAT_BLOCK_RADIUS = 5;
|
|
7
|
+
|
|
8
|
+
export class AutoEatBehavior extends Behavior {
|
|
9
|
+
readonly name = "auto_eat";
|
|
10
|
+
readonly category = "maintenance" as const;
|
|
11
|
+
private eating = false;
|
|
12
|
+
|
|
13
|
+
isActive(ctx: BehaviorContext): boolean {
|
|
14
|
+
const bot = ctx.bot;
|
|
15
|
+
if ((bot.food ?? 20) > HUNGER_THRESHOLD) return false;
|
|
16
|
+
if (!findFood(bot)) return false;
|
|
17
|
+
return nearestHostile(bot, COMBAT_BLOCK_RADIUS) === null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async onTick(ctx: BehaviorContext): Promise<void> {
|
|
21
|
+
if (this.eating) return;
|
|
22
|
+
this.eating = true;
|
|
23
|
+
try {
|
|
24
|
+
ctx.bot.clearControlStates();
|
|
25
|
+
} catch {
|
|
26
|
+
// ignore
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
await eatFood(ctx.bot);
|
|
30
|
+
} finally {
|
|
31
|
+
this.eating = false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onStop(): void {
|
|
36
|
+
this.eating = false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
contributesState(ctx: BehaviorContext): Record<string, unknown> {
|
|
40
|
+
return {
|
|
41
|
+
eating: this.eating,
|
|
42
|
+
food: ctx.bot.food ?? 0,
|
|
43
|
+
foodLow: (ctx.bot.food ?? 20) <= HUNGER_THRESHOLD,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Behavior, type BehaviorContext } from "../base-behavior";
|
|
2
|
+
|
|
3
|
+
export class EscapeLavaBehavior extends Behavior {
|
|
4
|
+
readonly name = "escape_lava";
|
|
5
|
+
readonly category = "survival" as const;
|
|
6
|
+
|
|
7
|
+
isActive(ctx: BehaviorContext): boolean {
|
|
8
|
+
const bot = ctx.bot;
|
|
9
|
+
const pos = bot.entity?.position;
|
|
10
|
+
if (!pos) return false;
|
|
11
|
+
const feet = bot.blockAt(pos);
|
|
12
|
+
const below = bot.blockAt(pos.offset(0, -1, 0));
|
|
13
|
+
const isDanger = (b: any) => {
|
|
14
|
+
const n = String(b?.name ?? "").toLowerCase();
|
|
15
|
+
return n === "lava" || n === "fire" || n === "flowing_lava";
|
|
16
|
+
};
|
|
17
|
+
return isDanger(feet) || isDanger(below);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onStart(ctx: BehaviorContext): void {
|
|
21
|
+
try { ctx.bot.pathEngine?.setGoal(null); } catch { /* ignore */ }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
onTick(ctx: BehaviorContext): void {
|
|
25
|
+
ctx.bot.setControlState("jump", true);
|
|
26
|
+
ctx.bot.setControlState("sprint", true);
|
|
27
|
+
ctx.bot.setControlState("forward", true);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
onStop(ctx: BehaviorContext): void {
|
|
31
|
+
try {
|
|
32
|
+
ctx.bot.clearControlStates();
|
|
33
|
+
} catch {
|
|
34
|
+
// ignore
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
contributesState(): Record<string, unknown> {
|
|
39
|
+
return {
|
|
40
|
+
active: true,
|
|
41
|
+
hazard: "lava_or_fire",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Behavior, type BehaviorContext } from "../base-behavior";
|
|
2
|
+
|
|
3
|
+
export class EscapeWaterBehavior extends Behavior {
|
|
4
|
+
readonly name = "escape_water";
|
|
5
|
+
readonly category = "survival" as const;
|
|
6
|
+
|
|
7
|
+
isActive(ctx: BehaviorContext): boolean {
|
|
8
|
+
return (ctx.bot.oxygenLevel ?? 20) <= 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
onStart(ctx: BehaviorContext): void {
|
|
12
|
+
try { ctx.bot.pathEngine?.setGoal(null); } catch { /* ignore */ }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
onTick(ctx: BehaviorContext): void {
|
|
16
|
+
ctx.bot.setControlState("jump", true);
|
|
17
|
+
ctx.bot.setControlState("forward", true);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onStop(ctx: BehaviorContext): void {
|
|
21
|
+
try {
|
|
22
|
+
ctx.bot.clearControlStates();
|
|
23
|
+
} catch {
|
|
24
|
+
// ignore
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
contributesState(ctx: BehaviorContext): Record<string, unknown> {
|
|
29
|
+
return {
|
|
30
|
+
active: true,
|
|
31
|
+
oxygen: ctx.bot.oxygenLevel ?? 0,
|
|
32
|
+
drowning: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Behavior, type BehaviorContext } from "../base-behavior";
|
|
2
|
+
import { GoalXZ } from "../../path-engine";
|
|
3
|
+
import { nearestCreeper } from "../../util/entities";
|
|
4
|
+
import { hasShield } from "../../util/inventory";
|
|
5
|
+
|
|
6
|
+
const CREEPER_FLEE_RADIUS = 6;
|
|
7
|
+
const FLEE_DISTANCE = 10;
|
|
8
|
+
const FLEE_TIMEOUT_MS = 5_000;
|
|
9
|
+
|
|
10
|
+
export class FleeCreeperBehavior extends Behavior {
|
|
11
|
+
readonly name = "flee_creeper";
|
|
12
|
+
readonly category = "survival" as const;
|
|
13
|
+
private fleeing = false;
|
|
14
|
+
|
|
15
|
+
isActive(ctx: BehaviorContext): boolean {
|
|
16
|
+
if (hasShield(ctx.bot)) return false;
|
|
17
|
+
return nearestCreeper(ctx.bot, CREEPER_FLEE_RADIUS) !== null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
onTick(ctx: BehaviorContext): void {
|
|
21
|
+
if (this.fleeing) return;
|
|
22
|
+
const bot = ctx.bot;
|
|
23
|
+
const engine = bot.pathEngine;
|
|
24
|
+
if (!engine) return;
|
|
25
|
+
const creeper = nearestCreeper(bot, CREEPER_FLEE_RADIUS + 2);
|
|
26
|
+
if (!creeper) return;
|
|
27
|
+
const pos = bot.entity?.position;
|
|
28
|
+
const cp = creeper.position;
|
|
29
|
+
if (!pos || !cp) return;
|
|
30
|
+
const dx = pos.x - cp.x;
|
|
31
|
+
const dz = pos.z - cp.z;
|
|
32
|
+
const len = Math.hypot(dx, dz) || 1;
|
|
33
|
+
const tx = Math.floor(pos.x + (dx / len) * FLEE_DISTANCE);
|
|
34
|
+
const tz = Math.floor(pos.z + (dz / len) * FLEE_DISTANCE);
|
|
35
|
+
this.fleeing = true;
|
|
36
|
+
ctx.log(`flee_creeper -> (${tx},${tz})`);
|
|
37
|
+
const timer = setTimeout(() => {
|
|
38
|
+
try {
|
|
39
|
+
engine.stop();
|
|
40
|
+
} catch {
|
|
41
|
+
// ignore
|
|
42
|
+
}
|
|
43
|
+
}, FLEE_TIMEOUT_MS);
|
|
44
|
+
engine
|
|
45
|
+
.goto(new GoalXZ(tx, tz))
|
|
46
|
+
.catch(() => {
|
|
47
|
+
// ignore
|
|
48
|
+
})
|
|
49
|
+
.finally(() => {
|
|
50
|
+
clearTimeout(timer);
|
|
51
|
+
try {
|
|
52
|
+
engine.setGoal(null);
|
|
53
|
+
} catch {
|
|
54
|
+
// ignore
|
|
55
|
+
}
|
|
56
|
+
this.fleeing = false;
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
onStop(ctx: BehaviorContext): void {
|
|
61
|
+
try {
|
|
62
|
+
ctx.bot.pathEngine?.stop();
|
|
63
|
+
} catch {
|
|
64
|
+
// ignore
|
|
65
|
+
}
|
|
66
|
+
this.fleeing = false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
contributesState(): Record<string, unknown> {
|
|
70
|
+
return {
|
|
71
|
+
active: true,
|
|
72
|
+
hazard: "creeper",
|
|
73
|
+
fleeing: this.fleeing,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Behavior, type BehaviorContext } from "../base-behavior";
|
|
2
|
+
|
|
3
|
+
const FALL_VELOCITY_THRESHOLD = -0.7;
|
|
4
|
+
|
|
5
|
+
export class MlgFallBehavior extends Behavior {
|
|
6
|
+
readonly name = "mlg_fall";
|
|
7
|
+
readonly category = "survival" as const;
|
|
8
|
+
private placed = false;
|
|
9
|
+
|
|
10
|
+
isActive(ctx: BehaviorContext): boolean {
|
|
11
|
+
const bot = ctx.bot;
|
|
12
|
+
const vy = bot.entity?.velocity?.y;
|
|
13
|
+
if (vy === undefined || vy >= FALL_VELOCITY_THRESHOLD) return false;
|
|
14
|
+
const head = bot.entity?.position;
|
|
15
|
+
if (!head) return false;
|
|
16
|
+
const below = bot.blockAt(head.offset(0, -2, 0));
|
|
17
|
+
const n = String(below?.name ?? "").toLowerCase();
|
|
18
|
+
return n !== "water" && n !== "lava";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
onStart(ctx: BehaviorContext): void {
|
|
22
|
+
this.placed = false;
|
|
23
|
+
try { ctx.bot.pathEngine?.setGoal(null); } catch { /* ignore */ }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
onTick(ctx: BehaviorContext): void {
|
|
27
|
+
if (this.placed) return;
|
|
28
|
+
const bot = ctx.bot;
|
|
29
|
+
const bucket = bot.inventory?.items?.().find((i: any) => i.name === "water_bucket");
|
|
30
|
+
if (!bucket) return;
|
|
31
|
+
this.placed = true;
|
|
32
|
+
void (async () => {
|
|
33
|
+
try {
|
|
34
|
+
await bot.equip(bucket, "hand");
|
|
35
|
+
bot.look(0, Math.PI / 2, true);
|
|
36
|
+
bot.activateItem();
|
|
37
|
+
setTimeout(() => {
|
|
38
|
+
try {
|
|
39
|
+
bot.activateItem();
|
|
40
|
+
} catch {
|
|
41
|
+
// ignore
|
|
42
|
+
}
|
|
43
|
+
}, 400);
|
|
44
|
+
} catch {
|
|
45
|
+
this.placed = false;
|
|
46
|
+
}
|
|
47
|
+
})();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
contributesState(): Record<string, unknown> {
|
|
51
|
+
return {
|
|
52
|
+
active: true,
|
|
53
|
+
waterBucketPlaced: this.placed,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|