@rpgjs/action-battle 5.0.0-alpha.32 → 5.0.0-alpha.35

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/README.md CHANGED
@@ -157,8 +157,9 @@ new BattleAi(event, {
157
157
  groupBehavior: true,
158
158
 
159
159
  // Callback when AI is defeated
160
- onDefeated: (event) => {
161
- console.log(`${event.name()} was defeated!`);
160
+ onDefeated: (event, attacker) => {
161
+ const name = attacker?.name?.() ?? "Unknown";
162
+ console.log(`${event.name()} was defeated by ${name}!`);
162
163
  }
163
164
  });
164
165
  ```
@@ -645,7 +646,7 @@ console.log(`Player knockback force: ${force}`);
645
646
 
646
647
  ## onDefeated Hook
647
648
 
648
- The `onDefeated` callback is triggered when an AI enemy is killed. Use it to:
649
+ The `onDefeated` callback is triggered when an AI enemy is killed. It receives the defeated event and the player who landed the killing blow (if available). Use it to:
649
650
  - Award experience, gold, or items to the player
650
651
  - Spawn loot drops
651
652
  - Trigger events or cutscenes
@@ -657,8 +658,9 @@ The `onDefeated` callback is triggered when an AI enemy is killed. Use it to:
657
658
  ```typescript
658
659
  new BattleAi(this, {
659
660
  enemyType: EnemyType.Aggressive,
660
- onDefeated: (event) => {
661
- console.log(`${event.name()} was defeated!`);
661
+ onDefeated: (event, attacker) => {
662
+ const name = attacker?.name?.() ?? "Unknown";
663
+ console.log(`${event.name()} was defeated by ${name}!`);
662
664
  }
663
665
  });
664
666
  ```
@@ -677,23 +679,19 @@ function Goblin() {
677
679
 
678
680
  new BattleAi(this, {
679
681
  enemyType: EnemyType.Aggressive,
680
- onDefeated: (event) => {
681
- // Find the player who killed this enemy
682
- const map = event.getCurrentMap();
683
- const players = map?.getPlayersIn() || [];
682
+ onDefeated: (event, attacker) => {
683
+ if (!attacker) return;
684
+
685
+ // Award gold
686
+ attacker.gold += 25;
684
687
 
685
- players.forEach(player => {
686
- // Award gold
687
- player.gold += 25;
688
-
689
- // Award experience
690
- player.exp += 50;
691
-
692
- // Random loot drop
693
- if (Math.random() < 0.3) {
694
- player.addItem(HealthPotion);
695
- }
696
- });
688
+ // Award experience
689
+ attacker.exp += 50;
690
+
691
+ // Random loot drop
692
+ if (Math.random() < 0.3) {
693
+ attacker.addItem(HealthPotion);
694
+ }
697
695
  }
698
696
  });
699
697
  }
@@ -705,7 +703,7 @@ function Goblin() {
705
703
 
706
704
  ```typescript
707
705
  new BattleAi(this, {
708
- onDefeated: (event) => {
706
+ onDefeated: (event, attacker) => {
709
707
  const map = event.getCurrentMap();
710
708
  if (!map) return;
711
709
 
@@ -725,7 +723,7 @@ new BattleAi(this, {
725
723
  let killCount = 0;
726
724
 
727
725
  new BattleAi(this, {
728
- onDefeated: (event) => {
726
+ onDefeated: (event, attacker) => {
729
727
  killCount++;
730
728
 
731
729
  // Check quest progress
@@ -749,7 +747,7 @@ function DragonBoss() {
749
747
 
750
748
  new BattleAi(this, {
751
749
  enemyType: EnemyType.Tank,
752
- onDefeated: (event) => {
750
+ onDefeated: (event, attacker) => {
753
751
  const map = event.getCurrentMap();
754
752
 
755
753
  // Announce victory
@@ -782,3 +780,93 @@ Automatic feedback:
782
780
  - **Damage Numbers**: Floating damage text
783
781
  - **Attack Animation**: Triggers `attack` animation
784
782
  - **Knockback**: Entities pushed back based on weapon `knockbackForce`
783
+
784
+ ## Action Bar + AoE Targeting (client + server)
785
+
786
+ The action-battle package includes optional GUI components for an A-RPG action bar
787
+ and AoE skill targeting. They are disabled by default and are configured via
788
+ `provideActionBattle()`.
789
+
790
+ ### Enable the Action Bar
791
+
792
+ ```ts
793
+ import { provideActionBattle } from "@rpgjs/action-battle";
794
+
795
+ export default provideActionBattle({
796
+ ui: {
797
+ actionBar: {
798
+ enabled: true,
799
+ autoOpen: true,
800
+ mode: "both" // "items" | "skills" | "both"
801
+ }
802
+ }
803
+ });
804
+ ```
805
+
806
+ You can open/close it manually on the server:
807
+
808
+ ```ts
809
+ import { openActionBattleActionBar } from "@rpgjs/action-battle/server";
810
+
811
+ openActionBattleActionBar(player);
812
+ ```
813
+
814
+ ### Skill Range + AoE Mask (ASCII)
815
+
816
+ Define range and AoE mask on the skill data (custom fields). The range uses
817
+ Manhattan distance, and the mask is centered on the target tile.
818
+
819
+ ```ts
820
+ @Skill({
821
+ name: "Nova",
822
+ spCost: 12,
823
+ // Custom fields used by action-battle
824
+ range: 3,
825
+ aoeMask: [
826
+ ".#.",
827
+ "###",
828
+ ".#."
829
+ ]
830
+ })
831
+ export class Nova {}
832
+ ```
833
+
834
+ ### Targeting Options
835
+
836
+ ```ts
837
+ export default provideActionBattle({
838
+ ui: {
839
+ actionBar: {
840
+ enabled: true,
841
+ autoOpen: false
842
+ },
843
+ targeting: {
844
+ enabled: true,
845
+ showGrid: true,
846
+ colors: {
847
+ area: 0x2f9ef7,
848
+ edge: 0x1b6a98,
849
+ cursor: 0xffd166
850
+ }
851
+ }
852
+ },
853
+ targeting: {
854
+ affects: "events", // "events" | "players" | "both"
855
+ allowEmptyTarget: true
856
+ }
857
+ });
858
+ ```
859
+
860
+ ### Custom Targeting Resolver (optional)
861
+
862
+ If you prefer to compute targeting from your own skill schema, use `getTargeting`:
863
+
864
+ ```ts
865
+ export default provideActionBattle({
866
+ skills: {
867
+ getTargeting(skill) {
868
+ return skill?.targeting;
869
+ }
870
+ }
871
+ });
872
+ ```
@@ -296,7 +296,7 @@ export declare class BattleAi {
296
296
  retreatThreshold?: number;
297
297
  };
298
298
  /** Callback called when the AI is defeated */
299
- onDefeated?: (event: RpgEvent) => void;
299
+ onDefeated?: (event: RpgEvent, attacker?: RpgPlayer) => void;
300
300
  });
301
301
  /**
302
302
  * Apply enemy type-specific behavior modifiers
@@ -1,17 +1,23 @@
1
- import client from "./index2.js";
1
+ import client, { createActionBattleClient } from "./index2.js";
2
2
  import { createModule } from "@rpgjs/common";
3
3
  import { AiDebug, AiState, AttackPattern, BattleAi, DEFAULT_KNOCKBACK, EnemyType } from "./index3.js";
4
- import { DEFAULT_PLAYER_ATTACK_HITBOXES, applyPlayerHitToEvent, getPlayerWeaponKnockbackForce } from "./index4.js";
4
+ import { ACTION_BATTLE_ACTION_BAR_GUI_ID, DEFAULT_PLAYER_ATTACK_HITBOXES, applyPlayerHitToEvent, createActionBattleServer, getPlayerWeaponKnockbackForce, openActionBattleActionBar, updateActionBattleActionBar } from "./index4.js";
5
5
  const server = null;
6
- function provideActionBattle() {
6
+ const createActionBattleServer2 = null;
7
+ function provideActionBattle(options = {}) {
7
8
  return createModule("ActionBattle", [
8
9
  {
9
- server,
10
- client
10
+ server: createActionBattleServer2?.(options),
11
+ client: createActionBattleClient?.(options)
11
12
  }
12
13
  ]);
13
14
  }
15
+ const index = {
16
+ server,
17
+ client
18
+ };
14
19
  export {
20
+ ACTION_BATTLE_ACTION_BAR_GUI_ID,
15
21
  AiDebug,
16
22
  AiState,
17
23
  AttackPattern,
@@ -20,6 +26,10 @@ export {
20
26
  DEFAULT_PLAYER_ATTACK_HITBOXES,
21
27
  EnemyType,
22
28
  applyPlayerHitToEvent,
29
+ createActionBattleServer,
30
+ index as default,
23
31
  getPlayerWeaponKnockbackForce,
24
- provideActionBattle
32
+ openActionBattleActionBar,
33
+ provideActionBattle,
34
+ updateActionBattleActionBar
25
35
  };
@@ -1,13 +1,45 @@
1
- import { PrebuiltComponentAnimations } from "@rpgjs/client";
1
+ import { PrebuiltComponentAnimations, inject, RpgGui, RpgClientEngine } from "@rpgjs/client";
2
2
  import { defineModule } from "@rpgjs/common";
3
- const client = defineModule({
4
- componentAnimations: [
5
- {
6
- id: "hit",
7
- component: PrebuiltComponentAnimations.Hit
3
+ import component$1 from "./index5.js";
4
+ import component from "./index6.js";
5
+ import { setActionBattleOptions } from "./index7.js";
6
+ import { normalizeActionBattleOptions } from "./index8.js";
7
+ const createActionBattleClient = (options = {}) => {
8
+ const normalized = normalizeActionBattleOptions(options);
9
+ setActionBattleOptions(normalized);
10
+ const actionBarEnabled = normalized.ui?.actionBar?.enabled;
11
+ const targetingEnabled = normalized.ui?.targeting?.enabled;
12
+ const hitComponent = PrebuiltComponentAnimations?.Hit;
13
+ return defineModule({
14
+ componentAnimations: hitComponent ? [
15
+ {
16
+ id: "hit",
17
+ component: hitComponent
18
+ }
19
+ ] : [],
20
+ gui: actionBarEnabled ? [
21
+ {
22
+ id: "action-battle-action-bar",
23
+ component: component$1,
24
+ dependencies: () => {
25
+ const engine = inject(RpgClientEngine);
26
+ return [engine.scene.currentPlayer];
27
+ }
28
+ }
29
+ ] : [],
30
+ sprite: {
31
+ componentsInFront: targetingEnabled ? [component] : []
32
+ },
33
+ sceneMap: {
34
+ onAfterLoading() {
35
+ const gui = inject(RpgGui);
36
+ gui.display("action-battle-action-bar");
37
+ }
8
38
  }
9
- ]
10
- });
39
+ });
40
+ };
41
+ const client = createActionBattleClient();
11
42
  export {
43
+ createActionBattleClient,
12
44
  client as default
13
45
  };
@@ -1122,7 +1122,7 @@ class BattleAi {
1122
1122
  }
1123
1123
  if (this.event.hp <= 0) {
1124
1124
  this.debugLog("damage", "Defeated!");
1125
- this.kill();
1125
+ this.kill(attacker);
1126
1126
  return true;
1127
1127
  }
1128
1128
  return false;
@@ -1133,9 +1133,9 @@ class BattleAi {
1133
1133
  * Stops all movements, cleans up resources, calls the onDefeated hook,
1134
1134
  * and removes the event from the map.
1135
1135
  */
1136
- kill() {
1136
+ kill(attacker) {
1137
1137
  if (this.onDefeatedCallback) {
1138
- this.onDefeatedCallback(this.event);
1138
+ this.onDefeatedCallback(this.event, attacker);
1139
1139
  }
1140
1140
  this.destroy();
1141
1141
  this.event.remove();
@@ -1,6 +1,9 @@
1
1
  import { defineModule, Control } from "@rpgjs/common";
2
+ import { normalizeActionBattleOptions } from "./index8.js";
3
+ import { manhattanDistance, parseAoeMask } from "./index9.js";
2
4
  const RpgEvent = null;
3
5
  const DEFAULT_KNOCKBACK = null;
6
+ const ACTION_BATTLE_ACTION_BAR_GUI_ID = "action-battle-action-bar";
4
7
  const DEFAULT_PLAYER_ATTACK_HITBOXES = {
5
8
  up: { offsetX: -16, offsetY: -48, width: 32, height: 32 },
6
9
  down: { offsetX: -16, offsetY: 16, width: 32, height: 32 },
@@ -56,82 +59,275 @@ function applyPlayerHitToEvent(player, target, hooks) {
56
59
  }
57
60
  return hitResult;
58
61
  }
59
- defineModule({
60
- player: {
61
- /**
62
- * Handle player input for combat actions
63
- *
64
- * When a player presses the action key, create an attack hitbox
65
- * that can damage AI enemies within range and knockback the event.
66
- * Knockback force is based on the player's equipped weapon.
67
- * Triggers attack animation and visual effects.
68
- *
69
- * @param player - The player performing the action
70
- * @param input - Input data containing pressed keys
71
- */
72
- onInput(player, input) {
73
- if (input.action == Control.Action) {
74
- player.setGraphicAnimation("attack", 1);
75
- const playerX = player.x();
76
- const playerY = player.y();
77
- const direction = player.getDirection();
78
- const directionKey = direction;
79
- const hitboxConfig = DEFAULT_PLAYER_ATTACK_HITBOXES[directionKey] || DEFAULT_PLAYER_ATTACK_HITBOXES.default;
80
- const hitboxes = [{
81
- x: playerX + hitboxConfig.offsetX,
82
- y: playerY + hitboxConfig.offsetY,
83
- width: hitboxConfig.width,
84
- height: hitboxConfig.height
85
- }];
86
- const map = player.getCurrentMap();
87
- map?.createMovingHitbox(hitboxes, { speed: 3 }).subscribe({
88
- next(hits) {
89
- hits.forEach((hit) => {
90
- if (hit instanceof RpgEvent) {
91
- const result = applyPlayerHitToEvent(player, hit);
92
- if (result?.defeated) {
93
- console.log(`Player ${player.id} defeated AI ${hit.id}`);
94
- }
95
- }
96
- });
97
- }
98
- });
62
+ const resolveSignal = (value) => typeof value === "function" ? value() : value;
63
+ const resolveItemData = (player, itemId) => {
64
+ try {
65
+ return player.databaseById?.(itemId);
66
+ } catch {
67
+ return null;
68
+ }
69
+ };
70
+ const resolveSkillData = (player, skillId) => {
71
+ try {
72
+ return player.databaseById?.(skillId);
73
+ } catch {
74
+ return null;
75
+ }
76
+ };
77
+ const resolveSkillTargeting = (player, skillId, options) => {
78
+ const skillsOptions = options.skills;
79
+ const skillData = resolveSkillData(player, skillId);
80
+ if (skillsOptions?.getTargeting) {
81
+ return skillsOptions.getTargeting(skillData);
82
+ }
83
+ const range = skillData?.range ?? skillData?.targeting?.range ?? skillData?.targeting?.distance;
84
+ const aoeMask = skillData?.aoeMask ?? skillData?.targeting?.aoeMask ?? skillData?.targeting?.mask;
85
+ if (range === void 0 && aoeMask === void 0) {
86
+ return null;
87
+ }
88
+ return {
89
+ range: range ?? 0,
90
+ aoeMask
91
+ };
92
+ };
93
+ const normalizeMaskRows = (mask) => {
94
+ if (!mask) return [];
95
+ if (Array.isArray(mask)) return mask;
96
+ return mask.trim().split("\n").map((row) => row.replace(/\r/g, ""));
97
+ };
98
+ const buildActionBarData = (player, options) => {
99
+ const items = (player.items?.() || []).map((item) => {
100
+ const id = item.id?.() ?? item.id;
101
+ const data = resolveItemData(player, id);
102
+ const name = resolveSignal(data?.name) ?? resolveSignal(item.name) ?? id;
103
+ const description = resolveSignal(data?.description) ?? resolveSignal(item.description) ?? "";
104
+ const icon = resolveSignal(data?.icon) ?? resolveSignal(item.icon);
105
+ const quantity = resolveSignal(item.quantity) ?? 1;
106
+ const consumable = resolveSignal(data?.consumable);
107
+ const itemType = resolveSignal(data?._type);
108
+ const usable = quantity > 0 && consumable !== false && (itemType ? itemType === "item" : true);
109
+ return {
110
+ id,
111
+ name,
112
+ description,
113
+ icon,
114
+ quantity,
115
+ usable
116
+ };
117
+ });
118
+ const skills = (player.skills?.() || []).map((skill) => {
119
+ const id = skill.id?.() ?? skill.id;
120
+ const data = resolveSkillData(player, id) || skill;
121
+ const name = resolveSignal(data?.name) ?? resolveSignal(skill.name) ?? id;
122
+ const description = resolveSignal(data?.description) ?? resolveSignal(skill.description) ?? "";
123
+ const icon = resolveSignal(data?.icon) ?? resolveSignal(skill.icon);
124
+ const spCost = resolveSignal(data?.spCost) ?? resolveSignal(skill.spCost) ?? 0;
125
+ const usable = spCost <= player.sp;
126
+ const targeting = resolveSkillTargeting(player, id, options);
127
+ const skillEntry = {
128
+ id,
129
+ name,
130
+ description,
131
+ icon,
132
+ spCost,
133
+ usable,
134
+ range: targeting?.range ?? 0
135
+ };
136
+ if (targeting) {
137
+ const mask = targeting.aoeMask ?? options.skills?.defaultAoeMask;
138
+ if (mask) {
139
+ skillEntry.aoeMask = normalizeMaskRows(mask);
99
140
  }
100
141
  }
101
- },
102
- event: {
103
- /**
104
- * Handle player detection when entering AI vision
105
- *
106
- * Called when a player enters an AI event's vision range.
107
- * The AI will start pursuing and attacking the player.
108
- *
109
- * @param event - The AI event
110
- * @param player - The player entering vision
111
- * @param shape - The vision shape
112
- */
113
- onDetectInShape(event, player, shape) {
114
- const ai = event.battleAi;
115
- ai?.onDetectInShape(player, shape);
116
- },
117
- /**
118
- * Handle player leaving AI vision
119
- *
120
- * Called when a player leaves an AI event's vision range.
121
- * The AI will stop pursuing the player.
122
- *
123
- * @param event - The AI event
124
- * @param player - The player leaving vision
125
- * @param shape - The vision shape
126
- */
127
- onDetectOutShape(event, player, shape) {
128
- const ai = event.battleAi;
129
- ai?.onDetectOutShape(player, shape);
130
- }
142
+ return skillEntry;
143
+ });
144
+ return { items, skills };
145
+ };
146
+ const ensureActionBarGui = (player, options) => {
147
+ const existing = player.getGui?.(ACTION_BATTLE_ACTION_BAR_GUI_ID);
148
+ const gui = existing || player.gui(ACTION_BATTLE_ACTION_BAR_GUI_ID);
149
+ if (!gui.__actionBattleReady) {
150
+ gui.__actionBattleReady = true;
151
+ gui.on("useItem", ({ id }) => {
152
+ try {
153
+ player.useItem(id);
154
+ } catch {
155
+ }
156
+ gui.update(buildActionBarData(player, options));
157
+ });
158
+ gui.on("useSkill", ({ id, target }) => {
159
+ handleActionBattleSkillUse(player, id, target, options);
160
+ gui.update(buildActionBarData(player, options));
161
+ });
162
+ gui.on("refresh", () => {
163
+ gui.update(buildActionBarData(player, options));
164
+ });
131
165
  }
166
+ return gui;
167
+ };
168
+ const openActionBattleActionBar = (player, rawOptions = {}) => {
169
+ const options = normalizeActionBattleOptions(rawOptions);
170
+ const gui = ensureActionBarGui(player, options);
171
+ gui.open(buildActionBarData(player, options));
172
+ };
173
+ const updateActionBattleActionBar = (player, rawOptions = {}) => {
174
+ const options = normalizeActionBattleOptions(rawOptions);
175
+ const gui = player.getGui?.(ACTION_BATTLE_ACTION_BAR_GUI_ID);
176
+ if (gui) {
177
+ gui.update(buildActionBarData(player, options));
178
+ }
179
+ };
180
+ const getTileSize = (map) => ({
181
+ width: map?.tileWidth ?? 32,
182
+ height: map?.tileHeight ?? 32
132
183
  });
184
+ const getEntityTile = (entity, tileSize) => {
185
+ const hitbox = entity.hitbox?.() || { w: tileSize.width, h: tileSize.height };
186
+ const x = Math.floor((entity.x() + hitbox.w / 2) / tileSize.width);
187
+ const y = Math.floor((entity.y() + hitbox.h / 2) / tileSize.height);
188
+ return { x, y };
189
+ };
190
+ const handleActionBattleSkillUse = (player, skillId, target, options) => {
191
+ const map = player.getCurrentMap();
192
+ if (!map) {
193
+ player.useSkill(skillId);
194
+ return;
195
+ }
196
+ const targeting = resolveSkillTargeting(player, skillId, options);
197
+ if (!targeting || !target) {
198
+ player.useSkill(skillId);
199
+ return;
200
+ }
201
+ const tileSize = getTileSize(map);
202
+ const origin = getEntityTile(player, tileSize);
203
+ const targetTile = { x: target.x, y: target.y };
204
+ if (manhattanDistance(origin, targetTile) > targeting.range) {
205
+ return;
206
+ }
207
+ const mask = parseAoeMask(
208
+ targeting.aoeMask || options.skills?.defaultAoeMask
209
+ );
210
+ const affected = /* @__PURE__ */ new Set();
211
+ mask.cells.forEach((cell) => {
212
+ const x = targetTile.x + cell.dx;
213
+ const y = targetTile.y + cell.dy;
214
+ affected.add(`${x},${y}`);
215
+ });
216
+ const targets = [];
217
+ const affects = options.targeting?.affects || "events";
218
+ if (affects === "events" || affects === "both") {
219
+ map.getEvents().forEach((event) => {
220
+ const tile = getEntityTile(event, tileSize);
221
+ if (affected.has(`${tile.x},${tile.y}`)) {
222
+ targets.push(event);
223
+ }
224
+ });
225
+ }
226
+ if (affects === "players" || affects === "both") {
227
+ map.getPlayers().forEach((other) => {
228
+ if (other.id === player.id) return;
229
+ const tile = getEntityTile(other, tileSize);
230
+ if (affected.has(`${tile.x},${tile.y}`)) {
231
+ targets.push(other);
232
+ }
233
+ });
234
+ }
235
+ if (!options.targeting?.allowEmptyTarget && targets.length === 0) {
236
+ return;
237
+ }
238
+ player.useSkill(skillId, targets);
239
+ };
240
+ const createActionBattleServer = (rawOptions = {}) => {
241
+ const options = normalizeActionBattleOptions(rawOptions);
242
+ return defineModule({
243
+ player: {
244
+ /**
245
+ * Handle player input for combat actions
246
+ *
247
+ * When a player presses the action key, create an attack hitbox
248
+ * that can damage AI enemies within range and knockback the event.
249
+ * Knockback force is based on the player's equipped weapon.
250
+ * Triggers attack animation and visual effects.
251
+ *
252
+ * @param player - The player performing the action
253
+ * @param input - Input data containing pressed keys
254
+ */
255
+ onInput(player, input) {
256
+ if (input.action == Control.Action) {
257
+ player.setGraphicAnimation("attack", 1);
258
+ const playerX = player.x();
259
+ const playerY = player.y();
260
+ const direction = player.getDirection();
261
+ const directionKey = direction;
262
+ const hitboxConfig = DEFAULT_PLAYER_ATTACK_HITBOXES[directionKey] || DEFAULT_PLAYER_ATTACK_HITBOXES.default;
263
+ const hitboxes = [
264
+ {
265
+ x: playerX + hitboxConfig.offsetX,
266
+ y: playerY + hitboxConfig.offsetY,
267
+ width: hitboxConfig.width,
268
+ height: hitboxConfig.height
269
+ }
270
+ ];
271
+ const map = player.getCurrentMap();
272
+ map?.createMovingHitbox(hitboxes, { speed: 3 }).subscribe({
273
+ next(hits) {
274
+ hits.forEach((hit) => {
275
+ if (hit instanceof RpgEvent) {
276
+ const result = applyPlayerHitToEvent(player, hit);
277
+ if (result?.defeated) {
278
+ console.log(`Player ${player.id} defeated AI ${hit.id}`);
279
+ }
280
+ }
281
+ });
282
+ }
283
+ });
284
+ }
285
+ },
286
+ onConnected(player) {
287
+ if (options.ui?.actionBar?.enabled && options.ui?.actionBar?.autoOpen) {
288
+ openActionBattleActionBar(player, options);
289
+ }
290
+ }
291
+ },
292
+ event: {
293
+ /**
294
+ * Handle player detection when entering AI vision
295
+ *
296
+ * Called when a player enters an AI event's vision range.
297
+ * The AI will start pursuing and attacking the player.
298
+ *
299
+ * @param event - The AI event
300
+ * @param player - The player entering vision
301
+ * @param shape - The vision shape
302
+ */
303
+ onDetectInShape(event, player, shape) {
304
+ const ai = event.battleAi;
305
+ ai?.onDetectInShape(player, shape);
306
+ },
307
+ /**
308
+ * Handle player leaving AI vision
309
+ *
310
+ * Called when a player leaves an AI event's vision range.
311
+ * The AI will stop pursuing the player.
312
+ *
313
+ * @param event - The AI event
314
+ * @param player - The player leaving vision
315
+ * @param shape - The vision shape
316
+ */
317
+ onDetectOutShape(event, player, shape) {
318
+ const ai = event.battleAi;
319
+ ai?.onDetectOutShape(player, shape);
320
+ }
321
+ }
322
+ });
323
+ };
324
+ createActionBattleServer();
133
325
  export {
326
+ ACTION_BATTLE_ACTION_BAR_GUI_ID,
134
327
  DEFAULT_PLAYER_ATTACK_HITBOXES,
135
328
  applyPlayerHitToEvent,
136
- getPlayerWeaponKnockbackForce
329
+ createActionBattleServer,
330
+ getPlayerWeaponKnockbackForce,
331
+ openActionBattleActionBar,
332
+ updateActionBattleActionBar
137
333
  };