@rpgjs/action-battle 5.0.0-beta.10 → 5.0.0-beta.12
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/CHANGELOG.md +22 -0
- package/dist/client/ai.server.d.ts +45 -8
- package/dist/client/attack-input.d.ts +3 -0
- package/dist/client/core/action-use.d.ts +18 -0
- package/dist/client/core/ai-behavior-tree.d.ts +99 -0
- package/dist/client/core/attack-runtime.d.ts +2 -0
- package/dist/client/core/defaults.d.ts +2 -1
- package/dist/client/core/equipment.d.ts +1 -0
- package/dist/client/core/targets.d.ts +15 -0
- package/dist/client/enemies/factory.d.ts +2 -0
- package/dist/client/index.d.ts +12 -7
- package/dist/client/index.js +16 -11
- package/dist/client/index10.js +32 -56
- package/dist/client/index11.js +99 -52
- package/dist/client/index12.js +76 -103
- package/dist/client/index13.js +72 -135
- package/dist/client/index14.js +67 -23
- package/dist/client/index15.js +197 -63
- package/dist/client/index16.js +112 -1337
- package/dist/client/index17.js +193 -7
- package/dist/client/index18.js +32 -58
- package/dist/client/index19.js +70 -8
- package/dist/client/index20.js +57 -501
- package/dist/client/index21.js +69 -0
- package/dist/client/index22.js +225 -0
- package/dist/client/index23.js +16 -0
- package/dist/client/index24.js +25 -0
- package/dist/client/index25.js +107 -0
- package/dist/client/index26.js +1707 -0
- package/dist/client/index27.js +12 -0
- package/dist/client/index28.js +589 -0
- package/dist/client/index4.js +79 -38
- package/dist/client/index6.js +65 -306
- package/dist/client/index7.js +33 -33
- package/dist/client/index8.js +24 -100
- package/dist/client/index9.js +293 -61
- package/dist/client/locomotion.d.ts +16 -0
- package/dist/client/movement.d.ts +14 -0
- package/dist/client/server.d.ts +7 -3
- package/dist/client/ui.d.ts +22 -0
- package/dist/client/visual.d.ts +15 -0
- package/dist/server/ai.server.d.ts +45 -8
- package/dist/server/attack-input.d.ts +3 -0
- package/dist/server/core/action-use.d.ts +18 -0
- package/dist/server/core/ai-behavior-tree.d.ts +99 -0
- package/dist/server/core/attack-runtime.d.ts +2 -0
- package/dist/server/core/defaults.d.ts +2 -1
- package/dist/server/core/equipment.d.ts +1 -0
- package/dist/server/core/targets.d.ts +15 -0
- package/dist/server/enemies/factory.d.ts +2 -0
- package/dist/server/index.d.ts +12 -7
- package/dist/server/index.js +14 -9
- package/dist/server/index10.js +64 -1336
- package/dist/server/index11.js +33 -33
- package/dist/server/index13.js +66 -11
- package/dist/server/index14.js +206 -484
- package/dist/server/index15.js +15 -9
- package/dist/server/index16.js +26 -0
- package/dist/server/index17.js +25 -0
- package/dist/server/index18.js +107 -0
- package/dist/server/index19.js +1707 -0
- package/dist/server/index2.js +10 -2
- package/dist/server/index20.js +37 -0
- package/dist/server/index21.js +588 -0
- package/dist/server/index22.js +78 -0
- package/dist/server/index23.js +12 -0
- package/dist/server/index5.js +79 -38
- package/dist/server/index6.js +192 -129
- package/dist/server/index7.js +198 -24
- package/dist/server/index8.js +28 -66
- package/dist/server/index9.js +68 -51
- package/dist/server/locomotion.d.ts +16 -0
- package/dist/server/movement.d.ts +14 -0
- package/dist/server/server.d.ts +7 -3
- package/dist/server/ui.d.ts +22 -0
- package/dist/server/visual.d.ts +15 -0
- package/package.json +10 -10
- package/src/ai.server.spec.ts +233 -0
- package/src/ai.server.ts +627 -108
- package/src/animations.spec.ts +40 -0
- package/src/animations.ts +31 -9
- package/src/attack-input.spec.ts +51 -0
- package/src/attack-input.ts +59 -0
- package/src/client.ts +75 -62
- package/src/components/action-bar.ce +2 -2
- package/src/config.ts +84 -37
- package/src/core/action-use.spec.ts +317 -0
- package/src/core/action-use.ts +386 -0
- package/src/core/ai-behavior-tree.spec.ts +116 -0
- package/src/core/ai-behavior-tree.ts +272 -0
- package/src/core/attack-profile.spec.ts +46 -0
- package/src/core/attack-runtime.spec.ts +35 -0
- package/src/core/attack-runtime.ts +32 -0
- package/src/core/context.ts +9 -0
- package/src/core/contracts.ts +146 -1
- package/src/core/defaults.ts +56 -0
- package/src/core/equipment.ts +9 -5
- package/src/core/targets.spec.ts +112 -0
- package/src/core/targets.ts +147 -0
- package/src/enemies/factory.ts +8 -0
- package/src/index.ts +111 -2
- package/src/locomotion.spec.ts +51 -0
- package/src/locomotion.ts +48 -0
- package/src/movement.spec.ts +78 -0
- package/src/movement.ts +46 -0
- package/src/server.ts +242 -66
- package/src/types.ts +105 -35
- package/src/ui.ts +113 -0
- package/src/visual.spec.ts +166 -0
- package/src/visual.ts +285 -0
- package/README.md +0 -1242
package/src/ai.server.spec.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { MAXHP } from "@rpgjs/server";
|
|
2
2
|
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
3
3
|
import { BattleAi } from "./ai.server";
|
|
4
|
+
import { chase, idle, ifTargetVisible } from "./core/ai-behavior-tree";
|
|
5
|
+
import { setActionBattleSystems } from "./core/context";
|
|
4
6
|
|
|
5
7
|
const createEvent = () => ({
|
|
6
8
|
id: "monster-1",
|
|
@@ -13,11 +15,13 @@ const createEvent = () => ({
|
|
|
13
15
|
showHit: vi.fn(),
|
|
14
16
|
setGraphicAnimation: vi.fn(),
|
|
15
17
|
stopMoveTo: vi.fn(),
|
|
18
|
+
moveTo: vi.fn(),
|
|
16
19
|
getCurrentMap: vi.fn(() => ({})),
|
|
17
20
|
remove: vi.fn(),
|
|
18
21
|
x: vi.fn(() => 0),
|
|
19
22
|
y: vi.fn(() => 0),
|
|
20
23
|
direction: vi.fn(() => "down"),
|
|
24
|
+
changeDirection: vi.fn(),
|
|
21
25
|
});
|
|
22
26
|
|
|
23
27
|
const createPlayer = () => ({
|
|
@@ -37,6 +41,7 @@ describe("BattleAi defeat flow", () => {
|
|
|
37
41
|
afterEach(() => {
|
|
38
42
|
vi.useRealTimers();
|
|
39
43
|
vi.restoreAllMocks();
|
|
44
|
+
setActionBattleSystems({});
|
|
40
45
|
});
|
|
41
46
|
|
|
42
47
|
test("awards the attacker and requests a defeated remove transition", () => {
|
|
@@ -118,3 +123,231 @@ describe("BattleAi defeat flow", () => {
|
|
|
118
123
|
});
|
|
119
124
|
});
|
|
120
125
|
});
|
|
126
|
+
|
|
127
|
+
describe("BattleAi vision setup", () => {
|
|
128
|
+
afterEach(() => {
|
|
129
|
+
vi.useRealTimers();
|
|
130
|
+
vi.restoreAllMocks();
|
|
131
|
+
setActionBattleSystems({});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("retries vision attachment when the physics body is not ready yet", () => {
|
|
135
|
+
vi.useFakeTimers();
|
|
136
|
+
const event = createEvent();
|
|
137
|
+
const visionShape = { id: "vision_monster-1" };
|
|
138
|
+
event.attachShape.mockReturnValueOnce(undefined).mockReturnValueOnce(visionShape);
|
|
139
|
+
|
|
140
|
+
const ai = new BattleAi(event as any);
|
|
141
|
+
|
|
142
|
+
expect(event.attachShape).toHaveBeenCalledTimes(1);
|
|
143
|
+
|
|
144
|
+
vi.advanceTimersByTime(60);
|
|
145
|
+
|
|
146
|
+
expect(event.attachShape).toHaveBeenCalledTimes(2);
|
|
147
|
+
expect(event.attachShape).toHaveBeenLastCalledWith("vision_monster-1", {
|
|
148
|
+
radius: 150,
|
|
149
|
+
width: 300,
|
|
150
|
+
height: 300,
|
|
151
|
+
angle: 360,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
ai.destroy();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("BattleAi behavior tree", () => {
|
|
159
|
+
afterEach(() => {
|
|
160
|
+
vi.useRealTimers();
|
|
161
|
+
vi.restoreAllMocks();
|
|
162
|
+
setActionBattleSystems({});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("executes simplified behavior intents", () => {
|
|
166
|
+
vi.useFakeTimers();
|
|
167
|
+
const event = createEvent();
|
|
168
|
+
event.attachShape.mockReturnValue({ id: "vision_monster-1" });
|
|
169
|
+
const player = {
|
|
170
|
+
...createPlayer(),
|
|
171
|
+
x: vi.fn(() => 20),
|
|
172
|
+
y: vi.fn(() => 0),
|
|
173
|
+
};
|
|
174
|
+
const ai = new BattleAi(event as any, {
|
|
175
|
+
simpleBehavior: {
|
|
176
|
+
when: [ifTargetVisible(chase())],
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
ai.onDetectInShape(player as any, {});
|
|
181
|
+
vi.advanceTimersByTime(100);
|
|
182
|
+
|
|
183
|
+
expect(event.moveTo).toHaveBeenCalledWith(player);
|
|
184
|
+
ai.destroy();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("composes named AI presets with local overrides", () => {
|
|
188
|
+
vi.useFakeTimers();
|
|
189
|
+
const event = createEvent();
|
|
190
|
+
event.attachShape.mockReturnValue({ id: "vision_monster-1" });
|
|
191
|
+
setActionBattleSystems({
|
|
192
|
+
ai: {
|
|
193
|
+
presets: {
|
|
194
|
+
slime: {
|
|
195
|
+
preset: "aggressive",
|
|
196
|
+
visionRange: 220,
|
|
197
|
+
simpleBehavior: {
|
|
198
|
+
otherwise: chase(),
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const ai = new BattleAi(event as any, {
|
|
206
|
+
preset: "slime",
|
|
207
|
+
attackRange: 70,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
expect(event.attachShape).toHaveBeenCalledWith("vision_monster-1", {
|
|
211
|
+
radius: 220,
|
|
212
|
+
width: 440,
|
|
213
|
+
height: 440,
|
|
214
|
+
angle: 360,
|
|
215
|
+
});
|
|
216
|
+
ai.destroy();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("local behavior tree overrides preset simple behavior", () => {
|
|
220
|
+
vi.useFakeTimers();
|
|
221
|
+
const event = createEvent();
|
|
222
|
+
event.attachShape.mockReturnValue({ id: "vision_monster-1" });
|
|
223
|
+
const player = {
|
|
224
|
+
...createPlayer(),
|
|
225
|
+
hp: 10,
|
|
226
|
+
x: vi.fn(() => 20),
|
|
227
|
+
y: vi.fn(() => 0),
|
|
228
|
+
};
|
|
229
|
+
setActionBattleSystems({
|
|
230
|
+
ai: {
|
|
231
|
+
presets: {
|
|
232
|
+
ranged: {
|
|
233
|
+
simpleBehavior: {
|
|
234
|
+
otherwise: chase(),
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const ai = new BattleAi(event as any, {
|
|
242
|
+
preset: "ranged",
|
|
243
|
+
behaviorTree: () => ({ status: "success", intent: idle() }),
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
ai.onDetectInShape(player as any, {});
|
|
247
|
+
vi.advanceTimersByTime(100);
|
|
248
|
+
|
|
249
|
+
expect(event.moveTo).not.toHaveBeenCalled();
|
|
250
|
+
expect(event.stopMoveTo).toHaveBeenCalled();
|
|
251
|
+
ai.destroy();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("does not target an already defeated player", () => {
|
|
255
|
+
const event = createEvent();
|
|
256
|
+
event.attachShape.mockReturnValue({ id: "vision_monster-1" });
|
|
257
|
+
const ai = new BattleAi(event as any);
|
|
258
|
+
const player = {
|
|
259
|
+
...createPlayer(),
|
|
260
|
+
hp: 0,
|
|
261
|
+
x: vi.fn(() => 20),
|
|
262
|
+
y: vi.fn(() => 0),
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
ai.onDetectInShape(player as any, {});
|
|
266
|
+
|
|
267
|
+
expect(ai.getTarget()).toBeNull();
|
|
268
|
+
ai.destroy();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("behavior tree idle fallback does not block target acquisition", () => {
|
|
272
|
+
vi.useFakeTimers();
|
|
273
|
+
const event = createEvent();
|
|
274
|
+
const player = {
|
|
275
|
+
...createPlayer(),
|
|
276
|
+
hp: 10,
|
|
277
|
+
x: vi.fn(() => 30),
|
|
278
|
+
y: vi.fn(() => 0),
|
|
279
|
+
};
|
|
280
|
+
const map = {
|
|
281
|
+
getPlayers: vi.fn(() => [player]),
|
|
282
|
+
getEvents: vi.fn(() => [event]),
|
|
283
|
+
};
|
|
284
|
+
event.getCurrentMap.mockReturnValue(map);
|
|
285
|
+
event.attachShape.mockReturnValue({ id: "vision_monster-1" });
|
|
286
|
+
|
|
287
|
+
const ai = new BattleAi(event as any, {
|
|
288
|
+
simpleBehavior: {
|
|
289
|
+
otherwise: idle(),
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
vi.advanceTimersByTime(100);
|
|
294
|
+
|
|
295
|
+
expect(ai.getTarget()).toBe(player);
|
|
296
|
+
ai.destroy();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("clears its target when the player is defeated", () => {
|
|
300
|
+
vi.useFakeTimers();
|
|
301
|
+
const event = createEvent();
|
|
302
|
+
event.attachShape.mockReturnValue({ id: "vision_monster-1" });
|
|
303
|
+
const player = {
|
|
304
|
+
...createPlayer(),
|
|
305
|
+
hp: 10,
|
|
306
|
+
x: vi.fn(() => 20),
|
|
307
|
+
y: vi.fn(() => 0),
|
|
308
|
+
};
|
|
309
|
+
const ai = new BattleAi(event as any);
|
|
310
|
+
|
|
311
|
+
ai.onDetectInShape(player as any, {});
|
|
312
|
+
expect(ai.getTarget()).toBe(player);
|
|
313
|
+
|
|
314
|
+
player.hp = 0;
|
|
315
|
+
vi.advanceTimersByTime(100);
|
|
316
|
+
|
|
317
|
+
expect(ai.getTarget()).toBeNull();
|
|
318
|
+
expect(event.stopMoveTo).toHaveBeenCalled();
|
|
319
|
+
ai.destroy();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("can target hostile BattleAi events by faction", () => {
|
|
323
|
+
vi.useFakeTimers();
|
|
324
|
+
const event = createEvent();
|
|
325
|
+
const hostile = {
|
|
326
|
+
...createEvent(),
|
|
327
|
+
id: "bandit-1",
|
|
328
|
+
hp: 10,
|
|
329
|
+
x: vi.fn(() => 30),
|
|
330
|
+
y: vi.fn(() => 0),
|
|
331
|
+
battleAi: {
|
|
332
|
+
getFaction: () => "bandits",
|
|
333
|
+
getTargets: () => "players",
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
const map = {
|
|
337
|
+
getPlayers: vi.fn(() => []),
|
|
338
|
+
getEvents: vi.fn(() => [event, hostile]),
|
|
339
|
+
};
|
|
340
|
+
event.getCurrentMap.mockReturnValue(map);
|
|
341
|
+
event.attachShape.mockReturnValue({ id: "vision_monster-1" });
|
|
342
|
+
|
|
343
|
+
const ai = new BattleAi(event as any, {
|
|
344
|
+
faction: "guards",
|
|
345
|
+
targets: ["bandits"],
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
vi.advanceTimersByTime(100);
|
|
349
|
+
|
|
350
|
+
expect(ai.getTarget()).toBe(hostile);
|
|
351
|
+
ai.destroy();
|
|
352
|
+
});
|
|
353
|
+
});
|