@rpgjs/action-battle 5.0.0-beta.11 → 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.
Files changed (110) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/client/ai.server.d.ts +45 -8
  3. package/dist/client/attack-input.d.ts +3 -0
  4. package/dist/client/core/action-use.d.ts +18 -0
  5. package/dist/client/core/ai-behavior-tree.d.ts +99 -0
  6. package/dist/client/core/attack-runtime.d.ts +2 -0
  7. package/dist/client/core/defaults.d.ts +2 -1
  8. package/dist/client/core/equipment.d.ts +1 -0
  9. package/dist/client/core/targets.d.ts +15 -0
  10. package/dist/client/enemies/factory.d.ts +2 -0
  11. package/dist/client/index.d.ts +12 -7
  12. package/dist/client/index.js +16 -11
  13. package/dist/client/index10.js +32 -56
  14. package/dist/client/index11.js +99 -52
  15. package/dist/client/index12.js +76 -103
  16. package/dist/client/index13.js +72 -135
  17. package/dist/client/index14.js +67 -23
  18. package/dist/client/index15.js +197 -63
  19. package/dist/client/index16.js +112 -1337
  20. package/dist/client/index17.js +193 -7
  21. package/dist/client/index18.js +32 -58
  22. package/dist/client/index19.js +70 -8
  23. package/dist/client/index20.js +57 -501
  24. package/dist/client/index21.js +69 -0
  25. package/dist/client/index22.js +225 -0
  26. package/dist/client/index23.js +16 -0
  27. package/dist/client/index24.js +25 -0
  28. package/dist/client/index25.js +107 -0
  29. package/dist/client/index26.js +1707 -0
  30. package/dist/client/index27.js +12 -0
  31. package/dist/client/index28.js +589 -0
  32. package/dist/client/index4.js +79 -38
  33. package/dist/client/index6.js +65 -306
  34. package/dist/client/index7.js +33 -33
  35. package/dist/client/index8.js +24 -100
  36. package/dist/client/index9.js +293 -61
  37. package/dist/client/locomotion.d.ts +16 -0
  38. package/dist/client/movement.d.ts +14 -0
  39. package/dist/client/server.d.ts +7 -3
  40. package/dist/client/ui.d.ts +22 -0
  41. package/dist/client/visual.d.ts +15 -0
  42. package/dist/server/ai.server.d.ts +45 -8
  43. package/dist/server/attack-input.d.ts +3 -0
  44. package/dist/server/core/action-use.d.ts +18 -0
  45. package/dist/server/core/ai-behavior-tree.d.ts +99 -0
  46. package/dist/server/core/attack-runtime.d.ts +2 -0
  47. package/dist/server/core/defaults.d.ts +2 -1
  48. package/dist/server/core/equipment.d.ts +1 -0
  49. package/dist/server/core/targets.d.ts +15 -0
  50. package/dist/server/enemies/factory.d.ts +2 -0
  51. package/dist/server/index.d.ts +12 -7
  52. package/dist/server/index.js +14 -9
  53. package/dist/server/index10.js +64 -1336
  54. package/dist/server/index11.js +33 -33
  55. package/dist/server/index13.js +66 -11
  56. package/dist/server/index14.js +206 -484
  57. package/dist/server/index15.js +15 -9
  58. package/dist/server/index16.js +26 -0
  59. package/dist/server/index17.js +25 -0
  60. package/dist/server/index18.js +107 -0
  61. package/dist/server/index19.js +1707 -0
  62. package/dist/server/index2.js +10 -2
  63. package/dist/server/index20.js +37 -0
  64. package/dist/server/index21.js +588 -0
  65. package/dist/server/index22.js +78 -0
  66. package/dist/server/index23.js +12 -0
  67. package/dist/server/index5.js +79 -38
  68. package/dist/server/index6.js +192 -129
  69. package/dist/server/index7.js +198 -24
  70. package/dist/server/index8.js +28 -66
  71. package/dist/server/index9.js +68 -51
  72. package/dist/server/locomotion.d.ts +16 -0
  73. package/dist/server/movement.d.ts +14 -0
  74. package/dist/server/server.d.ts +7 -3
  75. package/dist/server/ui.d.ts +22 -0
  76. package/dist/server/visual.d.ts +15 -0
  77. package/package.json +5 -5
  78. package/src/ai.server.spec.ts +233 -0
  79. package/src/ai.server.ts +627 -108
  80. package/src/animations.spec.ts +40 -0
  81. package/src/animations.ts +31 -9
  82. package/src/attack-input.spec.ts +51 -0
  83. package/src/attack-input.ts +59 -0
  84. package/src/client.ts +75 -62
  85. package/src/config.ts +84 -37
  86. package/src/core/action-use.spec.ts +317 -0
  87. package/src/core/action-use.ts +386 -0
  88. package/src/core/ai-behavior-tree.spec.ts +116 -0
  89. package/src/core/ai-behavior-tree.ts +272 -0
  90. package/src/core/attack-profile.spec.ts +46 -0
  91. package/src/core/attack-runtime.spec.ts +35 -0
  92. package/src/core/attack-runtime.ts +32 -0
  93. package/src/core/context.ts +9 -0
  94. package/src/core/contracts.ts +146 -1
  95. package/src/core/defaults.ts +56 -0
  96. package/src/core/equipment.ts +9 -5
  97. package/src/core/targets.spec.ts +112 -0
  98. package/src/core/targets.ts +147 -0
  99. package/src/enemies/factory.ts +8 -0
  100. package/src/index.ts +111 -2
  101. package/src/locomotion.spec.ts +51 -0
  102. package/src/locomotion.ts +48 -0
  103. package/src/movement.spec.ts +78 -0
  104. package/src/movement.ts +46 -0
  105. package/src/server.ts +242 -66
  106. package/src/types.ts +105 -35
  107. package/src/ui.ts +113 -0
  108. package/src/visual.spec.ts +166 -0
  109. package/src/visual.ts +285 -0
  110. package/README.md +0 -1242
package/src/index.ts CHANGED
@@ -26,6 +26,7 @@ export type {
26
26
  ActionBattleAnimationOptions,
27
27
  ActionBattleAnimationResolver,
28
28
  ActionBattleAnimationResult,
29
+ ActionBattleAiOptions,
29
30
  ActionBattleOptions,
30
31
  ActionBattleActionBarData,
31
32
  ActionBattleActionBarItem,
@@ -35,30 +36,49 @@ export type {
35
36
  ActionBattleAttackOptions,
36
37
  ActionBattleUiOptions,
37
38
  ActionBattleUiActionBarOptions,
39
+ ActionBattleUiAttackPreviewOptions,
40
+ ActionBattleUiGuiEntry,
38
41
  ActionBattleUiTargetingOptions,
39
42
  ActionBattleAttackDirection,
40
43
  ActionBattleAttackHitboxConfig,
41
44
  ActionBattleAttackHitboxMap,
42
45
  ActionBattleAttackHitPolicy,
43
46
  ActionBattleAttackProfile,
44
- ActionBattleDebugOptions,
45
47
  ActionBattleHitReactionProfile,
46
48
  NormalizedActionBattleHitReactionProfile,
47
49
  NormalizedActionBattleAttackProfile,
48
50
  ActionBattleCombatOptions,
49
51
  ActionBattleSystemOptions,
50
52
  ActionBattleAiSystemOptions,
53
+ ActionBattleVisualComposer,
54
+ ActionBattleVisualContext,
55
+ ActionBattleVisualHelpers,
56
+ ActionBattleVisualInput,
57
+ ActionBattleVisualMoment,
58
+ ActionBattleVisualPart,
59
+ ActionBattleVisualPreset,
51
60
  } from "./types";
52
61
  export type {
53
62
  ActionBattleAiBehavior,
54
63
  ActionBattleAiContext,
55
64
  ActionBattleAiDecision,
65
+ ActionBattleAiPreset,
56
66
  ActionBattleAttackContext,
57
67
  ActionBattleCombatSystem,
58
68
  ActionBattleDamageContext,
59
69
  ActionBattleDamageResult,
60
70
  ActionBattleDirection,
61
71
  ActionBattleEntity,
72
+ ActionBattleActionConfig,
73
+ ActionBattleActionMode,
74
+ ActionBattleActionTarget,
75
+ ActionBattleTargetContext,
76
+ ActionBattleTargetOptions,
77
+ ActionBattleTargetSelector,
78
+ ActionBattleProjectileImpactContext,
79
+ ActionBattleProjectileOptions,
80
+ ActionBattleUsable,
81
+ ActionBattleUseContext,
62
82
  ActionBattleHitContext,
63
83
  ActionBattleHitHooks,
64
84
  ActionBattleHitResult,
@@ -67,6 +87,50 @@ export type {
67
87
  ActionBattleKnockbackResult,
68
88
  ActionBattleSystems,
69
89
  } from "./core/contracts";
90
+ export {
91
+ action,
92
+ chase,
93
+ condition,
94
+ decision,
95
+ defineAiBehavior,
96
+ defineAiTree,
97
+ distanceLessThan,
98
+ faceTarget,
99
+ flee,
100
+ fleeFromTarget,
101
+ hpBelow,
102
+ idle,
103
+ ifDistanceLessThan,
104
+ ifHpBelow,
105
+ ifTargetInRange,
106
+ ifTargetVisible,
107
+ inState,
108
+ isEnemyType,
109
+ keepDistance,
110
+ moveToTarget,
111
+ patrol,
112
+ rule,
113
+ selector,
114
+ sequence,
115
+ setMode,
116
+ targetInRange,
117
+ targetVisible,
118
+ useAttack,
119
+ useSkill,
120
+ type ActionBattleAiCondition,
121
+ type ActionBattleAiIntent,
122
+ type ActionBattleAiIntentInput,
123
+ type ActionBattleAiMemory,
124
+ type ActionBattleAiRule,
125
+ type ActionBattleAiSimpleBehavior,
126
+ type ActionBattleAiSnapshotSelf,
127
+ type ActionBattleAiSnapshotTarget,
128
+ type ActionBattleAiTreeContext,
129
+ type ActionBattleAiTreeInput,
130
+ type ActionBattleAiTreeNode,
131
+ type ActionBattleAiTreeResult,
132
+ type ActionBattleAiTreeStatus,
133
+ } from "./core/ai-behavior-tree";
70
134
  export {
71
135
  DEFAULT_ACTION_BATTLE_ATTACK_PROFILE,
72
136
  normalizeActionBattleAttackProfile,
@@ -78,8 +142,18 @@ export {
78
142
  createActionBattleAttackId,
79
143
  getNormalizedActionBattleAttackProfile,
80
144
  resolveActionBattleHitboxSpeed,
145
+ runActionBattleActiveHitbox,
81
146
  scheduleActionBattleStartup,
82
147
  } from "./core/attack-runtime";
148
+ export {
149
+ canActionBattleUseTarget,
150
+ executeActionBattleUse,
151
+ getActionBattleActionConfig,
152
+ getActionBattleActionRange,
153
+ handleActionBattleProjectileDestroy,
154
+ handleActionBattleProjectileImpact,
155
+ shouldUseActionBattleUsable,
156
+ } from "./core/action-use";
83
157
  export {
84
158
  DEFAULT_ACTION_BATTLE_HIT_REACTION,
85
159
  isActionBattleEntityInvincible,
@@ -93,22 +167,56 @@ export {
93
167
  type ActionBattleEnemyAttackProfileMap,
94
168
  type NormalizedActionBattleEnemyAttackProfileMap,
95
169
  } from "./core/enemy-attack-profiles";
96
- export { resolveActionBattleWeaponAttackProfile } from "./core/equipment";
170
+ export {
171
+ resolveActionBattleWeapon,
172
+ resolveActionBattleWeaponAttackProfile,
173
+ } from "./core/equipment";
97
174
  export {
98
175
  DEFAULT_ZELDA_PLAYER_HITBOXES,
99
176
  createDefaultPlayerHitboxResolver,
100
177
  defaultCombatSystem,
101
178
  defaultEnemyBehaviors,
179
+ defaultEnemyPresets,
102
180
  defaultKnockbackResolver,
103
181
  defaultRpgjsDamageResolver,
104
182
  } from "./core/defaults";
183
+ export {
184
+ ACTION_BATTLE_ENEMY_FACTION,
185
+ ACTION_BATTLE_PLAYER_FACTION,
186
+ canActionBattleTarget,
187
+ getActionBattleFaction,
188
+ getActionBattleTargets,
189
+ isActionBattleCombatEntity,
190
+ isActionBattleEvent,
191
+ isActionBattlePlayer,
192
+ matchesActionBattleTargetSelector,
193
+ } from "./core/targets";
105
194
  export {
106
195
  createActionBattleSystems,
107
196
  getActionBattleSystems,
108
197
  } from "./core/context";
109
198
  export { applyActionBattleHit } from "./core/hit";
199
+ export {
200
+ ACTION_BATTLE_CLIENT_VISUAL_ID,
201
+ ACTION_BATTLE_HIT_FX_COMPONENT_ID,
202
+ createActionBattleClientVisuals,
203
+ createActionBattleVisual,
204
+ createClassicActionBattleVisual,
205
+ createFxActionBattleVisual,
206
+ emitActionBattleClientVisual,
207
+ playActionBattleVisual,
208
+ setActionBattlePreviewStarter,
209
+ } from "./visual";
210
+ export {
211
+ ActionBattleUi,
212
+ createActionBattleUi,
213
+ resolveActionBattleUi,
214
+ type ResolvedActionBattleUi,
215
+ } from "./ui";
110
216
  export {
111
217
  createActionEnemy,
218
+ defineActionBattleAiPreset,
219
+ defineActionBattleEnemy,
112
220
  type ActionBattleEnemyPreset,
113
221
  type ActionBattleEnemyPresetMap,
114
222
  } from "./enemies/factory";
@@ -117,6 +225,7 @@ export {
117
225
  export {
118
226
  DEFAULT_PLAYER_ATTACK_HITBOXES,
119
227
  getPlayerWeaponKnockbackForce,
228
+ applyActionBattleEntityHit,
120
229
  applyPlayerHitToEvent,
121
230
  ACTION_BATTLE_ACTION_BAR_GUI_ID,
122
231
  openActionBattleActionBar,
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import {
3
+ forceActionBattleLocomotionAnimation,
4
+ withActionBattleAnimationUnlocked,
5
+ } from "./locomotion";
6
+
7
+ describe("action battle locomotion helpers", () => {
8
+ test("temporarily unlocks animation to restore locomotion state", () => {
9
+ const entity = {
10
+ animationFixed: true,
11
+ setGraphicAnimation: vi.fn(function (this: { animationFixed: boolean }) {
12
+ expect(this.animationFixed).toBe(false);
13
+ }),
14
+ };
15
+
16
+ forceActionBattleLocomotionAnimation(entity, "stand");
17
+
18
+ expect(entity.setGraphicAnimation).toHaveBeenCalledWith("stand");
19
+ expect(entity.animationFixed).toBe(true);
20
+ });
21
+
22
+ test("updates client-side animation signals when no server animation API exists", () => {
23
+ const setAnimationName = vi.fn();
24
+ const entity = {
25
+ animationFixed: false,
26
+ animationName: {
27
+ set: setAnimationName,
28
+ },
29
+ resetAnimationState: vi.fn(),
30
+ };
31
+
32
+ forceActionBattleLocomotionAnimation(entity, "stand");
33
+
34
+ expect(setAnimationName).toHaveBeenCalledWith("stand");
35
+ expect(entity.resetAnimationState).toHaveBeenCalled();
36
+ expect(entity.resetAnimationState.mock.invocationCallOrder[0]).toBeLessThan(
37
+ setAnimationName.mock.invocationCallOrder[0]
38
+ );
39
+ });
40
+
41
+ test("temporarily unlocks action animations while preserving the current lock", () => {
42
+ const entity = { animationFixed: true };
43
+ const callback = vi.fn(() => {
44
+ expect(entity.animationFixed).toBe(false);
45
+ return "played";
46
+ });
47
+
48
+ expect(withActionBattleAnimationUnlocked(entity, callback)).toBe("played");
49
+ expect(entity.animationFixed).toBe(true);
50
+ });
51
+ });
@@ -0,0 +1,48 @@
1
+ type LocomotionEntity = {
2
+ animationFixed?: boolean;
3
+ setGraphicAnimation?: (...args: any[]) => unknown;
4
+ animationName?: {
5
+ set?: (animationName: string) => unknown;
6
+ };
7
+ resetAnimationState?: () => unknown;
8
+ };
9
+
10
+ export const withActionBattleAnimationUnlocked = <T>(
11
+ entity: LocomotionEntity | undefined,
12
+ callback: () => T
13
+ ): T => {
14
+ if (!entity) return callback();
15
+
16
+ const previousAnimationFixed = entity.animationFixed;
17
+ entity.animationFixed = false;
18
+
19
+ try {
20
+ return callback();
21
+ } finally {
22
+ entity.animationFixed = previousAnimationFixed;
23
+ }
24
+ };
25
+
26
+ /**
27
+ * Force a locomotion animation even when an action lock temporarily froze
28
+ * animation changes. This keeps server state and local rendering coherent
29
+ * after attack recovery interrupts movement.
30
+ */
31
+ export const forceActionBattleLocomotionAnimation = (
32
+ entity: LocomotionEntity | undefined,
33
+ animationName: "stand" | "walk"
34
+ ) => {
35
+ if (!entity) return;
36
+
37
+ withActionBattleAnimationUnlocked(entity, () => {
38
+ if (typeof entity.resetAnimationState === "function") {
39
+ entity.resetAnimationState();
40
+ }
41
+
42
+ if (typeof entity.setGraphicAnimation === "function") {
43
+ entity.setGraphicAnimation(animationName);
44
+ } else if (typeof entity.animationName?.set === "function") {
45
+ entity.animationName.set(animationName);
46
+ }
47
+ });
48
+ };
@@ -0,0 +1,78 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import {
3
+ hasActionBattlePhysicsBody,
4
+ isActionBattleMovementResolutionError,
5
+ safeActionBattleDash,
6
+ } from "./movement";
7
+
8
+ describe("action battle movement helpers", () => {
9
+ test("does not dash when the entity physics body has already been removed", () => {
10
+ const entity = {
11
+ id: "enemy-1",
12
+ getCurrentMap: vi.fn(() => ({
13
+ getBody: vi.fn(() => undefined),
14
+ })),
15
+ dash: vi.fn(),
16
+ };
17
+
18
+ expect(safeActionBattleDash(entity, { x: 1, y: 0 }, 10, 200)).toBe(false);
19
+ expect(entity.dash).not.toHaveBeenCalled();
20
+ });
21
+
22
+ test("dashes when the entity still has a physics body", () => {
23
+ const entity = {
24
+ id: "enemy-1",
25
+ getCurrentMap: vi.fn(() => ({
26
+ getBody: vi.fn(() => ({ id: "enemy-1" })),
27
+ })),
28
+ dash: vi.fn(),
29
+ };
30
+
31
+ expect(safeActionBattleDash(entity, { x: 1, y: 0 }, 10, 200)).toBe(true);
32
+ expect(entity.dash).toHaveBeenCalledWith({ x: 1, y: 0 }, 10, 200);
33
+ });
34
+
35
+ test("absorbs teardown races between body check and dash registration", () => {
36
+ const entity = {
37
+ id: "enemy-1",
38
+ getCurrentMap: vi.fn(() => ({
39
+ getBody: vi.fn(() => ({ id: "enemy-1" })),
40
+ })),
41
+ dash: vi.fn(() => {
42
+ throw new Error("MovementManager: unable to resolve entity for identifier enemy-1.");
43
+ }),
44
+ };
45
+
46
+ expect(safeActionBattleDash(entity, { x: 1, y: 0 }, 10, 200)).toBe(false);
47
+ });
48
+
49
+ test("keeps unexpected movement errors visible", () => {
50
+ const entity = {
51
+ id: "enemy-1",
52
+ getCurrentMap: vi.fn(() => ({
53
+ getBody: vi.fn(() => ({ id: "enemy-1" })),
54
+ })),
55
+ dash: vi.fn(() => {
56
+ throw new Error("boom");
57
+ }),
58
+ };
59
+
60
+ expect(() => safeActionBattleDash(entity, { x: 1, y: 0 }, 10, 200)).toThrow(
61
+ "boom"
62
+ );
63
+ });
64
+
65
+ test("detects unavailable bodies only when the map exposes body lookup", () => {
66
+ expect(
67
+ hasActionBattlePhysicsBody({
68
+ id: "enemy-1",
69
+ getCurrentMap: () => ({}),
70
+ })
71
+ ).toBe(true);
72
+ expect(
73
+ isActionBattleMovementResolutionError(
74
+ new Error("MovementManager: unable to resolve entity for identifier enemy-1.")
75
+ )
76
+ ).toBe(true);
77
+ });
78
+ });
@@ -0,0 +1,46 @@
1
+ const UNRESOLVED_ENTITY_MESSAGE = "unable to resolve entity";
2
+
3
+ export type ActionBattleDashEntity = {
4
+ id?: string;
5
+ getCurrentMap?: () => any;
6
+ dash?: (
7
+ direction: { x: number; y: number },
8
+ additionalSpeed?: number,
9
+ duration?: number
10
+ ) => unknown;
11
+ };
12
+
13
+ export const isActionBattleMovementResolutionError = (error: unknown): boolean => {
14
+ const message = error instanceof Error ? error.message : String(error ?? "");
15
+ return message.includes(UNRESOLVED_ENTITY_MESSAGE);
16
+ };
17
+
18
+ export const hasActionBattlePhysicsBody = (
19
+ entity: ActionBattleDashEntity | null | undefined
20
+ ): boolean => {
21
+ if (!entity) return false;
22
+ const map = entity.getCurrentMap?.();
23
+ if (!map) return false;
24
+ if (typeof map.getBody !== "function" || !entity.id) return true;
25
+ return Boolean(map.getBody(entity.id));
26
+ };
27
+
28
+ export const safeActionBattleDash = (
29
+ entity: ActionBattleDashEntity | null | undefined,
30
+ direction: { x: number; y: number },
31
+ additionalSpeed?: number,
32
+ duration?: number
33
+ ): boolean => {
34
+ if (!entity || typeof entity.dash !== "function") return false;
35
+ if (!hasActionBattlePhysicsBody(entity)) return false;
36
+
37
+ try {
38
+ entity.dash(direction, additionalSpeed, duration);
39
+ return true;
40
+ } catch (error) {
41
+ if (isActionBattleMovementResolutionError(error)) {
42
+ return false;
43
+ }
44
+ throw error;
45
+ }
46
+ };