@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.
Files changed (111) hide show
  1. package/CHANGELOG.md +22 -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 +10 -10
  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/components/action-bar.ce +2 -2
  86. package/src/config.ts +84 -37
  87. package/src/core/action-use.spec.ts +317 -0
  88. package/src/core/action-use.ts +386 -0
  89. package/src/core/ai-behavior-tree.spec.ts +116 -0
  90. package/src/core/ai-behavior-tree.ts +272 -0
  91. package/src/core/attack-profile.spec.ts +46 -0
  92. package/src/core/attack-runtime.spec.ts +35 -0
  93. package/src/core/attack-runtime.ts +32 -0
  94. package/src/core/context.ts +9 -0
  95. package/src/core/contracts.ts +146 -1
  96. package/src/core/defaults.ts +56 -0
  97. package/src/core/equipment.ts +9 -5
  98. package/src/core/targets.spec.ts +112 -0
  99. package/src/core/targets.ts +147 -0
  100. package/src/enemies/factory.ts +8 -0
  101. package/src/index.ts +111 -2
  102. package/src/locomotion.spec.ts +51 -0
  103. package/src/locomotion.ts +48 -0
  104. package/src/movement.spec.ts +78 -0
  105. package/src/movement.ts +46 -0
  106. package/src/server.ts +242 -66
  107. package/src/types.ts +105 -35
  108. package/src/ui.ts +113 -0
  109. package/src/visual.spec.ts +166 -0
  110. package/src/visual.ts +285 -0
  111. package/README.md +0 -1242
@@ -0,0 +1,12 @@
1
+ //#region src/enemies/factory.ts
2
+ var BattleAi = null;
3
+ var defineActionBattleEnemy = (preset) => preset;
4
+ var defineActionBattleAiPreset = (preset) => preset;
5
+ var createActionEnemy = (event, presetOrOptions, presets = {}) => {
6
+ const options = typeof presetOrOptions === "string" ? presets[presetOrOptions] : presetOrOptions;
7
+ if (!options) throw new Error(`Action battle enemy preset not found: ${presetOrOptions}`);
8
+ options.stats?.(event);
9
+ return new BattleAi(event, options);
10
+ };
11
+ //#endregion
12
+ export { createActionEnemy, defineActionBattleAiPreset, defineActionBattleEnemy };
@@ -0,0 +1,589 @@
1
+ import "./index2.js";
2
+ import { normalizeActionBattleAttackProfile } from "./index3.js";
3
+ import { normalizeActionBattleOptions, setActionBattleOptions } from "./index4.js";
4
+ import { ActionBattleHitTracker, createActionBattleAttackId, getNormalizedActionBattleAttackProfile, runActionBattleActiveHitbox } from "./index6.js";
5
+ import { applyActionBattleAttackDirection, resolveActionBattleAttackDirection } from "./index7.js";
6
+ import { forceActionBattleLocomotionAnimation, withActionBattleAnimationUnlocked } from "./index8.js";
7
+ import { manhattanDistance, parseAoeMask } from "./index10.js";
8
+ import { emitActionBattleClientVisual } from "./index15.js";
9
+ import { DEFAULT_ZELDA_PLAYER_HITBOXES } from "./index17.js";
10
+ import { getActionBattleSystems, setActionBattleSystems } from "./index18.js";
11
+ import "./index19.js";
12
+ import { applyActionBattleHit } from "./index20.js";
13
+ import { canActionBattleTarget, getActionBattleTargets } from "./index21.js";
14
+ import { canActionBattleUseTarget, executeActionBattleUse, getActionBattleActionConfig, handleActionBattleProjectileDestroy, handleActionBattleProjectileImpact } from "./index22.js";
15
+ import { resolveActionBattleWeapon, resolveActionBattleWeaponAttackProfile } from "./index23.js";
16
+ import "./index26.js";
17
+ import { Control, defineModule } from "@rpgjs/common";
18
+ //#region src/server.ts
19
+ var RpgEvent = null;
20
+ var DEFAULT_KNOCKBACK = null;
21
+ var ACTION_BATTLE_ACTION_BAR_GUI_ID = "action-battle-action-bar";
22
+ var DEFAULT_ATTACK_LOCK_DURATION_MS = 350;
23
+ /**
24
+ * Default player attack hitboxes offsets for each direction
25
+ *
26
+ * These hitboxes define the attack areas relative to the player's position
27
+ * for each cardinal direction. They are converted to absolute coordinates
28
+ * when creating the moving hitbox.
29
+ */
30
+ var DEFAULT_PLAYER_ATTACK_HITBOXES = { ...DEFAULT_ZELDA_PLAYER_HITBOXES };
31
+ var beginPlayerAttackLock = (player, map, durationMs, locks) => {
32
+ if (durationMs <= 0) return true;
33
+ const runtimePlayer = player;
34
+ const now = Date.now();
35
+ if (typeof runtimePlayer.__actionBattleAttackLockedUntil === "number" && runtimePlayer.__actionBattleAttackLockedUntil > now) return false;
36
+ const lockId = (runtimePlayer.__actionBattleAttackLockId ?? 0) + 1;
37
+ runtimePlayer.__actionBattleAttackLockId = lockId;
38
+ runtimePlayer.__actionBattleAttackLockedUntil = now + durationMs;
39
+ const previousCanMove = player.canMove;
40
+ const previousDirectionFixed = player.directionFixed;
41
+ const previousAnimationFixed = player.animationFixed;
42
+ if (locks.movement) {
43
+ player.pendingInputs = [];
44
+ player.lastProcessedInputTs = 0;
45
+ map?.stopMovement?.(player);
46
+ player.canMove = false;
47
+ }
48
+ if (locks.direction) player.directionFixed = true;
49
+ setTimeout(() => {
50
+ if (runtimePlayer.__actionBattleAttackLockId !== lockId) return;
51
+ runtimePlayer.__actionBattleAttackLockedUntil = 0;
52
+ player.canMove = previousCanMove;
53
+ player.directionFixed = previousDirectionFixed;
54
+ player.animationFixed = previousAnimationFixed;
55
+ if (locks.movement && !previousAnimationFixed) forceActionBattleLocomotionAnimation(player, "stand");
56
+ }, durationMs);
57
+ return true;
58
+ };
59
+ var isBattleEvent = (event) => !!event.battleAi;
60
+ var rectsOverlap = (a, b) => a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
61
+ var entityRect = (entity) => {
62
+ const hitbox = typeof entity.hitbox === "function" ? entity.hitbox() : entity.hitbox;
63
+ return {
64
+ x: entity.x(),
65
+ y: entity.y(),
66
+ width: hitbox?.w ?? 32,
67
+ height: hitbox?.h ?? 32
68
+ };
69
+ };
70
+ var getVisibleActionEvents = (player, map, hitboxes) => {
71
+ if (!map) return [];
72
+ const eventsById = /* @__PURE__ */ new Map();
73
+ const addEvent = (event) => {
74
+ if (!event) return;
75
+ if (!(typeof map.isEventVisibleForPlayer === "function" ? map.isEventVisibleForPlayer(event, player) : true)) return;
76
+ eventsById.set(event.id, event);
77
+ };
78
+ const collisions = map.getCollisions?.(player.id);
79
+ if (Array.isArray(collisions)) collisions.forEach((id) => addEvent(map.getEvent(id)));
80
+ const direction = typeof player.getDirection === "function" ? player.getDirection() : void 0;
81
+ const interactionCollisions = map.getInteractionCollisions?.(player.id, direction);
82
+ if (Array.isArray(interactionCollisions)) interactionCollisions.forEach((id) => addEvent(map.getEvent(id)));
83
+ for (const event of map.getEvents()) {
84
+ const rect = entityRect(event);
85
+ if (hitboxes.some((hitbox) => rectsOverlap(hitbox, rect))) addEvent(event);
86
+ }
87
+ return Array.from(eventsById.values());
88
+ };
89
+ var isActionReservedForNormalEvent = (player, map, hitboxes) => {
90
+ const events = getVisibleActionEvents(player, map, hitboxes);
91
+ return events.length > 0 && !events.some(isBattleEvent);
92
+ };
93
+ /**
94
+ * Get knockback force from player's equipped weapon
95
+ *
96
+ * Retrieves the knockbackForce property from the player's equipped weapon.
97
+ * Falls back to DEFAULT_KNOCKBACK.force if no weapon or property is set.
98
+ *
99
+ * @param player - The player to get weapon knockback from
100
+ * @returns Knockback force value
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * // Player with weapon having knockbackForce: 80
105
+ * const force = getPlayerWeaponKnockbackForce(player); // 80
106
+ *
107
+ * // No weapon equipped
108
+ * const force = getPlayerWeaponKnockbackForce(player); // 50 (default)
109
+ * ```
110
+ */
111
+ function getPlayerWeaponKnockbackForce(player) {
112
+ try {
113
+ const equipments = player.equipments?.() || [];
114
+ for (const item of equipments) {
115
+ const itemData = player.databaseById?.(item.id());
116
+ if (itemData?._type === "weapon" && itemData.knockbackForce !== void 0) return itemData.knockbackForce;
117
+ }
118
+ } catch {}
119
+ return DEFAULT_KNOCKBACK.force;
120
+ }
121
+ /**
122
+ * Apply hit from player to target (event with AI)
123
+ *
124
+ * Handles damage calculation, knockback based on weapon, and visual effects.
125
+ * Can be customized using hooks.
126
+ *
127
+ * @param player - The attacking player
128
+ * @param target - The event being hit
129
+ * @param hooks - Optional hooks for customizing hit behavior
130
+ * @returns Hit result if AI exists, undefined otherwise
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * // Basic hit
135
+ * const result = applyPlayerHitToEvent(player, event);
136
+ *
137
+ * // With custom hooks
138
+ * const result = applyPlayerHitToEvent(player, event, {
139
+ * onBeforeHit(result) {
140
+ * result.knockbackForce *= 2; // Double knockback
141
+ * return result;
142
+ * },
143
+ * onAfterHit(result) {
144
+ * if (result.defeated) {
145
+ * player.gold += 10;
146
+ * }
147
+ * }
148
+ * });
149
+ * ```
150
+ */
151
+ function applyPlayerHitToEvent(player, target, hooks, metadata) {
152
+ if (!target.battleAi) return void 0;
153
+ return applyActionBattleEntityHit(player, target, hooks, metadata);
154
+ }
155
+ function applyActionBattleEntityHit(attacker, target, hooks, metadata) {
156
+ const ai = target.battleAi;
157
+ if (target instanceof RpgEvent && !ai) return void 0;
158
+ const systems = getActionBattleSystems();
159
+ const result = applyActionBattleHit({
160
+ ...systems.combat,
161
+ hooks: hooks ? {
162
+ ...systems.combat.hooks,
163
+ beforeHit(context) {
164
+ const before = systems.combat.hooks?.beforeHit?.(context);
165
+ if (before === false) return false;
166
+ const nextContext = before || context;
167
+ const legacyResult = toLegacyHitResult(nextContext);
168
+ const modified = hooks.onBeforeHit?.(legacyResult);
169
+ if (!modified) return nextContext;
170
+ return {
171
+ ...nextContext,
172
+ damage: {
173
+ damage: modified.damage,
174
+ defeated: modified.defeated,
175
+ raw: nextContext.damage?.raw
176
+ },
177
+ knockback: {
178
+ force: modified.knockbackForce,
179
+ duration: modified.knockbackDuration,
180
+ direction: nextContext.knockback?.direction
181
+ }
182
+ };
183
+ },
184
+ afterHit(result) {
185
+ systems.combat.hooks?.afterHit?.(result);
186
+ hooks.onAfterHit?.(result);
187
+ }
188
+ } : systems.combat.hooks
189
+ }, {
190
+ attacker,
191
+ target,
192
+ metadata,
193
+ reaction: metadata?.reaction
194
+ });
195
+ if (!result.cancelled && ai) ai.handleDamage(attacker, {
196
+ damage: result.damage,
197
+ defeated: result.defeated,
198
+ raw: result.rawDamage,
199
+ reaction: result.reaction
200
+ });
201
+ return result;
202
+ }
203
+ var toLegacyHitResult = (context) => ({
204
+ damage: context.damage?.damage ?? 0,
205
+ knockbackForce: context.knockback?.force ?? getPlayerWeaponKnockbackForce(context.attacker),
206
+ knockbackDuration: context.knockback?.duration ?? DEFAULT_KNOCKBACK.duration,
207
+ defeated: context.damage?.defeated ?? false,
208
+ attacker: context.attacker,
209
+ target: context.target
210
+ });
211
+ var resolvePlayerAttackHitboxes = (player, directionKey, options, profile) => {
212
+ const configuredHitboxes = {
213
+ ...DEFAULT_PLAYER_ATTACK_HITBOXES,
214
+ ...options.attack?.hitboxes,
215
+ ...profile.hitboxes
216
+ };
217
+ const hitboxConfig = configuredHitboxes[directionKey] || configuredHitboxes.default;
218
+ const defaultHitboxes = [{
219
+ x: player.x() + hitboxConfig.offsetX,
220
+ y: player.y() + hitboxConfig.offsetY,
221
+ width: hitboxConfig.width,
222
+ height: hitboxConfig.height
223
+ }];
224
+ return options.attack?.resolveHitboxes?.({
225
+ player,
226
+ direction: directionKey,
227
+ defaultHitboxes
228
+ }) ?? defaultHitboxes;
229
+ };
230
+ var getActionBattleHitboxCandidates = (map, hitboxes, options = {}) => {
231
+ if (!map) return [];
232
+ if (typeof map.queryHitbox === "function") {
233
+ const candidates = /* @__PURE__ */ new Map();
234
+ for (const hitbox of hitboxes) for (const entity of map.queryHitbox(hitbox, options)) if (entity?.id) candidates.set(entity.id, entity);
235
+ return Array.from(candidates.values());
236
+ }
237
+ const candidates = /* @__PURE__ */ new Map();
238
+ const excluded = new Set(options.excludeIds ?? []);
239
+ const add = (entity) => {
240
+ if (!entity?.id) return;
241
+ if (excluded.has(entity.id)) return;
242
+ const rect = entityRect(entity);
243
+ if (hitboxes.some((hitbox) => rectsOverlap(hitbox, rect))) candidates.set(entity.id, entity);
244
+ };
245
+ const kinds = new Set(options.kinds ?? ["players", "events"]);
246
+ if (kinds.has("players")) map.getPlayers?.().forEach(add);
247
+ if (kinds.has("events")) map.getEvents?.().forEach(add);
248
+ return Array.from(candidates.values());
249
+ };
250
+ var mergeAttackProfileOverrides = (base, override) => ({
251
+ ...base,
252
+ ...override,
253
+ reaction: {
254
+ ...base.reaction,
255
+ ...override.reaction
256
+ },
257
+ hitboxes: {
258
+ ...base.hitboxes,
259
+ ...override.hitboxes
260
+ }
261
+ });
262
+ var resolvePlayerAttackProfile = (player, options) => {
263
+ const baseProfile = getNormalizedActionBattleAttackProfile(options);
264
+ const weaponProfile = resolveActionBattleWeaponAttackProfile(player);
265
+ if (!weaponProfile) return baseProfile;
266
+ return normalizeActionBattleAttackProfile(mergeAttackProfileOverrides(baseProfile, weaponProfile), {
267
+ lockMovement: options.attack?.lockMovement,
268
+ lockDurationMs: options.attack?.lockDurationMs,
269
+ hitboxes: options.attack?.hitboxes
270
+ });
271
+ };
272
+ var resolveSignal = (value) => typeof value === "function" ? value() : value;
273
+ var resolveItemData = (player, itemId) => {
274
+ try {
275
+ return player.databaseById?.(itemId);
276
+ } catch {
277
+ return null;
278
+ }
279
+ };
280
+ var resolveSkillData = (player, skillId) => {
281
+ try {
282
+ return player.databaseById?.(skillId);
283
+ } catch {
284
+ return null;
285
+ }
286
+ };
287
+ var resolvePlayerSkillUsable = (player, skillId) => {
288
+ try {
289
+ return player.getSkill?.(skillId) ?? resolveSkillData(player, skillId);
290
+ } catch {
291
+ return resolveSkillData(player, skillId);
292
+ }
293
+ };
294
+ var resolveSkillTargeting = (player, skillId, options) => {
295
+ const skillsOptions = options.skills;
296
+ const skillData = resolveSkillData(player, skillId);
297
+ if (skillsOptions?.getTargeting) return skillsOptions.getTargeting(skillData);
298
+ const range = skillData?.range ?? skillData?.targeting?.range ?? skillData?.targeting?.distance;
299
+ const aoeMask = skillData?.aoeMask ?? skillData?.targeting?.aoeMask ?? skillData?.targeting?.mask;
300
+ if (range === void 0 && aoeMask === void 0) return null;
301
+ return {
302
+ range: range ?? 0,
303
+ aoeMask
304
+ };
305
+ };
306
+ var normalizeMaskRows = (mask) => {
307
+ if (!mask) return [];
308
+ if (Array.isArray(mask)) return mask;
309
+ return mask.trim().split("\n").map((row) => row.replace(/\r/g, ""));
310
+ };
311
+ var buildActionBarData = (player, options) => {
312
+ return {
313
+ items: (player.items?.() || []).map((item) => {
314
+ const id = item.id?.() ?? item.id;
315
+ const data = resolveItemData(player, id);
316
+ const name = resolveSignal(data?.name) ?? resolveSignal(item.name) ?? id;
317
+ const description = resolveSignal(data?.description) ?? resolveSignal(item.description) ?? "";
318
+ const icon = resolveSignal(data?.icon) ?? resolveSignal(item.icon);
319
+ const quantity = resolveSignal(item.quantity) ?? 1;
320
+ const consumable = resolveSignal(data?.consumable);
321
+ const itemType = resolveSignal(data?._type);
322
+ return {
323
+ id,
324
+ name,
325
+ description,
326
+ icon,
327
+ quantity,
328
+ usable: quantity > 0 && consumable !== false && (itemType ? itemType === "item" : true)
329
+ };
330
+ }),
331
+ skills: (player.skills?.() || []).map((skill) => {
332
+ const id = skill.id?.() ?? skill.id;
333
+ const data = resolveSkillData(player, id) || skill;
334
+ const name = resolveSignal(data?.name) ?? resolveSignal(skill.name) ?? id;
335
+ const description = resolveSignal(data?.description) ?? resolveSignal(skill.description) ?? "";
336
+ const icon = resolveSignal(data?.icon) ?? resolveSignal(skill.icon);
337
+ const spCost = resolveSignal(data?.spCost) ?? resolveSignal(skill.spCost) ?? 0;
338
+ const usable = spCost <= player.sp;
339
+ const targeting = resolveSkillTargeting(player, id, options);
340
+ const skillEntry = {
341
+ id,
342
+ name,
343
+ description,
344
+ icon,
345
+ spCost,
346
+ usable,
347
+ range: targeting?.range ?? 0
348
+ };
349
+ if (targeting) {
350
+ const mask = targeting.aoeMask ?? options.skills?.defaultAoeMask;
351
+ if (mask) skillEntry.aoeMask = normalizeMaskRows(mask);
352
+ }
353
+ return skillEntry;
354
+ })
355
+ };
356
+ };
357
+ var ensureActionBarGui = (player, options) => {
358
+ const gui = player.getGui?.("action-battle-action-bar") || player.gui("action-battle-action-bar");
359
+ if (!gui.__actionBattleReady) {
360
+ gui.__actionBattleReady = true;
361
+ gui.on("useItem", ({ id }) => {
362
+ try {
363
+ player.useItem(id);
364
+ } catch {}
365
+ gui.update(buildActionBarData(player, options));
366
+ });
367
+ gui.on("useSkill", ({ id, target }) => {
368
+ handleActionBattleSkillUse(player, id, target, options);
369
+ gui.update(buildActionBarData(player, options));
370
+ });
371
+ gui.on("refresh", () => {
372
+ gui.update(buildActionBarData(player, options));
373
+ });
374
+ }
375
+ return gui;
376
+ };
377
+ var openActionBattleActionBar = (player, rawOptions = {}) => {
378
+ const options = normalizeActionBattleOptions(rawOptions);
379
+ ensureActionBarGui(player, options).open(buildActionBarData(player, options));
380
+ };
381
+ var updateActionBattleActionBar = (player, rawOptions = {}) => {
382
+ const options = normalizeActionBattleOptions(rawOptions);
383
+ const gui = player.getGui?.(ACTION_BATTLE_ACTION_BAR_GUI_ID);
384
+ if (gui) gui.update(buildActionBarData(player, options));
385
+ };
386
+ var getTileSize = (map) => ({
387
+ width: map?.tileWidth ?? 32,
388
+ height: map?.tileHeight ?? 32
389
+ });
390
+ var getEntityTile = (entity, tileSize) => {
391
+ const hitbox = entity.hitbox?.() || {
392
+ w: tileSize.width,
393
+ h: tileSize.height
394
+ };
395
+ return {
396
+ x: Math.floor((entity.x() + hitbox.w / 2) / tileSize.width),
397
+ y: Math.floor((entity.y() + hitbox.h / 2) / tileSize.height)
398
+ };
399
+ };
400
+ var handleActionBattleSkillUse = (player, skillId, target, options) => {
401
+ const skillData = resolvePlayerSkillUsable(player, skillId);
402
+ const actionConfig = getActionBattleActionConfig(skillData);
403
+ if (actionConfig?.target === "self") {
404
+ executeActionBattleUse({
405
+ attacker: player,
406
+ target: player,
407
+ usable: skillData,
408
+ skill: skillData
409
+ });
410
+ return;
411
+ }
412
+ const map = player.getCurrentMap();
413
+ if (!map) {
414
+ emitActionBattleClientVisual({
415
+ moment: "castSkill",
416
+ entity: player,
417
+ skill: skillData
418
+ });
419
+ player.useSkill(skillId);
420
+ return;
421
+ }
422
+ const targeting = resolveSkillTargeting(player, skillId, options);
423
+ if (!targeting || !target) {
424
+ emitActionBattleClientVisual({
425
+ moment: "castSkill",
426
+ entity: player,
427
+ skill: skillData
428
+ });
429
+ player.useSkill(skillId);
430
+ return;
431
+ }
432
+ const tileSize = getTileSize(map);
433
+ const origin = getEntityTile(player, tileSize);
434
+ const targetTile = {
435
+ x: target.x,
436
+ y: target.y
437
+ };
438
+ if (manhattanDistance(origin, targetTile) > targeting.range) return;
439
+ const mask = parseAoeMask(targeting.aoeMask || options.skills?.defaultAoeMask);
440
+ const affected = /* @__PURE__ */ new Set();
441
+ mask.cells.forEach((cell) => {
442
+ const x = targetTile.x + cell.dx;
443
+ const y = targetTile.y + cell.dy;
444
+ affected.add(`${x},${y}`);
445
+ });
446
+ const targets = [];
447
+ const actionTarget = actionConfig?.target ?? "enemy";
448
+ const affects = options.targeting?.affects || "events";
449
+ if (affects === "events" || affects === "both") map.getEvents().forEach((event) => {
450
+ const tile = getEntityTile(event, tileSize);
451
+ if (affected.has(`${tile.x},${tile.y}`) && canActionBattleUseTarget(player, event, actionTarget, options.combat?.targets)) targets.push(event);
452
+ });
453
+ if (affects === "players" || affects === "both") map.getPlayers().forEach((other) => {
454
+ if (other.id === player.id) return;
455
+ const tile = getEntityTile(other, tileSize);
456
+ if (affected.has(`${tile.x},${tile.y}`) && canActionBattleUseTarget(player, other, actionTarget, options.combat?.targets)) targets.push(other);
457
+ });
458
+ if (!options.targeting?.allowEmptyTarget && targets.length === 0) return;
459
+ executeActionBattleUse({
460
+ attacker: player,
461
+ target: targets,
462
+ usable: skillData,
463
+ skill: skillData
464
+ });
465
+ };
466
+ var createActionBattleServer = (rawOptions = {}) => {
467
+ const options = normalizeActionBattleOptions(rawOptions);
468
+ setActionBattleOptions(options);
469
+ setActionBattleSystems(options);
470
+ return defineModule({
471
+ player: {
472
+ /**
473
+ * Handle player input for combat actions
474
+ *
475
+ * When a player presses the action key, create an attack hitbox
476
+ * that can damage AI enemies within range and knockback the event.
477
+ * Knockback force is based on the player's equipped weapon.
478
+ * Triggers attack animation and visual effects.
479
+ *
480
+ * @param player - The player performing the action
481
+ * @param input - Input data containing pressed keys
482
+ */
483
+ onInput(player, input) {
484
+ if (input.action == Control.Action) {
485
+ const map = player.getCurrentMap();
486
+ const direction = resolveActionBattleAttackDirection(player, input);
487
+ applyActionBattleAttackDirection(player, direction);
488
+ const attackProfile = resolvePlayerAttackProfile(player, options);
489
+ const directionKey = direction;
490
+ const resolveActiveHitboxes = () => resolvePlayerAttackHitboxes(player, directionKey, options, attackProfile);
491
+ if (isActionReservedForNormalEvent(player, map, resolveActiveHitboxes())) return;
492
+ const lockMovement = attackProfile.movementLock;
493
+ const lockDirection = attackProfile.directionLock;
494
+ const lockDurationMs = attackProfile.totalDurationMs ?? DEFAULT_ATTACK_LOCK_DURATION_MS;
495
+ const actionLocked = (lockMovement || lockDirection) && lockDurationMs > 0;
496
+ if (actionLocked && !beginPlayerAttackLock(player, map, Math.max(0, lockDurationMs), {
497
+ movement: lockMovement,
498
+ direction: lockDirection
499
+ })) return;
500
+ withActionBattleAnimationUnlocked(player, () => {
501
+ emitActionBattleClientVisual({
502
+ moment: "attack",
503
+ entity: player
504
+ });
505
+ });
506
+ if (actionLocked) player.animationFixed = true;
507
+ const attackId = createActionBattleAttackId(player.id, attackProfile.id);
508
+ const weapon = resolveActionBattleWeapon(player);
509
+ const hitTracker = new ActionBattleHitTracker(attackProfile.hitPolicy);
510
+ const targetSelector = getActionBattleTargets(player, "events");
511
+ const processHits = (hits) => {
512
+ hits.forEach((hit) => {
513
+ if (!canActionBattleTarget(player, hit, targetSelector, options.combat?.targets)) return;
514
+ if (!hitTracker.tryHit(hit)) return;
515
+ if (weapon && executeActionBattleUse({
516
+ attacker: player,
517
+ target: hit,
518
+ usable: weapon,
519
+ weapon,
520
+ profile: attackProfile,
521
+ playVisual: false
522
+ })) return;
523
+ applyActionBattleEntityHit(player, hit, void 0, {
524
+ attackId,
525
+ attackProfileId: attackProfile.id,
526
+ reaction: attackProfile.reaction
527
+ });
528
+ });
529
+ };
530
+ runActionBattleActiveHitbox(attackProfile, resolveActiveHitboxes, (activeHitboxes) => {
531
+ processHits(getActionBattleHitboxCandidates(map, activeHitboxes, {
532
+ excludeIds: [player.id],
533
+ kinds: ["players", "events"]
534
+ }));
535
+ });
536
+ }
537
+ },
538
+ onConnected(player) {
539
+ const actionBar = options.ui?.actionBar;
540
+ if (actionBar?.enabled && actionBar?.autoOpen) openActionBattleActionBar(player, options);
541
+ }
542
+ },
543
+ event: {
544
+ /**
545
+ * Handle player detection when entering AI vision
546
+ *
547
+ * Called when a player enters an AI event's vision range.
548
+ * The AI will start pursuing and attacking the player.
549
+ *
550
+ * @param event - The AI event
551
+ * @param player - The player entering vision
552
+ * @param shape - The vision shape
553
+ */
554
+ onDetectInShape(event, player, shape) {
555
+ event.battleAi?.onDetectInShape(player, shape);
556
+ },
557
+ /**
558
+ * Handle player leaving AI vision
559
+ *
560
+ * Called when a player leaves an AI event's vision range.
561
+ * The AI will stop pursuing the player.
562
+ *
563
+ * @param event - The AI event
564
+ * @param player - The player leaving vision
565
+ * @param shape - The vision shape
566
+ */
567
+ onDetectOutShape(event, player, shape) {
568
+ event.battleAi?.onDetectOutShape(player, shape);
569
+ }
570
+ },
571
+ projectiles: {
572
+ onImpact(context) {
573
+ handleActionBattleProjectileImpact({
574
+ attacker: context.projectile?.payload?.attackerId ? context.map?.getObjectById?.(context.projectile.payload.attackerId) : void 0,
575
+ target: context.target,
576
+ projectile: context.projectile,
577
+ hit: context.hit,
578
+ map: context.map
579
+ });
580
+ },
581
+ onDestroy(context) {
582
+ handleActionBattleProjectileDestroy(context.projectile?.id);
583
+ }
584
+ }
585
+ });
586
+ };
587
+ createActionBattleServer();
588
+ //#endregion
589
+ export { ACTION_BATTLE_ACTION_BAR_GUI_ID, DEFAULT_PLAYER_ATTACK_HITBOXES, applyActionBattleEntityHit, applyPlayerHitToEvent, createActionBattleServer, getPlayerWeaponKnockbackForce, openActionBattleActionBar, updateActionBattleActionBar };