@rpgjs/action-battle 5.0.0-beta.1 → 5.0.0-beta.10

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 (103) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/LICENSE +19 -0
  3. package/README.md +392 -22
  4. package/dist/{ai.server.d.ts → client/ai.server.d.ts} +90 -28
  5. package/dist/client/animations.d.ts +16 -0
  6. package/dist/{client.d.ts → client/client.d.ts} +3 -2
  7. package/dist/{config.d.ts → client/config.d.ts} +2 -0
  8. package/dist/client/core/attack-profile.d.ts +9 -0
  9. package/dist/client/core/attack-runtime.d.ts +20 -0
  10. package/dist/client/core/context.d.ts +5 -0
  11. package/dist/client/core/defaults.d.ts +81 -0
  12. package/dist/client/core/enemy-attack-profiles.d.ts +6 -0
  13. package/dist/client/core/equipment.d.ts +2 -0
  14. package/dist/client/core/hit-reaction.d.ts +5 -0
  15. package/dist/client/core/hit.d.ts +2 -0
  16. package/dist/client/enemies/factory.d.ts +7 -0
  17. package/dist/client/index.d.ts +21 -0
  18. package/dist/client/index.js +24 -31
  19. package/dist/client/index10.js +61 -0
  20. package/dist/client/index11.js +55 -0
  21. package/dist/client/index12.js +106 -0
  22. package/dist/client/index13.js +143 -0
  23. package/dist/client/index14.js +25 -0
  24. package/dist/client/index15.js +72 -0
  25. package/dist/client/index16.js +1343 -0
  26. package/dist/client/index17.js +13 -0
  27. package/dist/client/index18.js +60 -0
  28. package/dist/client/index19.js +10 -0
  29. package/dist/client/index2.js +30 -45
  30. package/dist/client/index20.js +504 -0
  31. package/dist/client/index3.js +45 -1288
  32. package/dist/client/index4.js +105 -330
  33. package/dist/client/index5.js +84 -291
  34. package/dist/client/index6.js +309 -95
  35. package/dist/client/index7.js +35 -59
  36. package/dist/client/index8.js +101 -54
  37. package/dist/client/index9.js +79 -30
  38. package/dist/{server.d.ts → client/server.d.ts} +12 -4
  39. package/dist/client/ui/state.d.ts +35 -0
  40. package/dist/server/ai.server.d.ts +569 -0
  41. package/dist/server/animations.d.ts +16 -0
  42. package/dist/server/config.d.ts +5 -0
  43. package/dist/server/core/attack-profile.d.ts +9 -0
  44. package/dist/server/core/attack-runtime.d.ts +20 -0
  45. package/dist/server/core/context.d.ts +5 -0
  46. package/dist/server/core/defaults.d.ts +81 -0
  47. package/dist/server/core/enemy-attack-profiles.d.ts +6 -0
  48. package/dist/server/core/equipment.d.ts +2 -0
  49. package/dist/server/core/hit-reaction.d.ts +5 -0
  50. package/dist/server/core/hit.d.ts +2 -0
  51. package/dist/server/enemies/factory.d.ts +7 -0
  52. package/dist/server/index.d.ts +21 -0
  53. package/dist/server/index.js +23 -31
  54. package/dist/server/index10.js +1342 -0
  55. package/dist/server/index11.js +37 -0
  56. package/dist/server/index12.js +60 -0
  57. package/dist/server/index13.js +13 -0
  58. package/dist/server/index14.js +503 -0
  59. package/dist/server/index15.js +10 -0
  60. package/dist/server/index2.js +59 -332
  61. package/dist/server/index3.js +29 -1286
  62. package/dist/server/index4.js +45 -53
  63. package/dist/server/index5.js +107 -29
  64. package/dist/server/index6.js +143 -0
  65. package/dist/server/index7.js +25 -0
  66. package/dist/server/index8.js +72 -0
  67. package/dist/server/index9.js +55 -0
  68. package/dist/server/server.d.ts +106 -0
  69. package/dist/server/targeting.d.ts +19 -0
  70. package/package.json +12 -12
  71. package/src/ai.server.spec.ts +120 -0
  72. package/src/ai.server.ts +515 -91
  73. package/src/animations.ts +149 -0
  74. package/src/canvas-engine-shim.ts +4 -0
  75. package/src/client.ts +130 -2
  76. package/src/components/action-bar.ce +5 -3
  77. package/src/components/attack-preview.ce +90 -0
  78. package/src/config.ts +61 -0
  79. package/src/core/attack-profile.spec.ts +118 -0
  80. package/src/core/attack-profile.ts +100 -0
  81. package/src/core/attack-runtime.spec.ts +103 -0
  82. package/src/core/attack-runtime.ts +83 -0
  83. package/src/core/context.ts +35 -0
  84. package/src/core/contracts.ts +126 -0
  85. package/src/core/defaults.ts +162 -0
  86. package/src/core/enemy-attack-profiles.spec.ts +35 -0
  87. package/src/core/enemy-attack-profiles.ts +103 -0
  88. package/src/core/equipment.spec.ts +37 -0
  89. package/src/core/equipment.ts +17 -0
  90. package/src/core/hit-reaction.spec.ts +43 -0
  91. package/src/core/hit-reaction.ts +70 -0
  92. package/src/core/hit.spec.ts +111 -0
  93. package/src/core/hit.ts +92 -0
  94. package/src/enemies/factory.ts +25 -0
  95. package/src/index.ts +94 -1
  96. package/src/server.ts +427 -93
  97. package/src/targeting.spec.ts +24 -0
  98. package/src/types/canvas-engine.d.ts +4 -0
  99. package/src/types.ts +148 -0
  100. package/src/ui/state.ts +57 -0
  101. package/dist/index.d.ts +0 -11
  102. package/dist/ui/state.d.ts +0 -18
  103. /package/dist/{targeting.d.ts → client/targeting.d.ts} +0 -0
@@ -1,55 +1,47 @@
1
- const DEFAULT_ACTION_BATTLE_OPTIONS = {
2
- ui: {
3
- actionBar: {
4
- enabled: false,
5
- autoOpen: false,
6
- mode: "both"
7
- },
8
- targeting: {
9
- enabled: true,
10
- showGrid: true,
11
- colors: {
12
- area: 3120887,
13
- edge: 1796760,
14
- cursor: 16765286
15
- }
16
- }
17
- },
18
- skills: {
19
- defaultAoeMask: ["#"]
20
- },
21
- targeting: {
22
- affects: "events",
23
- allowEmptyTarget: true
24
- }
1
+ import { DEFAULT_ACTION_BATTLE_HIT_REACTION, normalizeActionBattleHitReaction } from "./index3.js";
2
+ //#region src/core/attack-profile.ts
3
+ var DEFAULT_ACTION_BATTLE_ATTACK_PROFILE = {
4
+ id: "basic",
5
+ startupMs: 0,
6
+ activeMs: 120,
7
+ recoveryMs: 230,
8
+ cooldownMs: 350,
9
+ movementLock: true,
10
+ directionLock: true,
11
+ animationKey: "attack",
12
+ hitPolicy: "oncePerTarget",
13
+ reaction: DEFAULT_ACTION_BATTLE_HIT_REACTION,
14
+ totalDurationMs: 350
25
15
  };
26
- function normalizeActionBattleOptions(options = {}) {
27
- return {
28
- ui: {
29
- actionBar: {
30
- ...DEFAULT_ACTION_BATTLE_OPTIONS.ui?.actionBar,
31
- ...options.ui?.actionBar
32
- },
33
- targeting: {
34
- ...DEFAULT_ACTION_BATTLE_OPTIONS.ui?.targeting,
35
- ...options.ui?.targeting,
36
- colors: {
37
- ...DEFAULT_ACTION_BATTLE_OPTIONS.ui?.targeting?.colors,
38
- ...options.ui?.targeting?.colors
39
- }
40
- }
41
- },
42
- skills: {
43
- ...DEFAULT_ACTION_BATTLE_OPTIONS.skills,
44
- ...options.skills
45
- },
46
- targeting: {
47
- ...DEFAULT_ACTION_BATTLE_OPTIONS.targeting,
48
- ...options.targeting
49
- }
50
- };
16
+ var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
17
+ var nonNegativeMs = (value, fallback) => isFiniteNumber(value) ? Math.max(0, value) : fallback;
18
+ var positiveMs = (value, fallback) => isFiniteNumber(value) ? Math.max(1, value) : fallback;
19
+ var resolveHitPolicy = (value) => value === "allowRepeatHits" ? "allowRepeatHits" : "oncePerTarget";
20
+ var resolveAnimationKey = (value) => value ?? DEFAULT_ACTION_BATTLE_ATTACK_PROFILE.animationKey;
21
+ function normalizeActionBattleAttackProfile(profile = {}, fallbacks = {}) {
22
+ const startupMs = nonNegativeMs(profile.startupMs, DEFAULT_ACTION_BATTLE_ATTACK_PROFILE.startupMs);
23
+ const activeMs = positiveMs(profile.activeMs, DEFAULT_ACTION_BATTLE_ATTACK_PROFILE.activeMs);
24
+ const legacyDuration = nonNegativeMs(fallbacks.lockDurationMs, DEFAULT_ACTION_BATTLE_ATTACK_PROFILE.totalDurationMs);
25
+ const fallbackRecoveryMs = Math.max(0, legacyDuration - startupMs - activeMs);
26
+ const recoveryMs = nonNegativeMs(profile.recoveryMs, fallbackRecoveryMs);
27
+ const totalDurationMs = startupMs + activeMs + recoveryMs;
28
+ const cooldownMs = nonNegativeMs(profile.cooldownMs, totalDurationMs);
29
+ const hitboxes = profile.hitboxes ?? fallbacks.hitboxes;
30
+ const normalized = {
31
+ id: profile.id || fallbacks.id || DEFAULT_ACTION_BATTLE_ATTACK_PROFILE.id,
32
+ startupMs,
33
+ activeMs,
34
+ recoveryMs,
35
+ cooldownMs,
36
+ movementLock: profile.movementLock ?? fallbacks.lockMovement ?? DEFAULT_ACTION_BATTLE_ATTACK_PROFILE.movementLock,
37
+ directionLock: profile.directionLock ?? DEFAULT_ACTION_BATTLE_ATTACK_PROFILE.directionLock,
38
+ animationKey: resolveAnimationKey(profile.animationKey),
39
+ hitPolicy: resolveHitPolicy(profile.hitPolicy),
40
+ reaction: normalizeActionBattleHitReaction(profile.reaction),
41
+ totalDurationMs
42
+ };
43
+ if (hitboxes) normalized.hitboxes = hitboxes;
44
+ return normalized;
51
45
  }
52
- export {
53
- DEFAULT_ACTION_BATTLE_OPTIONS,
54
- normalizeActionBattleOptions
55
- };
46
+ //#endregion
47
+ export { DEFAULT_ACTION_BATTLE_ATTACK_PROFILE, normalizeActionBattleAttackProfile };
@@ -1,30 +1,108 @@
1
- const normalizeMaskRows = (mask) => {
2
- if (!mask) return ["#"];
3
- if (Array.isArray(mask)) return mask;
4
- return mask.trim().split("\n").map((row) => row.replace(/\r/g, ""));
5
- };
6
- const parseAoeMask = (mask) => {
7
- const rows = normalizeMaskRows(mask);
8
- const height = rows.length;
9
- const width = rows.reduce((max, row) => Math.max(max, row.length), 0);
10
- const centerX = Math.floor(width / 2);
11
- const centerY = Math.floor(height / 2);
12
- const cells = [];
13
- rows.forEach((row, y) => {
14
- for (let x = 0; x < row.length; x++) {
15
- const char = row[x];
16
- if (char && char !== "." && char !== " ") {
17
- cells.push({ dx: x - centerX, dy: y - centerY });
18
- }
19
- }
20
- });
21
- if (cells.length === 0) {
22
- cells.push({ dx: 0, dy: 0 });
23
- }
24
- return { width, height, centerX, centerY, cells };
25
- };
26
- const manhattanDistance = (a, b) => Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
27
- export {
28
- manhattanDistance,
29
- parseAoeMask
1
+ import { normalizeActionBattleAttackProfile } from "./index4.js";
2
+ //#region src/config.ts
3
+ var DEFAULT_ACTION_BATTLE_OPTIONS = {
4
+ ui: {
5
+ actionBar: {
6
+ enabled: false,
7
+ autoOpen: false,
8
+ mode: "both"
9
+ },
10
+ targeting: {
11
+ enabled: true,
12
+ showGrid: true,
13
+ colors: {
14
+ area: 3120887,
15
+ edge: 1796760,
16
+ cursor: 16765286
17
+ }
18
+ }
19
+ },
20
+ skills: { defaultAoeMask: ["#"] },
21
+ targeting: {
22
+ affects: "events",
23
+ allowEmptyTarget: true
24
+ },
25
+ attack: {
26
+ lockMovement: true,
27
+ lockDurationMs: 350,
28
+ showPreview: true,
29
+ previewDurationMs: 180,
30
+ previewColor: 16774064,
31
+ previewAccentColor: 16777215
32
+ },
33
+ animations: {}
30
34
  };
35
+ var currentActionBattleOptions = DEFAULT_ACTION_BATTLE_OPTIONS;
36
+ function normalizeActionBattleOptions(options = {}) {
37
+ const attack = {
38
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.attack,
39
+ ...options.attack
40
+ };
41
+ const attackProfile = normalizeActionBattleAttackProfile(attack.profile, {
42
+ lockMovement: attack.lockMovement,
43
+ lockDurationMs: attack.lockDurationMs,
44
+ hitboxes: attack.hitboxes
45
+ });
46
+ return {
47
+ ui: {
48
+ actionBar: {
49
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.ui?.actionBar,
50
+ ...options.ui?.actionBar
51
+ },
52
+ targeting: {
53
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.ui?.targeting,
54
+ ...options.ui?.targeting,
55
+ colors: {
56
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.ui?.targeting?.colors,
57
+ ...options.ui?.targeting?.colors
58
+ }
59
+ }
60
+ },
61
+ skills: {
62
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.skills,
63
+ ...options.skills
64
+ },
65
+ targeting: {
66
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.targeting,
67
+ ...options.targeting
68
+ },
69
+ debug: {
70
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.debug,
71
+ ...options.debug
72
+ },
73
+ attack: {
74
+ ...attack,
75
+ profile: attackProfile
76
+ },
77
+ animations: {
78
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.animations,
79
+ ...options.animations
80
+ },
81
+ systems: {
82
+ combat: {
83
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.systems?.combat,
84
+ ...options.systems?.combat,
85
+ hooks: {
86
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.systems?.combat?.hooks,
87
+ ...options.systems?.combat?.hooks
88
+ }
89
+ },
90
+ ai: {
91
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.systems?.ai,
92
+ ...options.systems?.ai,
93
+ behaviors: {
94
+ ...DEFAULT_ACTION_BATTLE_OPTIONS.systems?.ai?.behaviors,
95
+ ...options.systems?.ai?.behaviors
96
+ }
97
+ }
98
+ }
99
+ };
100
+ }
101
+ function setActionBattleOptions(options) {
102
+ currentActionBattleOptions = options;
103
+ }
104
+ function getActionBattleOptions() {
105
+ return currentActionBattleOptions;
106
+ }
107
+ //#endregion
108
+ export { getActionBattleOptions, normalizeActionBattleOptions, setActionBattleOptions };
@@ -0,0 +1,143 @@
1
+ //#region src/core/defaults.ts
2
+ var DEFAULT_CORE_KNOCKBACK = {
3
+ force: 50,
4
+ duration: 300
5
+ };
6
+ var CoreAttackPattern = {
7
+ Melee: "melee",
8
+ Combo: "combo",
9
+ Charged: "charged",
10
+ Zone: "zone",
11
+ DashAttack: "dashAttack"
12
+ };
13
+ var CoreEnemyType = {
14
+ Aggressive: "aggressive",
15
+ Defensive: "defensive",
16
+ Ranged: "ranged",
17
+ Tank: "tank",
18
+ Berserker: "berserker"
19
+ };
20
+ var DEFAULT_ZELDA_PLAYER_HITBOXES = {
21
+ up: {
22
+ offsetX: -16,
23
+ offsetY: -48,
24
+ width: 32,
25
+ height: 32
26
+ },
27
+ down: {
28
+ offsetX: -16,
29
+ offsetY: 16,
30
+ width: 32,
31
+ height: 32
32
+ },
33
+ left: {
34
+ offsetX: -48,
35
+ offsetY: -16,
36
+ width: 32,
37
+ height: 32
38
+ },
39
+ right: {
40
+ offsetX: 16,
41
+ offsetY: -16,
42
+ width: 32,
43
+ height: 32
44
+ },
45
+ default: {
46
+ offsetX: 0,
47
+ offsetY: -32,
48
+ width: 32,
49
+ height: 32
50
+ }
51
+ };
52
+ var resolveEquippedWeapon = (entity) => {
53
+ const equipments = entity?.equipments?.() || [];
54
+ for (const item of equipments) {
55
+ const itemId = item?.id?.() ?? item?.id;
56
+ const itemData = entity?.databaseById?.(itemId);
57
+ if (itemData?._type === "weapon") return itemData;
58
+ }
59
+ return null;
60
+ };
61
+ var resolveDirection = (attacker, target) => {
62
+ const dx = target.x() - attacker.x();
63
+ const dy = target.y() - attacker.y();
64
+ const distance = Math.sqrt(dx * dx + dy * dy);
65
+ if (distance <= 0) return void 0;
66
+ return {
67
+ x: dx / distance,
68
+ y: dy / distance
69
+ };
70
+ };
71
+ var createDefaultPlayerHitboxResolver = (hitboxes = DEFAULT_ZELDA_PLAYER_HITBOXES) => (context) => {
72
+ const attacker = context.attacker;
73
+ const config = hitboxes[context.direction ?? (typeof attacker.getDirection === "function" ? attacker.getDirection() : "default")] || hitboxes.default;
74
+ return [{
75
+ x: attacker.x() + config.offsetX,
76
+ y: attacker.y() + config.offsetY,
77
+ width: config.width,
78
+ height: config.height
79
+ }];
80
+ };
81
+ var defaultRpgjsDamageResolver = (context) => {
82
+ const target = context.target;
83
+ const raw = target.applyDamage(context.attacker, context.skill);
84
+ return {
85
+ damage: raw?.damage ?? 0,
86
+ defeated: target.hp <= 0,
87
+ raw
88
+ };
89
+ };
90
+ var defaultKnockbackResolver = (context) => {
91
+ const weapon = context.weapon ?? resolveEquippedWeapon(context.attacker);
92
+ return {
93
+ force: weapon?.knockbackForce ?? DEFAULT_CORE_KNOCKBACK.force,
94
+ duration: weapon?.knockbackDuration ?? DEFAULT_CORE_KNOCKBACK.duration,
95
+ direction: resolveDirection(context.attacker, context.target)
96
+ };
97
+ };
98
+ var defaultCombatSystem = {
99
+ resolveHitboxes: createDefaultPlayerHitboxResolver(),
100
+ resolveDamage: defaultRpgjsDamageResolver,
101
+ resolveKnockback: defaultKnockbackResolver
102
+ };
103
+ var defaultEnemyBehaviors = {
104
+ [CoreEnemyType.Aggressive]: ({ hpPercent }) => ({
105
+ mode: hpPercent !== null && hpPercent < .15 ? "retreat" : "assault",
106
+ attackPatterns: [
107
+ CoreAttackPattern.Melee,
108
+ CoreAttackPattern.Combo,
109
+ CoreAttackPattern.DashAttack
110
+ ]
111
+ }),
112
+ [CoreEnemyType.Defensive]: ({ hpPercent }) => ({
113
+ mode: hpPercent !== null && hpPercent < .3 ? "retreat" : "tactical",
114
+ attackPatterns: [CoreAttackPattern.Melee, CoreAttackPattern.Charged]
115
+ }),
116
+ [CoreEnemyType.Ranged]: ({ distance }) => ({
117
+ mode: distance !== null && distance < 80 ? "retreat" : "tactical",
118
+ attackPatterns: [CoreAttackPattern.Melee, CoreAttackPattern.Zone]
119
+ }),
120
+ [CoreEnemyType.Tank]: () => ({
121
+ mode: "assault",
122
+ attackPatterns: [
123
+ CoreAttackPattern.Melee,
124
+ CoreAttackPattern.Charged,
125
+ CoreAttackPattern.Zone
126
+ ]
127
+ }),
128
+ [CoreEnemyType.Berserker]: ({ hpPercent }) => ({
129
+ mode: "assault",
130
+ attackCooldown: hpPercent === null ? void 0 : Math.max(250, 800 * Math.max(.3, hpPercent)),
131
+ attackPatterns: [
132
+ CoreAttackPattern.Melee,
133
+ CoreAttackPattern.Combo,
134
+ CoreAttackPattern.DashAttack
135
+ ]
136
+ })
137
+ };
138
+ var defaultActionBattleSystems = {
139
+ combat: defaultCombatSystem,
140
+ ai: { behaviors: defaultEnemyBehaviors }
141
+ };
142
+ //#endregion
143
+ export { DEFAULT_ZELDA_PLAYER_HITBOXES, createDefaultPlayerHitboxResolver, defaultActionBattleSystems, defaultCombatSystem, defaultEnemyBehaviors, defaultKnockbackResolver, defaultRpgjsDamageResolver };
@@ -0,0 +1,25 @@
1
+ import { defaultActionBattleSystems } from "./index6.js";
2
+ //#region src/core/context.ts
3
+ var mergeSystems = (options = {}) => ({
4
+ combat: {
5
+ ...defaultActionBattleSystems.combat,
6
+ resolveDamage: options.systems?.combat?.damage ?? defaultActionBattleSystems.combat.resolveDamage,
7
+ resolveKnockback: options.systems?.combat?.knockback ?? defaultActionBattleSystems.combat.resolveKnockback,
8
+ hooks: {
9
+ ...defaultActionBattleSystems.combat.hooks,
10
+ ...options.systems?.combat?.hooks
11
+ }
12
+ },
13
+ ai: { behaviors: {
14
+ ...defaultActionBattleSystems.ai.behaviors,
15
+ ...options.systems?.ai?.behaviors
16
+ } }
17
+ });
18
+ var currentActionBattleSystems = mergeSystems();
19
+ var setActionBattleSystems = (options = {}) => {
20
+ currentActionBattleSystems = mergeSystems(options);
21
+ };
22
+ var getActionBattleSystems = () => currentActionBattleSystems;
23
+ var createActionBattleSystems = mergeSystems;
24
+ //#endregion
25
+ export { createActionBattleSystems, getActionBattleSystems, setActionBattleSystems };
@@ -0,0 +1,72 @@
1
+ import { normalizeActionBattleAttackProfile } from "./index4.js";
2
+ //#region src/core/enemy-attack-profiles.ts
3
+ var DEFAULT_ACTION_BATTLE_ENEMY_ATTACK_PROFILES = {
4
+ melee: {
5
+ id: "enemy-melee",
6
+ startupMs: 120,
7
+ activeMs: 100,
8
+ recoveryMs: 220,
9
+ cooldownMs: 440,
10
+ reaction: {
11
+ invincibilityMs: 250,
12
+ hitstunMs: 120,
13
+ staggerPower: 1
14
+ }
15
+ },
16
+ combo: {
17
+ id: "enemy-combo",
18
+ startupMs: 80,
19
+ activeMs: 80,
20
+ recoveryMs: 140,
21
+ cooldownMs: 300,
22
+ reaction: {
23
+ invincibilityMs: 180,
24
+ hitstunMs: 90,
25
+ staggerPower: .75
26
+ }
27
+ },
28
+ charged: {
29
+ id: "enemy-charged",
30
+ startupMs: 800,
31
+ activeMs: 140,
32
+ recoveryMs: 320,
33
+ cooldownMs: 1260,
34
+ reaction: {
35
+ invincibilityMs: 350,
36
+ hitstunMs: 220,
37
+ staggerPower: 2
38
+ }
39
+ },
40
+ zone: {
41
+ id: "enemy-zone",
42
+ startupMs: 450,
43
+ activeMs: 180,
44
+ recoveryMs: 320,
45
+ cooldownMs: 950,
46
+ reaction: {
47
+ invincibilityMs: 300,
48
+ hitstunMs: 160,
49
+ staggerPower: 1.25
50
+ }
51
+ },
52
+ dashAttack: {
53
+ id: "enemy-dash",
54
+ startupMs: 180,
55
+ activeMs: 120,
56
+ recoveryMs: 260,
57
+ cooldownMs: 560,
58
+ reaction: {
59
+ invincibilityMs: 280,
60
+ hitstunMs: 150,
61
+ staggerPower: 1.2
62
+ }
63
+ }
64
+ };
65
+ function normalizeActionBattleEnemyAttackProfiles(overrides = {}) {
66
+ return Object.fromEntries(Object.entries(DEFAULT_ACTION_BATTLE_ENEMY_ATTACK_PROFILES).map(([key, defaultProfile]) => [key, normalizeActionBattleAttackProfile({
67
+ ...defaultProfile,
68
+ ...overrides[key]
69
+ })]));
70
+ }
71
+ //#endregion
72
+ export { DEFAULT_ACTION_BATTLE_ENEMY_ATTACK_PROFILES, normalizeActionBattleEnemyAttackProfiles };
@@ -0,0 +1,55 @@
1
+ import { normalizeActionBattleAttackProfile } from "./index4.js";
2
+ //#region src/core/attack-runtime.ts
3
+ var ACTION_BATTLE_HITBOX_FRAME_MS = 16;
4
+ function getNormalizedActionBattleAttackProfile(options = {}) {
5
+ const attack = options.attack ?? {};
6
+ return normalizeActionBattleAttackProfile(attack.profile, {
7
+ lockMovement: attack.lockMovement,
8
+ lockDurationMs: attack.lockDurationMs,
9
+ hitboxes: attack.hitboxes
10
+ });
11
+ }
12
+ function resolveActionBattleHitboxSpeed(profile, hitboxCount) {
13
+ const positions = Math.max(1, Math.floor(hitboxCount));
14
+ const activeFrames = Math.max(1, Math.ceil(profile.activeMs / 16));
15
+ return Math.max(1, Math.ceil(activeFrames / positions));
16
+ }
17
+ function scheduleActionBattleStartup(profile, callback, scheduler = setTimeout) {
18
+ if (profile.startupMs <= 0) {
19
+ callback();
20
+ return null;
21
+ }
22
+ return scheduler(callback, profile.startupMs);
23
+ }
24
+ var attackIdCounter = 0;
25
+ function createActionBattleAttackId(attackerId, profileId) {
26
+ attackIdCounter++;
27
+ return `${attackerId ?? "unknown"}:${profileId}:${Date.now()}:${attackIdCounter}`;
28
+ }
29
+ var getTargetKey = (target) => {
30
+ if (!target || target.id === void 0 || target.id === null) return null;
31
+ return String(target.id);
32
+ };
33
+ var ActionBattleHitTracker = class {
34
+ hitPolicy;
35
+ hitTargets = /* @__PURE__ */ new Set();
36
+ constructor(hitPolicy) {
37
+ this.hitPolicy = hitPolicy;
38
+ }
39
+ canHit(target) {
40
+ if (this.hitPolicy === "allowRepeatHits") return true;
41
+ const key = getTargetKey(target);
42
+ return !key || !this.hitTargets.has(key);
43
+ }
44
+ recordHit(target) {
45
+ const key = getTargetKey(target);
46
+ if (key) this.hitTargets.add(key);
47
+ }
48
+ tryHit(target) {
49
+ if (!this.canHit(target)) return false;
50
+ this.recordHit(target);
51
+ return true;
52
+ }
53
+ };
54
+ //#endregion
55
+ export { ACTION_BATTLE_HITBOX_FRAME_MS, ActionBattleHitTracker, createActionBattleAttackId, getNormalizedActionBattleAttackProfile, resolveActionBattleHitboxSpeed, scheduleActionBattleStartup };
@@ -0,0 +1,106 @@
1
+ import { RpgEvent, RpgPlayer, RpgServer } from '@rpgjs/server';
2
+ import { HitResult, ApplyHitHooks } from './ai.server';
3
+ import { ActionBattleOptions } from './types';
4
+ export declare const ACTION_BATTLE_ACTION_BAR_GUI_ID = "action-battle-action-bar";
5
+ /**
6
+ * Default player attack hitboxes offsets for each direction
7
+ *
8
+ * These hitboxes define the attack areas relative to the player's position
9
+ * for each cardinal direction. They are converted to absolute coordinates
10
+ * when creating the moving hitbox.
11
+ */
12
+ export declare const DEFAULT_PLAYER_ATTACK_HITBOXES: {
13
+ up: {
14
+ offsetX: number;
15
+ offsetY: number;
16
+ width: number;
17
+ height: number;
18
+ };
19
+ down: {
20
+ offsetX: number;
21
+ offsetY: number;
22
+ width: number;
23
+ height: number;
24
+ };
25
+ left: {
26
+ offsetX: number;
27
+ offsetY: number;
28
+ width: number;
29
+ height: number;
30
+ };
31
+ right: {
32
+ offsetX: number;
33
+ offsetY: number;
34
+ width: number;
35
+ height: number;
36
+ };
37
+ default: {
38
+ offsetX: number;
39
+ offsetY: number;
40
+ width: number;
41
+ height: number;
42
+ };
43
+ };
44
+ /**
45
+ * Get knockback force from player's equipped weapon
46
+ *
47
+ * Retrieves the knockbackForce property from the player's equipped weapon.
48
+ * Falls back to DEFAULT_KNOCKBACK.force if no weapon or property is set.
49
+ *
50
+ * @param player - The player to get weapon knockback from
51
+ * @returns Knockback force value
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * // Player with weapon having knockbackForce: 80
56
+ * const force = getPlayerWeaponKnockbackForce(player); // 80
57
+ *
58
+ * // No weapon equipped
59
+ * const force = getPlayerWeaponKnockbackForce(player); // 50 (default)
60
+ * ```
61
+ */
62
+ export declare function getPlayerWeaponKnockbackForce(player: RpgPlayer): number;
63
+ /**
64
+ * Apply hit from player to target (event with AI)
65
+ *
66
+ * Handles damage calculation, knockback based on weapon, and visual effects.
67
+ * Can be customized using hooks.
68
+ *
69
+ * @param player - The attacking player
70
+ * @param target - The event being hit
71
+ * @param hooks - Optional hooks for customizing hit behavior
72
+ * @returns Hit result if AI exists, undefined otherwise
73
+ *
74
+ * @example
75
+ * ```ts
76
+ * // Basic hit
77
+ * const result = applyPlayerHitToEvent(player, event);
78
+ *
79
+ * // With custom hooks
80
+ * const result = applyPlayerHitToEvent(player, event, {
81
+ * onBeforeHit(result) {
82
+ * result.knockbackForce *= 2; // Double knockback
83
+ * return result;
84
+ * },
85
+ * onAfterHit(result) {
86
+ * if (result.defeated) {
87
+ * player.gold += 10;
88
+ * }
89
+ * }
90
+ * });
91
+ * ```
92
+ */
93
+ export declare function applyPlayerHitToEvent(player: RpgPlayer, target: RpgEvent, hooks?: ApplyHitHooks, metadata?: Record<string, any>): HitResult | undefined;
94
+ export declare const openActionBattleActionBar: (player: RpgPlayer, rawOptions?: ActionBattleOptions) => void;
95
+ export declare const updateActionBattleActionBar: (player: RpgPlayer, rawOptions?: ActionBattleOptions) => void;
96
+ export declare const createActionBattleServer: (rawOptions?: ActionBattleOptions) => RpgServer;
97
+ declare const _default: RpgServer;
98
+ export default _default;
99
+ export { ACTION_BATTLE_HITBOX_FRAME_MS, ActionBattleHitTracker, createActionBattleAttackId, getNormalizedActionBattleAttackProfile, resolveActionBattleHitboxSpeed, scheduleActionBattleStartup, } from './core/attack-runtime';
100
+ export { DEFAULT_ACTION_BATTLE_ATTACK_PROFILE, normalizeActionBattleAttackProfile, type ActionBattleAttackProfileFallbacks, } from './core/attack-profile';
101
+ export type { ActionBattleAttackDirection, ActionBattleAttackHitboxConfig, ActionBattleAttackHitboxMap, ActionBattleAttackHitPolicy, ActionBattleAttackProfile, ActionBattleDebugOptions, ActionBattleHitReactionProfile, NormalizedActionBattleHitReactionProfile, NormalizedActionBattleAttackProfile, } from './types';
102
+ export { DEFAULT_ACTION_BATTLE_HIT_REACTION, isActionBattleEntityInvincible, normalizeActionBattleHitReaction, setActionBattleInvincibility, } from './core/hit-reaction';
103
+ export { DEFAULT_ACTION_BATTLE_ENEMY_ATTACK_PROFILES, normalizeActionBattleEnemyAttackProfiles, type ActionBattleEnemyAttackProfileKey, type ActionBattleEnemyAttackProfileMap, type NormalizedActionBattleEnemyAttackProfileMap, } from './core/enemy-attack-profiles';
104
+ export { resolveActionBattleWeaponAttackProfile } from './core/equipment';
105
+ export { AiDebug, AiState, AttackPattern, BattleAi, DEFAULT_KNOCKBACK, EnemyType, } from './ai.server';
106
+ export type { ApplyHitHooks, BattleAiDefeatedCallback, BattleAiDefeatedContext, BattleAiDefeatReward, BattleAiLegacyDefeatedCallback, BattleAiLegacyOptions, BattleAiOptions, BattleAiRewardItem, BattleAiRewards, HitResult, } from './ai.server';
@@ -0,0 +1,19 @@
1
+ import { ActionBattleAoeMask } from './types';
2
+ export interface ParsedAoeMask {
3
+ width: number;
4
+ height: number;
5
+ centerX: number;
6
+ centerY: number;
7
+ cells: Array<{
8
+ dx: number;
9
+ dy: number;
10
+ }>;
11
+ }
12
+ export declare const parseAoeMask: (mask: ActionBattleAoeMask | undefined) => ParsedAoeMask;
13
+ export declare const manhattanDistance: (a: {
14
+ x: number;
15
+ y: number;
16
+ }, b: {
17
+ x: number;
18
+ y: number;
19
+ }) => number;