@rpgjs/action-battle 5.0.0-alpha.44 → 5.0.0-beta.2
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/LICENSE +19 -0
- package/README.md +94 -0
- package/dist/ai.server.d.ts +31 -27
- package/dist/animations.d.ts +16 -0
- package/dist/client/index10.js +30 -0
- package/dist/client/index3.js +46 -4
- package/dist/client/index4.js +16 -3
- package/dist/client/index6.js +1 -1
- package/dist/client/index8.js +16 -2
- package/dist/client/index9.js +60 -26
- package/dist/config.d.ts +2 -0
- package/dist/index.d.ts +2 -2
- package/dist/server/index2.js +15 -2
- package/dist/server/index3.js +46 -4
- package/dist/server/index4.js +16 -2
- package/dist/server/index6.js +64 -0
- package/package.json +5 -5
- package/src/ai.server.ts +79 -28
- package/src/animations.ts +110 -0
- package/src/config.ts +16 -0
- package/src/index.ts +7 -1
- package/src/server.ts +16 -3
- package/src/types.ts +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (C) 2026 by Samuel Ronce
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -496,6 +496,100 @@ The module handles player attacks via the `action` input:
|
|
|
496
496
|
// Knockback force is based on equipped weapon's knockbackForce property
|
|
497
497
|
```
|
|
498
498
|
|
|
499
|
+
## Configurable Combat Animations
|
|
500
|
+
|
|
501
|
+
By default, player and AI attacks keep using the existing `attack` animation:
|
|
502
|
+
|
|
503
|
+
```ts
|
|
504
|
+
player.setGraphicAnimation("attack", 1);
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
Use `animations` when your combat sprites are stored in separate graphics such
|
|
508
|
+
as `hero_attack`, `hero_hurt`, or `hero_die`.
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
import { provideActionBattle } from "@rpgjs/action-battle/server";
|
|
512
|
+
|
|
513
|
+
export default provideActionBattle({
|
|
514
|
+
animations: {
|
|
515
|
+
attack: "attack",
|
|
516
|
+
hurt: "hurt",
|
|
517
|
+
die: {
|
|
518
|
+
animationName: "die",
|
|
519
|
+
repeat: 1,
|
|
520
|
+
delayMs: 500
|
|
521
|
+
},
|
|
522
|
+
castSkill: "skill"
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
For data-driven spritesheets, use resolver functions:
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
provideActionBattle({
|
|
531
|
+
animations: {
|
|
532
|
+
attack: (entity) => ({
|
|
533
|
+
animationName: "walk",
|
|
534
|
+
graphic: entity.combatAnimations?.attack,
|
|
535
|
+
repeat: 1
|
|
536
|
+
}),
|
|
537
|
+
hurt: (entity) => ({
|
|
538
|
+
animationName: "walk",
|
|
539
|
+
graphic: entity.combatAnimations?.hurt,
|
|
540
|
+
repeat: 1
|
|
541
|
+
}),
|
|
542
|
+
die: (entity) => ({
|
|
543
|
+
animationName: "walk",
|
|
544
|
+
graphic: entity.combatAnimations?.die,
|
|
545
|
+
repeat: 1,
|
|
546
|
+
waitEnd: true
|
|
547
|
+
}),
|
|
548
|
+
castSkill: (entity, context) => ({
|
|
549
|
+
animationName: "walk",
|
|
550
|
+
graphic: entity.combatAnimations?.castSkill,
|
|
551
|
+
repeat: 1
|
|
552
|
+
})
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
When `graphic` is provided, action-battle calls:
|
|
558
|
+
|
|
559
|
+
```ts
|
|
560
|
+
entity.setGraphicAnimation(animationName, graphic, repeat);
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
Otherwise it calls:
|
|
564
|
+
|
|
565
|
+
```ts
|
|
566
|
+
entity.setGraphicAnimation(animationName, repeat);
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
Return `null` or `undefined` from a resolver to skip the animation. `BattleAi`
|
|
570
|
+
can also override the global configuration per enemy:
|
|
571
|
+
|
|
572
|
+
```ts
|
|
573
|
+
new BattleAi(this, {
|
|
574
|
+
animations: {
|
|
575
|
+
attack: {
|
|
576
|
+
animationName: "walk",
|
|
577
|
+
graphic: "slime_attack",
|
|
578
|
+
repeat: 1
|
|
579
|
+
},
|
|
580
|
+
die: {
|
|
581
|
+
animationName: "walk",
|
|
582
|
+
graphic: "slime_die",
|
|
583
|
+
repeat: 1,
|
|
584
|
+
delayMs: 700
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
`waitEnd: true` delays event removal for defeated AI with the default delay used
|
|
591
|
+
by action-battle. Use `delayMs` when you need an exact duration.
|
|
592
|
+
|
|
499
593
|
## Knockback System
|
|
500
594
|
|
|
501
595
|
Knockback force is determined by the equipped weapon's `knockbackForce` property.
|
package/dist/ai.server.d.ts
CHANGED
|
@@ -1,7 +1,36 @@
|
|
|
1
1
|
import { RpgEvent, RpgPlayer } from '@rpgjs/server';
|
|
2
|
+
import { ActionBattleAnimationOptions } from './types';
|
|
2
3
|
type RpgEventWithBattleAi = RpgEvent & {
|
|
3
4
|
battleAi: BattleAi;
|
|
4
5
|
};
|
|
6
|
+
export interface BattleAiOptions {
|
|
7
|
+
enemyType?: EnemyType;
|
|
8
|
+
attackCooldown?: number;
|
|
9
|
+
visionRange?: number;
|
|
10
|
+
attackRange?: number;
|
|
11
|
+
dodgeChance?: number;
|
|
12
|
+
dodgeCooldown?: number;
|
|
13
|
+
fleeThreshold?: number;
|
|
14
|
+
attackSkill?: any;
|
|
15
|
+
attackPatterns?: AttackPattern[];
|
|
16
|
+
patrolWaypoints?: Array<{
|
|
17
|
+
x: number;
|
|
18
|
+
y: number;
|
|
19
|
+
}>;
|
|
20
|
+
groupBehavior?: boolean;
|
|
21
|
+
moveToCooldown?: number;
|
|
22
|
+
retreatCooldown?: number;
|
|
23
|
+
behavior?: {
|
|
24
|
+
baseScore?: number;
|
|
25
|
+
updateInterval?: number;
|
|
26
|
+
minStateDuration?: number;
|
|
27
|
+
assaultThreshold?: number;
|
|
28
|
+
retreatThreshold?: number;
|
|
29
|
+
};
|
|
30
|
+
animations?: ActionBattleAnimationOptions;
|
|
31
|
+
/** Callback called when the AI is defeated */
|
|
32
|
+
onDefeated?: (event: RpgEvent, attacker?: RpgPlayer) => void;
|
|
33
|
+
}
|
|
5
34
|
/**
|
|
6
35
|
* Hit result data returned after applying damage
|
|
7
36
|
*
|
|
@@ -221,6 +250,7 @@ export declare class BattleAi {
|
|
|
221
250
|
private fleeThreshold;
|
|
222
251
|
private attackSkill;
|
|
223
252
|
private attackPatterns;
|
|
253
|
+
private animations?;
|
|
224
254
|
private comboCount;
|
|
225
255
|
private comboMax;
|
|
226
256
|
private chargingAttack;
|
|
@@ -271,33 +301,7 @@ export declare class BattleAi {
|
|
|
271
301
|
* });
|
|
272
302
|
* ```
|
|
273
303
|
*/
|
|
274
|
-
constructor(event: RpgEventWithBattleAi, options?:
|
|
275
|
-
enemyType?: EnemyType;
|
|
276
|
-
attackCooldown?: number;
|
|
277
|
-
visionRange?: number;
|
|
278
|
-
attackRange?: number;
|
|
279
|
-
dodgeChance?: number;
|
|
280
|
-
dodgeCooldown?: number;
|
|
281
|
-
fleeThreshold?: number;
|
|
282
|
-
attackSkill?: any;
|
|
283
|
-
attackPatterns?: AttackPattern[];
|
|
284
|
-
patrolWaypoints?: Array<{
|
|
285
|
-
x: number;
|
|
286
|
-
y: number;
|
|
287
|
-
}>;
|
|
288
|
-
groupBehavior?: boolean;
|
|
289
|
-
moveToCooldown?: number;
|
|
290
|
-
retreatCooldown?: number;
|
|
291
|
-
behavior?: {
|
|
292
|
-
baseScore?: number;
|
|
293
|
-
updateInterval?: number;
|
|
294
|
-
minStateDuration?: number;
|
|
295
|
-
assaultThreshold?: number;
|
|
296
|
-
retreatThreshold?: number;
|
|
297
|
-
};
|
|
298
|
-
/** Callback called when the AI is defeated */
|
|
299
|
-
onDefeated?: (event: RpgEvent, attacker?: RpgPlayer) => void;
|
|
300
|
-
});
|
|
304
|
+
constructor(event: RpgEventWithBattleAi, options?: BattleAiOptions);
|
|
301
305
|
/**
|
|
302
306
|
* Apply enemy type-specific behavior modifiers
|
|
303
307
|
*
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ActionBattleAnimationContext, ActionBattleAnimationEntity, ActionBattleAnimationKey, ActionBattleAnimationOptions } from './types';
|
|
2
|
+
export declare const DEFAULT_DIE_ANIMATION_DELAY_MS = 500;
|
|
3
|
+
export interface ResolvedActionBattleAnimation {
|
|
4
|
+
animationName: string;
|
|
5
|
+
graphic?: string | string[];
|
|
6
|
+
repeat: number;
|
|
7
|
+
waitEnd: boolean;
|
|
8
|
+
delayMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ActionBattleAnimationDefaults {
|
|
11
|
+
animationName?: string;
|
|
12
|
+
repeat?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function resolveActionBattleAnimation(key: ActionBattleAnimationKey, entity: ActionBattleAnimationEntity, animations?: ActionBattleAnimationOptions, context?: ActionBattleAnimationContext, defaults?: ActionBattleAnimationDefaults): ResolvedActionBattleAnimation | null;
|
|
15
|
+
export declare function playActionBattleAnimation(key: ActionBattleAnimationKey, entity: ActionBattleAnimationEntity, animations?: ActionBattleAnimationOptions, context?: ActionBattleAnimationContext, defaults?: ActionBattleAnimationDefaults): ResolvedActionBattleAnimation | null;
|
|
16
|
+
export declare function getActionBattleAnimationRemovalDelay(animation: ResolvedActionBattleAnimation | null): number;
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
|
30
|
+
};
|
package/dist/client/index3.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { playActionBattleAnimation, getActionBattleAnimationRemovalDelay } from "./index9.js";
|
|
2
|
+
import { getActionBattleOptions } from "./index8.js";
|
|
1
3
|
const MAXHP = null;
|
|
2
4
|
const RpgPlayer = null;
|
|
3
5
|
const AiDebug = {
|
|
@@ -118,6 +120,10 @@ class BattleAi {
|
|
|
118
120
|
this.enemyType = options.enemyType || "aggressive";
|
|
119
121
|
this.applyEnemyTypeBehavior(options);
|
|
120
122
|
this.attackSkill = options.attackSkill || null;
|
|
123
|
+
this.animations = {
|
|
124
|
+
...getActionBattleOptions().animations,
|
|
125
|
+
...options.animations
|
|
126
|
+
};
|
|
121
127
|
this.attackPatterns = options.attackPatterns || [
|
|
122
128
|
"melee",
|
|
123
129
|
"combo",
|
|
@@ -643,9 +649,15 @@ class BattleAi {
|
|
|
643
649
|
performMeleeAttack() {
|
|
644
650
|
if (!this.target) return;
|
|
645
651
|
this.faceTarget();
|
|
646
|
-
|
|
652
|
+
playActionBattleAnimation("attack", this.event, this.animations, {
|
|
653
|
+
target: this.target
|
|
654
|
+
});
|
|
647
655
|
if (this.attackSkill) {
|
|
648
656
|
try {
|
|
657
|
+
playActionBattleAnimation("castSkill", this.event, this.animations, {
|
|
658
|
+
skill: this.attackSkill,
|
|
659
|
+
target: this.target
|
|
660
|
+
});
|
|
649
661
|
this.event.useSkill(this.attackSkill, this.target);
|
|
650
662
|
} catch (e) {
|
|
651
663
|
this.performBasicHitbox();
|
|
@@ -810,7 +822,15 @@ class BattleAi {
|
|
|
810
822
|
if (!this.target) return;
|
|
811
823
|
this.chargingAttack = true;
|
|
812
824
|
this.faceTarget();
|
|
813
|
-
|
|
825
|
+
playActionBattleAnimation(
|
|
826
|
+
"attack",
|
|
827
|
+
this.event,
|
|
828
|
+
this.animations,
|
|
829
|
+
{
|
|
830
|
+
target: this.target
|
|
831
|
+
},
|
|
832
|
+
{ repeat: 2 }
|
|
833
|
+
);
|
|
814
834
|
setTimeout(() => {
|
|
815
835
|
if (!this.target || this.state !== "combat") {
|
|
816
836
|
this.chargingAttack = false;
|
|
@@ -818,6 +838,10 @@ class BattleAi {
|
|
|
818
838
|
}
|
|
819
839
|
if (this.attackSkill) {
|
|
820
840
|
try {
|
|
841
|
+
playActionBattleAnimation("castSkill", this.event, this.animations, {
|
|
842
|
+
skill: this.attackSkill,
|
|
843
|
+
target: this.target
|
|
844
|
+
});
|
|
821
845
|
this.event.useSkill(this.attackSkill, this.target);
|
|
822
846
|
} catch (e) {
|
|
823
847
|
this.performBasicHitbox();
|
|
@@ -832,7 +856,9 @@ class BattleAi {
|
|
|
832
856
|
* Perform zone attack (360 degrees)
|
|
833
857
|
*/
|
|
834
858
|
performZoneAttack() {
|
|
835
|
-
|
|
859
|
+
playActionBattleAnimation("attack", this.event, this.animations, {
|
|
860
|
+
target: this.target ?? void 0
|
|
861
|
+
});
|
|
836
862
|
const eventX = this.event.x();
|
|
837
863
|
const eventY = this.event.y();
|
|
838
864
|
const radius = 50;
|
|
@@ -1110,6 +1136,9 @@ class BattleAi {
|
|
|
1110
1136
|
cycles: 1
|
|
1111
1137
|
});
|
|
1112
1138
|
this.event.showHit(`-${damage}`);
|
|
1139
|
+
playActionBattleAnimation("hurt", this.event, this.animations, {
|
|
1140
|
+
attacker
|
|
1141
|
+
});
|
|
1113
1142
|
this.recentDamageTaken += damage;
|
|
1114
1143
|
if (this.state !== "stunned" && this.state !== "flee") {
|
|
1115
1144
|
this.debugLog("damage", "Stunned from damage");
|
|
@@ -1134,11 +1163,24 @@ class BattleAi {
|
|
|
1134
1163
|
* and removes the event from the map.
|
|
1135
1164
|
*/
|
|
1136
1165
|
kill(attacker) {
|
|
1166
|
+
const dieAnimation = playActionBattleAnimation(
|
|
1167
|
+
"die",
|
|
1168
|
+
this.event,
|
|
1169
|
+
this.animations,
|
|
1170
|
+
{
|
|
1171
|
+
attacker
|
|
1172
|
+
}
|
|
1173
|
+
);
|
|
1174
|
+
const removeDelay = getActionBattleAnimationRemovalDelay(dieAnimation);
|
|
1137
1175
|
if (this.onDefeatedCallback) {
|
|
1138
1176
|
this.onDefeatedCallback(this.event, attacker);
|
|
1139
1177
|
}
|
|
1140
1178
|
this.destroy();
|
|
1141
|
-
|
|
1179
|
+
if (removeDelay > 0) {
|
|
1180
|
+
setTimeout(() => this.event.remove(), removeDelay);
|
|
1181
|
+
} else {
|
|
1182
|
+
this.event.remove();
|
|
1183
|
+
}
|
|
1142
1184
|
}
|
|
1143
1185
|
/**
|
|
1144
1186
|
* Get distance between entities
|
package/dist/client/index4.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineModule, Control } from "@rpgjs/common";
|
|
2
|
-
import { normalizeActionBattleOptions } from "./index8.js";
|
|
3
|
-
import { manhattanDistance, parseAoeMask } from "./
|
|
2
|
+
import { normalizeActionBattleOptions, setActionBattleOptions } from "./index8.js";
|
|
3
|
+
import { manhattanDistance, parseAoeMask } from "./index10.js";
|
|
4
|
+
import { playActionBattleAnimation } from "./index9.js";
|
|
4
5
|
const RpgEvent = null;
|
|
5
6
|
const DEFAULT_KNOCKBACK = null;
|
|
6
7
|
const ACTION_BATTLE_ACTION_BAR_GUI_ID = "action-battle-action-bar";
|
|
@@ -188,13 +189,20 @@ const getEntityTile = (entity, tileSize) => {
|
|
|
188
189
|
return { x, y };
|
|
189
190
|
};
|
|
190
191
|
const handleActionBattleSkillUse = (player, skillId, target, options) => {
|
|
192
|
+
const skillData = resolveSkillData(player, skillId);
|
|
191
193
|
const map = player.getCurrentMap();
|
|
192
194
|
if (!map) {
|
|
195
|
+
playActionBattleAnimation("castSkill", player, options.animations, {
|
|
196
|
+
skill: skillData
|
|
197
|
+
});
|
|
193
198
|
player.useSkill(skillId);
|
|
194
199
|
return;
|
|
195
200
|
}
|
|
196
201
|
const targeting = resolveSkillTargeting(player, skillId, options);
|
|
197
202
|
if (!targeting || !target) {
|
|
203
|
+
playActionBattleAnimation("castSkill", player, options.animations, {
|
|
204
|
+
skill: skillData
|
|
205
|
+
});
|
|
198
206
|
player.useSkill(skillId);
|
|
199
207
|
return;
|
|
200
208
|
}
|
|
@@ -235,10 +243,15 @@ const handleActionBattleSkillUse = (player, skillId, target, options) => {
|
|
|
235
243
|
if (!options.targeting?.allowEmptyTarget && targets.length === 0) {
|
|
236
244
|
return;
|
|
237
245
|
}
|
|
246
|
+
playActionBattleAnimation("castSkill", player, options.animations, {
|
|
247
|
+
skill: skillData,
|
|
248
|
+
target: targets[0]
|
|
249
|
+
});
|
|
238
250
|
player.useSkill(skillId, targets);
|
|
239
251
|
};
|
|
240
252
|
const createActionBattleServer = (rawOptions = {}) => {
|
|
241
253
|
const options = normalizeActionBattleOptions(rawOptions);
|
|
254
|
+
setActionBattleOptions(options);
|
|
242
255
|
return defineModule({
|
|
243
256
|
player: {
|
|
244
257
|
/**
|
|
@@ -254,7 +267,7 @@ const createActionBattleServer = (rawOptions = {}) => {
|
|
|
254
267
|
*/
|
|
255
268
|
onInput(player, input) {
|
|
256
269
|
if (input.action == Control.Action) {
|
|
257
|
-
|
|
270
|
+
playActionBattleAnimation("attack", player, options.animations);
|
|
258
271
|
const playerX = player.x();
|
|
259
272
|
const playerY = player.y();
|
|
260
273
|
const direction = player.getDirection();
|
package/dist/client/index6.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useProps, useDefineProps, computed, h, Container, cond, Graphics } from "canvasengine";
|
|
2
2
|
import { inject, RpgClientEngine } from "@rpgjs/client";
|
|
3
3
|
import { actionBattleUiOptions, actionBattleTargetingState } from "./index7.js";
|
|
4
|
-
import { parseAoeMask } from "./
|
|
4
|
+
import { parseAoeMask } from "./index10.js";
|
|
5
5
|
function component($$props) {
|
|
6
6
|
useProps($$props);
|
|
7
7
|
const defineProps = useDefineProps($$props);
|
package/dist/client/index8.js
CHANGED
|
@@ -21,8 +21,10 @@ const DEFAULT_ACTION_BATTLE_OPTIONS = {
|
|
|
21
21
|
targeting: {
|
|
22
22
|
affects: "events",
|
|
23
23
|
allowEmptyTarget: true
|
|
24
|
-
}
|
|
24
|
+
},
|
|
25
|
+
animations: {}
|
|
25
26
|
};
|
|
27
|
+
let currentActionBattleOptions = DEFAULT_ACTION_BATTLE_OPTIONS;
|
|
26
28
|
function normalizeActionBattleOptions(options = {}) {
|
|
27
29
|
return {
|
|
28
30
|
ui: {
|
|
@@ -46,10 +48,22 @@ function normalizeActionBattleOptions(options = {}) {
|
|
|
46
48
|
targeting: {
|
|
47
49
|
...DEFAULT_ACTION_BATTLE_OPTIONS.targeting,
|
|
48
50
|
...options.targeting
|
|
51
|
+
},
|
|
52
|
+
animations: {
|
|
53
|
+
...DEFAULT_ACTION_BATTLE_OPTIONS.animations,
|
|
54
|
+
...options.animations
|
|
49
55
|
}
|
|
50
56
|
};
|
|
51
57
|
}
|
|
58
|
+
function setActionBattleOptions(options) {
|
|
59
|
+
currentActionBattleOptions = options;
|
|
60
|
+
}
|
|
61
|
+
function getActionBattleOptions() {
|
|
62
|
+
return currentActionBattleOptions;
|
|
63
|
+
}
|
|
52
64
|
export {
|
|
53
65
|
DEFAULT_ACTION_BATTLE_OPTIONS,
|
|
54
|
-
|
|
66
|
+
getActionBattleOptions,
|
|
67
|
+
normalizeActionBattleOptions,
|
|
68
|
+
setActionBattleOptions
|
|
55
69
|
};
|
package/dist/client/index9.js
CHANGED
|
@@ -1,30 +1,64 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
const DEFAULT_DIE_ANIMATION_DELAY_MS = 500;
|
|
2
|
+
const DEFAULT_ANIMATION_BY_KEY = {
|
|
3
|
+
attack: "attack",
|
|
4
|
+
hurt: "hurt",
|
|
5
|
+
die: "die",
|
|
6
|
+
castSkill: "skill"
|
|
5
7
|
};
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
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 });
|
|
8
|
+
function resolveActionBattleAnimation(key, entity, animations, context, defaults = {}) {
|
|
9
|
+
const defaultAnimationName = defaults.animationName ?? DEFAULT_ANIMATION_BY_KEY[key];
|
|
10
|
+
const defaultRepeat = defaults.repeat ?? 1;
|
|
11
|
+
const hasConfiguredAnimation = animations ? Object.prototype.hasOwnProperty.call(animations, key) : false;
|
|
12
|
+
if (!hasConfiguredAnimation && key !== "attack") {
|
|
13
|
+
return null;
|
|
23
14
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
const configured = hasConfiguredAnimation ? animations?.[key] : defaultAnimationName;
|
|
16
|
+
const result = typeof configured === "function" ? configured(entity, context) : configured;
|
|
17
|
+
if (result == null) return null;
|
|
18
|
+
if (typeof result === "string") {
|
|
19
|
+
return {
|
|
20
|
+
animationName: result,
|
|
21
|
+
repeat: defaultRepeat,
|
|
22
|
+
waitEnd: false
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const animationName = result.animationName ?? defaultAnimationName;
|
|
26
|
+
return {
|
|
27
|
+
animationName,
|
|
28
|
+
graphic: result.graphic,
|
|
29
|
+
repeat: result.repeat ?? defaultRepeat,
|
|
30
|
+
waitEnd: result.waitEnd ?? false,
|
|
31
|
+
delayMs: result.delayMs
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function playActionBattleAnimation(key, entity, animations, context, defaults = {}) {
|
|
35
|
+
const animation = resolveActionBattleAnimation(
|
|
36
|
+
key,
|
|
37
|
+
entity,
|
|
38
|
+
animations,
|
|
39
|
+
context,
|
|
40
|
+
defaults
|
|
41
|
+
);
|
|
42
|
+
if (!animation) return null;
|
|
43
|
+
if (animation.graphic !== void 0) {
|
|
44
|
+
entity.setGraphicAnimation(
|
|
45
|
+
animation.animationName,
|
|
46
|
+
animation.graphic,
|
|
47
|
+
animation.repeat
|
|
48
|
+
);
|
|
49
|
+
} else {
|
|
50
|
+
entity.setGraphicAnimation(animation.animationName, animation.repeat);
|
|
51
|
+
}
|
|
52
|
+
return animation;
|
|
53
|
+
}
|
|
54
|
+
function getActionBattleAnimationRemovalDelay(animation) {
|
|
55
|
+
if (!animation) return 0;
|
|
56
|
+
if (animation.delayMs !== void 0) return animation.delayMs;
|
|
57
|
+
return animation.waitEnd ? DEFAULT_DIE_ANIMATION_DELAY_MS : 0;
|
|
58
|
+
}
|
|
27
59
|
export {
|
|
28
|
-
|
|
29
|
-
|
|
60
|
+
DEFAULT_DIE_ANIMATION_DELAY_MS,
|
|
61
|
+
getActionBattleAnimationRemovalDelay,
|
|
62
|
+
playActionBattleAnimation,
|
|
63
|
+
resolveActionBattleAnimation
|
|
30
64
|
};
|
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import { ActionBattleOptions } from './types';
|
|
2
2
|
export declare const DEFAULT_ACTION_BATTLE_OPTIONS: ActionBattleOptions;
|
|
3
3
|
export declare function normalizeActionBattleOptions(options?: ActionBattleOptions): ActionBattleOptions;
|
|
4
|
+
export declare function setActionBattleOptions(options: ActionBattleOptions): void;
|
|
5
|
+
export declare function getActionBattleOptions(): ActionBattleOptions;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ActionBattleOptions } from './types';
|
|
2
2
|
export { BattleAi, AiState, EnemyType, AttackPattern, AiDebug, DEFAULT_KNOCKBACK } from './ai.server';
|
|
3
|
-
export type { HitResult, ApplyHitHooks } from './ai.server';
|
|
4
|
-
export type { ActionBattleOptions, ActionBattleActionBarData, ActionBattleActionBarItem, ActionBattleActionBarSkill, ActionBattleSkillTargeting, ActionBattleSkillTargetingResolver, ActionBattleUiOptions, ActionBattleUiActionBarOptions, ActionBattleUiTargetingOptions, } from './types';
|
|
3
|
+
export type { HitResult, ApplyHitHooks, BattleAiOptions } from './ai.server';
|
|
4
|
+
export type { ActionBattleAnimationContext, ActionBattleAnimationEntity, ActionBattleAnimationKey, ActionBattleAnimationOptions, ActionBattleAnimationResolver, ActionBattleAnimationResult, ActionBattleOptions, ActionBattleActionBarData, ActionBattleActionBarItem, ActionBattleActionBarSkill, ActionBattleSkillTargeting, ActionBattleSkillTargetingResolver, ActionBattleUiOptions, ActionBattleUiActionBarOptions, ActionBattleUiTargetingOptions, } from './types';
|
|
5
5
|
export { DEFAULT_PLAYER_ATTACK_HITBOXES, getPlayerWeaponKnockbackForce, applyPlayerHitToEvent, ACTION_BATTLE_ACTION_BAR_GUI_ID, openActionBattleActionBar, updateActionBattleActionBar, createActionBattleServer, } from './server';
|
|
6
6
|
export declare function provideActionBattle(options?: ActionBattleOptions): any;
|
|
7
7
|
declare const _default: {
|
package/dist/server/index2.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { RpgEvent } from "@rpgjs/server";
|
|
2
2
|
import { defineModule, Control } from "@rpgjs/common";
|
|
3
3
|
import { DEFAULT_KNOCKBACK } from "./index3.js";
|
|
4
|
-
import { normalizeActionBattleOptions } from "./index4.js";
|
|
4
|
+
import { normalizeActionBattleOptions, setActionBattleOptions } from "./index4.js";
|
|
5
5
|
import { manhattanDistance, parseAoeMask } from "./index5.js";
|
|
6
|
+
import { playActionBattleAnimation } from "./index6.js";
|
|
6
7
|
const ACTION_BATTLE_ACTION_BAR_GUI_ID = "action-battle-action-bar";
|
|
7
8
|
const DEFAULT_PLAYER_ATTACK_HITBOXES = {
|
|
8
9
|
up: { offsetX: -16, offsetY: -48, width: 32, height: 32 },
|
|
@@ -188,13 +189,20 @@ const getEntityTile = (entity, tileSize) => {
|
|
|
188
189
|
return { x, y };
|
|
189
190
|
};
|
|
190
191
|
const handleActionBattleSkillUse = (player, skillId, target, options) => {
|
|
192
|
+
const skillData = resolveSkillData(player, skillId);
|
|
191
193
|
const map = player.getCurrentMap();
|
|
192
194
|
if (!map) {
|
|
195
|
+
playActionBattleAnimation("castSkill", player, options.animations, {
|
|
196
|
+
skill: skillData
|
|
197
|
+
});
|
|
193
198
|
player.useSkill(skillId);
|
|
194
199
|
return;
|
|
195
200
|
}
|
|
196
201
|
const targeting = resolveSkillTargeting(player, skillId, options);
|
|
197
202
|
if (!targeting || !target) {
|
|
203
|
+
playActionBattleAnimation("castSkill", player, options.animations, {
|
|
204
|
+
skill: skillData
|
|
205
|
+
});
|
|
198
206
|
player.useSkill(skillId);
|
|
199
207
|
return;
|
|
200
208
|
}
|
|
@@ -235,10 +243,15 @@ const handleActionBattleSkillUse = (player, skillId, target, options) => {
|
|
|
235
243
|
if (!options.targeting?.allowEmptyTarget && targets.length === 0) {
|
|
236
244
|
return;
|
|
237
245
|
}
|
|
246
|
+
playActionBattleAnimation("castSkill", player, options.animations, {
|
|
247
|
+
skill: skillData,
|
|
248
|
+
target: targets[0]
|
|
249
|
+
});
|
|
238
250
|
player.useSkill(skillId, targets);
|
|
239
251
|
};
|
|
240
252
|
const createActionBattleServer = (rawOptions = {}) => {
|
|
241
253
|
const options = normalizeActionBattleOptions(rawOptions);
|
|
254
|
+
setActionBattleOptions(options);
|
|
242
255
|
return defineModule({
|
|
243
256
|
player: {
|
|
244
257
|
/**
|
|
@@ -254,7 +267,7 @@ const createActionBattleServer = (rawOptions = {}) => {
|
|
|
254
267
|
*/
|
|
255
268
|
onInput(player, input) {
|
|
256
269
|
if (input.action == Control.Action) {
|
|
257
|
-
|
|
270
|
+
playActionBattleAnimation("attack", player, options.animations);
|
|
258
271
|
const playerX = player.x();
|
|
259
272
|
const playerY = player.y();
|
|
260
273
|
const direction = player.getDirection();
|
package/dist/server/index3.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { MAXHP, RpgPlayer } from "@rpgjs/server";
|
|
2
|
+
import { playActionBattleAnimation, getActionBattleAnimationRemovalDelay } from "./index6.js";
|
|
3
|
+
import { getActionBattleOptions } from "./index4.js";
|
|
2
4
|
const AiDebug = {
|
|
3
5
|
/** Enable/disable all AI debug logs */
|
|
4
6
|
enabled: typeof process !== "undefined" && process.env?.RPGJS_DEBUG_AI === "1" || false,
|
|
@@ -117,6 +119,10 @@ class BattleAi {
|
|
|
117
119
|
this.enemyType = options.enemyType || "aggressive";
|
|
118
120
|
this.applyEnemyTypeBehavior(options);
|
|
119
121
|
this.attackSkill = options.attackSkill || null;
|
|
122
|
+
this.animations = {
|
|
123
|
+
...getActionBattleOptions().animations,
|
|
124
|
+
...options.animations
|
|
125
|
+
};
|
|
120
126
|
this.attackPatterns = options.attackPatterns || [
|
|
121
127
|
"melee",
|
|
122
128
|
"combo",
|
|
@@ -642,9 +648,15 @@ class BattleAi {
|
|
|
642
648
|
performMeleeAttack() {
|
|
643
649
|
if (!this.target) return;
|
|
644
650
|
this.faceTarget();
|
|
645
|
-
|
|
651
|
+
playActionBattleAnimation("attack", this.event, this.animations, {
|
|
652
|
+
target: this.target
|
|
653
|
+
});
|
|
646
654
|
if (this.attackSkill) {
|
|
647
655
|
try {
|
|
656
|
+
playActionBattleAnimation("castSkill", this.event, this.animations, {
|
|
657
|
+
skill: this.attackSkill,
|
|
658
|
+
target: this.target
|
|
659
|
+
});
|
|
648
660
|
this.event.useSkill(this.attackSkill, this.target);
|
|
649
661
|
} catch (e) {
|
|
650
662
|
this.performBasicHitbox();
|
|
@@ -809,7 +821,15 @@ class BattleAi {
|
|
|
809
821
|
if (!this.target) return;
|
|
810
822
|
this.chargingAttack = true;
|
|
811
823
|
this.faceTarget();
|
|
812
|
-
|
|
824
|
+
playActionBattleAnimation(
|
|
825
|
+
"attack",
|
|
826
|
+
this.event,
|
|
827
|
+
this.animations,
|
|
828
|
+
{
|
|
829
|
+
target: this.target
|
|
830
|
+
},
|
|
831
|
+
{ repeat: 2 }
|
|
832
|
+
);
|
|
813
833
|
setTimeout(() => {
|
|
814
834
|
if (!this.target || this.state !== "combat") {
|
|
815
835
|
this.chargingAttack = false;
|
|
@@ -817,6 +837,10 @@ class BattleAi {
|
|
|
817
837
|
}
|
|
818
838
|
if (this.attackSkill) {
|
|
819
839
|
try {
|
|
840
|
+
playActionBattleAnimation("castSkill", this.event, this.animations, {
|
|
841
|
+
skill: this.attackSkill,
|
|
842
|
+
target: this.target
|
|
843
|
+
});
|
|
820
844
|
this.event.useSkill(this.attackSkill, this.target);
|
|
821
845
|
} catch (e) {
|
|
822
846
|
this.performBasicHitbox();
|
|
@@ -831,7 +855,9 @@ class BattleAi {
|
|
|
831
855
|
* Perform zone attack (360 degrees)
|
|
832
856
|
*/
|
|
833
857
|
performZoneAttack() {
|
|
834
|
-
|
|
858
|
+
playActionBattleAnimation("attack", this.event, this.animations, {
|
|
859
|
+
target: this.target ?? void 0
|
|
860
|
+
});
|
|
835
861
|
const eventX = this.event.x();
|
|
836
862
|
const eventY = this.event.y();
|
|
837
863
|
const radius = 50;
|
|
@@ -1109,6 +1135,9 @@ class BattleAi {
|
|
|
1109
1135
|
cycles: 1
|
|
1110
1136
|
});
|
|
1111
1137
|
this.event.showHit(`-${damage}`);
|
|
1138
|
+
playActionBattleAnimation("hurt", this.event, this.animations, {
|
|
1139
|
+
attacker
|
|
1140
|
+
});
|
|
1112
1141
|
this.recentDamageTaken += damage;
|
|
1113
1142
|
if (this.state !== "stunned" && this.state !== "flee") {
|
|
1114
1143
|
this.debugLog("damage", "Stunned from damage");
|
|
@@ -1133,11 +1162,24 @@ class BattleAi {
|
|
|
1133
1162
|
* and removes the event from the map.
|
|
1134
1163
|
*/
|
|
1135
1164
|
kill(attacker) {
|
|
1165
|
+
const dieAnimation = playActionBattleAnimation(
|
|
1166
|
+
"die",
|
|
1167
|
+
this.event,
|
|
1168
|
+
this.animations,
|
|
1169
|
+
{
|
|
1170
|
+
attacker
|
|
1171
|
+
}
|
|
1172
|
+
);
|
|
1173
|
+
const removeDelay = getActionBattleAnimationRemovalDelay(dieAnimation);
|
|
1136
1174
|
if (this.onDefeatedCallback) {
|
|
1137
1175
|
this.onDefeatedCallback(this.event, attacker);
|
|
1138
1176
|
}
|
|
1139
1177
|
this.destroy();
|
|
1140
|
-
|
|
1178
|
+
if (removeDelay > 0) {
|
|
1179
|
+
setTimeout(() => this.event.remove(), removeDelay);
|
|
1180
|
+
} else {
|
|
1181
|
+
this.event.remove();
|
|
1182
|
+
}
|
|
1141
1183
|
}
|
|
1142
1184
|
/**
|
|
1143
1185
|
* Get distance between entities
|
package/dist/server/index4.js
CHANGED
|
@@ -21,8 +21,10 @@ const DEFAULT_ACTION_BATTLE_OPTIONS = {
|
|
|
21
21
|
targeting: {
|
|
22
22
|
affects: "events",
|
|
23
23
|
allowEmptyTarget: true
|
|
24
|
-
}
|
|
24
|
+
},
|
|
25
|
+
animations: {}
|
|
25
26
|
};
|
|
27
|
+
let currentActionBattleOptions = DEFAULT_ACTION_BATTLE_OPTIONS;
|
|
26
28
|
function normalizeActionBattleOptions(options = {}) {
|
|
27
29
|
return {
|
|
28
30
|
ui: {
|
|
@@ -46,10 +48,22 @@ function normalizeActionBattleOptions(options = {}) {
|
|
|
46
48
|
targeting: {
|
|
47
49
|
...DEFAULT_ACTION_BATTLE_OPTIONS.targeting,
|
|
48
50
|
...options.targeting
|
|
51
|
+
},
|
|
52
|
+
animations: {
|
|
53
|
+
...DEFAULT_ACTION_BATTLE_OPTIONS.animations,
|
|
54
|
+
...options.animations
|
|
49
55
|
}
|
|
50
56
|
};
|
|
51
57
|
}
|
|
58
|
+
function setActionBattleOptions(options) {
|
|
59
|
+
currentActionBattleOptions = options;
|
|
60
|
+
}
|
|
61
|
+
function getActionBattleOptions() {
|
|
62
|
+
return currentActionBattleOptions;
|
|
63
|
+
}
|
|
52
64
|
export {
|
|
53
65
|
DEFAULT_ACTION_BATTLE_OPTIONS,
|
|
54
|
-
|
|
66
|
+
getActionBattleOptions,
|
|
67
|
+
normalizeActionBattleOptions,
|
|
68
|
+
setActionBattleOptions
|
|
55
69
|
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const DEFAULT_DIE_ANIMATION_DELAY_MS = 500;
|
|
2
|
+
const DEFAULT_ANIMATION_BY_KEY = {
|
|
3
|
+
attack: "attack",
|
|
4
|
+
hurt: "hurt",
|
|
5
|
+
die: "die",
|
|
6
|
+
castSkill: "skill"
|
|
7
|
+
};
|
|
8
|
+
function resolveActionBattleAnimation(key, entity, animations, context, defaults = {}) {
|
|
9
|
+
const defaultAnimationName = defaults.animationName ?? DEFAULT_ANIMATION_BY_KEY[key];
|
|
10
|
+
const defaultRepeat = defaults.repeat ?? 1;
|
|
11
|
+
const hasConfiguredAnimation = animations ? Object.prototype.hasOwnProperty.call(animations, key) : false;
|
|
12
|
+
if (!hasConfiguredAnimation && key !== "attack") {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const configured = hasConfiguredAnimation ? animations?.[key] : defaultAnimationName;
|
|
16
|
+
const result = typeof configured === "function" ? configured(entity, context) : configured;
|
|
17
|
+
if (result == null) return null;
|
|
18
|
+
if (typeof result === "string") {
|
|
19
|
+
return {
|
|
20
|
+
animationName: result,
|
|
21
|
+
repeat: defaultRepeat,
|
|
22
|
+
waitEnd: false
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const animationName = result.animationName ?? defaultAnimationName;
|
|
26
|
+
return {
|
|
27
|
+
animationName,
|
|
28
|
+
graphic: result.graphic,
|
|
29
|
+
repeat: result.repeat ?? defaultRepeat,
|
|
30
|
+
waitEnd: result.waitEnd ?? false,
|
|
31
|
+
delayMs: result.delayMs
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function playActionBattleAnimation(key, entity, animations, context, defaults = {}) {
|
|
35
|
+
const animation = resolveActionBattleAnimation(
|
|
36
|
+
key,
|
|
37
|
+
entity,
|
|
38
|
+
animations,
|
|
39
|
+
context,
|
|
40
|
+
defaults
|
|
41
|
+
);
|
|
42
|
+
if (!animation) return null;
|
|
43
|
+
if (animation.graphic !== void 0) {
|
|
44
|
+
entity.setGraphicAnimation(
|
|
45
|
+
animation.animationName,
|
|
46
|
+
animation.graphic,
|
|
47
|
+
animation.repeat
|
|
48
|
+
);
|
|
49
|
+
} else {
|
|
50
|
+
entity.setGraphicAnimation(animation.animationName, animation.repeat);
|
|
51
|
+
}
|
|
52
|
+
return animation;
|
|
53
|
+
}
|
|
54
|
+
function getActionBattleAnimationRemovalDelay(animation) {
|
|
55
|
+
if (!animation) return 0;
|
|
56
|
+
if (animation.delayMs !== void 0) return animation.delayMs;
|
|
57
|
+
return animation.waitEnd ? DEFAULT_DIE_ANIMATION_DELAY_MS : 0;
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
DEFAULT_DIE_ANIMATION_DELAY_MS,
|
|
61
|
+
getActionBattleAnimationRemovalDelay,
|
|
62
|
+
playActionBattleAnimation,
|
|
63
|
+
resolveActionBattleAnimation
|
|
64
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpgjs/action-battle",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-beta.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
"description": "RPGJS is a framework for creating RPG/MMORPG games",
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"@canvasengine/presets": "*",
|
|
26
|
-
"@rpgjs/client": "5.0.0-
|
|
27
|
-
"@rpgjs/common": "5.0.0-
|
|
28
|
-
"@rpgjs/server": "5.0.0-
|
|
29
|
-
"@rpgjs/vite": "5.0.0-
|
|
26
|
+
"@rpgjs/client": "5.0.0-beta.2",
|
|
27
|
+
"@rpgjs/common": "5.0.0-beta.2",
|
|
28
|
+
"@rpgjs/server": "5.0.0-beta.2",
|
|
29
|
+
"@rpgjs/vite": "5.0.0-beta.2",
|
|
30
30
|
"canvasengine": "*"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
package/src/ai.server.ts
CHANGED
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
import { MAXHP, RpgEvent, RpgPlayer } from "@rpgjs/server";
|
|
2
|
+
import {
|
|
3
|
+
getActionBattleAnimationRemovalDelay,
|
|
4
|
+
playActionBattleAnimation,
|
|
5
|
+
} from "./animations";
|
|
6
|
+
import { getActionBattleOptions } from "./config";
|
|
7
|
+
import type { ActionBattleAnimationOptions } from "./types";
|
|
2
8
|
|
|
3
9
|
type RpgEventWithBattleAi = RpgEvent & {
|
|
4
10
|
battleAi: BattleAi;
|
|
5
11
|
};
|
|
6
12
|
|
|
13
|
+
export interface BattleAiOptions {
|
|
14
|
+
enemyType?: EnemyType;
|
|
15
|
+
attackCooldown?: number;
|
|
16
|
+
visionRange?: number;
|
|
17
|
+
attackRange?: number;
|
|
18
|
+
dodgeChance?: number;
|
|
19
|
+
dodgeCooldown?: number;
|
|
20
|
+
fleeThreshold?: number;
|
|
21
|
+
attackSkill?: any;
|
|
22
|
+
attackPatterns?: AttackPattern[];
|
|
23
|
+
patrolWaypoints?: Array<{ x: number; y: number }>;
|
|
24
|
+
groupBehavior?: boolean;
|
|
25
|
+
moveToCooldown?: number;
|
|
26
|
+
retreatCooldown?: number;
|
|
27
|
+
behavior?: {
|
|
28
|
+
baseScore?: number;
|
|
29
|
+
updateInterval?: number;
|
|
30
|
+
minStateDuration?: number;
|
|
31
|
+
assaultThreshold?: number;
|
|
32
|
+
retreatThreshold?: number;
|
|
33
|
+
};
|
|
34
|
+
animations?: ActionBattleAnimationOptions;
|
|
35
|
+
/** Callback called when the AI is defeated */
|
|
36
|
+
onDefeated?: (event: RpgEvent, attacker?: RpgPlayer) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
7
39
|
/**
|
|
8
40
|
* Hit result data returned after applying damage
|
|
9
41
|
*
|
|
@@ -258,6 +290,7 @@ export class BattleAi {
|
|
|
258
290
|
// Attack configuration
|
|
259
291
|
private attackSkill: any | null; // Skill to use for attacks
|
|
260
292
|
private attackPatterns: AttackPattern[];
|
|
293
|
+
private animations?: ActionBattleAnimationOptions;
|
|
261
294
|
private comboCount: number = 0;
|
|
262
295
|
private comboMax: number = 3;
|
|
263
296
|
private chargingAttack: boolean = false;
|
|
@@ -327,30 +360,7 @@ export class BattleAi {
|
|
|
327
360
|
*/
|
|
328
361
|
constructor(
|
|
329
362
|
event: RpgEventWithBattleAi,
|
|
330
|
-
options: {
|
|
331
|
-
enemyType?: EnemyType;
|
|
332
|
-
attackCooldown?: number;
|
|
333
|
-
visionRange?: number;
|
|
334
|
-
attackRange?: number;
|
|
335
|
-
dodgeChance?: number;
|
|
336
|
-
dodgeCooldown?: number;
|
|
337
|
-
fleeThreshold?: number;
|
|
338
|
-
attackSkill?: any;
|
|
339
|
-
attackPatterns?: AttackPattern[];
|
|
340
|
-
patrolWaypoints?: Array<{ x: number; y: number }>;
|
|
341
|
-
groupBehavior?: boolean;
|
|
342
|
-
moveToCooldown?: number;
|
|
343
|
-
retreatCooldown?: number;
|
|
344
|
-
behavior?: {
|
|
345
|
-
baseScore?: number;
|
|
346
|
-
updateInterval?: number;
|
|
347
|
-
minStateDuration?: number;
|
|
348
|
-
assaultThreshold?: number;
|
|
349
|
-
retreatThreshold?: number;
|
|
350
|
-
};
|
|
351
|
-
/** Callback called when the AI is defeated */
|
|
352
|
-
onDefeated?: (event: RpgEvent, attacker?: RpgPlayer) => void;
|
|
353
|
-
} = {}
|
|
363
|
+
options: BattleAiOptions = {}
|
|
354
364
|
) {
|
|
355
365
|
event.battleAi = this;
|
|
356
366
|
this.event = event;
|
|
@@ -361,6 +371,10 @@ export class BattleAi {
|
|
|
361
371
|
|
|
362
372
|
// Store attack skill reference
|
|
363
373
|
this.attackSkill = options.attackSkill || null;
|
|
374
|
+
this.animations = {
|
|
375
|
+
...getActionBattleOptions().animations,
|
|
376
|
+
...options.animations,
|
|
377
|
+
};
|
|
364
378
|
|
|
365
379
|
// Initialize attack patterns
|
|
366
380
|
this.attackPatterns = options.attackPatterns || [
|
|
@@ -867,11 +881,17 @@ export class BattleAi {
|
|
|
867
881
|
if (!this.target) return;
|
|
868
882
|
|
|
869
883
|
this.faceTarget();
|
|
870
|
-
this.event.
|
|
884
|
+
playActionBattleAnimation("attack", this.event, this.animations, {
|
|
885
|
+
target: this.target,
|
|
886
|
+
});
|
|
871
887
|
|
|
872
888
|
// Use skill if available
|
|
873
889
|
if (this.attackSkill) {
|
|
874
890
|
try {
|
|
891
|
+
playActionBattleAnimation("castSkill", this.event, this.animations, {
|
|
892
|
+
skill: this.attackSkill,
|
|
893
|
+
target: this.target,
|
|
894
|
+
});
|
|
875
895
|
this.event.useSkill(this.attackSkill, this.target);
|
|
876
896
|
} catch (e) {
|
|
877
897
|
// Skill failed (no SP, etc.) - fall back to basic attack
|
|
@@ -1066,7 +1086,15 @@ export class BattleAi {
|
|
|
1066
1086
|
|
|
1067
1087
|
this.chargingAttack = true;
|
|
1068
1088
|
this.faceTarget();
|
|
1069
|
-
|
|
1089
|
+
playActionBattleAnimation(
|
|
1090
|
+
"attack",
|
|
1091
|
+
this.event,
|
|
1092
|
+
this.animations,
|
|
1093
|
+
{
|
|
1094
|
+
target: this.target,
|
|
1095
|
+
},
|
|
1096
|
+
{ repeat: 2 }
|
|
1097
|
+
);
|
|
1070
1098
|
|
|
1071
1099
|
setTimeout(() => {
|
|
1072
1100
|
if (!this.target || this.state !== AiState.Combat) {
|
|
@@ -1077,6 +1105,10 @@ export class BattleAi {
|
|
|
1077
1105
|
// Charged attacks can use a stronger skill or wider hitbox
|
|
1078
1106
|
if (this.attackSkill) {
|
|
1079
1107
|
try {
|
|
1108
|
+
playActionBattleAnimation("castSkill", this.event, this.animations, {
|
|
1109
|
+
skill: this.attackSkill,
|
|
1110
|
+
target: this.target,
|
|
1111
|
+
});
|
|
1080
1112
|
this.event.useSkill(this.attackSkill, this.target);
|
|
1081
1113
|
} catch (e) {
|
|
1082
1114
|
this.performBasicHitbox();
|
|
@@ -1093,7 +1125,9 @@ export class BattleAi {
|
|
|
1093
1125
|
* Perform zone attack (360 degrees)
|
|
1094
1126
|
*/
|
|
1095
1127
|
private performZoneAttack() {
|
|
1096
|
-
this.event.
|
|
1128
|
+
playActionBattleAnimation("attack", this.event, this.animations, {
|
|
1129
|
+
target: this.target ?? undefined,
|
|
1130
|
+
});
|
|
1097
1131
|
|
|
1098
1132
|
const eventX = this.event.x();
|
|
1099
1133
|
const eventY = this.event.y();
|
|
@@ -1439,6 +1473,9 @@ export class BattleAi {
|
|
|
1439
1473
|
cycles: 1
|
|
1440
1474
|
});
|
|
1441
1475
|
this.event.showHit(`-${damage}`);
|
|
1476
|
+
playActionBattleAnimation("hurt", this.event, this.animations, {
|
|
1477
|
+
attacker,
|
|
1478
|
+
});
|
|
1442
1479
|
|
|
1443
1480
|
// Track damage
|
|
1444
1481
|
this.recentDamageTaken += damage;
|
|
@@ -1468,13 +1505,27 @@ export class BattleAi {
|
|
|
1468
1505
|
* and removes the event from the map.
|
|
1469
1506
|
*/
|
|
1470
1507
|
private kill(attacker?: RpgPlayer) {
|
|
1508
|
+
const dieAnimation = playActionBattleAnimation(
|
|
1509
|
+
"die",
|
|
1510
|
+
this.event,
|
|
1511
|
+
this.animations,
|
|
1512
|
+
{
|
|
1513
|
+
attacker,
|
|
1514
|
+
}
|
|
1515
|
+
);
|
|
1516
|
+
const removeDelay = getActionBattleAnimationRemovalDelay(dieAnimation);
|
|
1517
|
+
|
|
1471
1518
|
// Call onDefeated hook before cleanup
|
|
1472
1519
|
if (this.onDefeatedCallback) {
|
|
1473
1520
|
this.onDefeatedCallback(this.event, attacker);
|
|
1474
1521
|
}
|
|
1475
1522
|
|
|
1476
1523
|
this.destroy();
|
|
1477
|
-
|
|
1524
|
+
if (removeDelay > 0) {
|
|
1525
|
+
setTimeout(() => this.event.remove(), removeDelay);
|
|
1526
|
+
} else {
|
|
1527
|
+
this.event.remove();
|
|
1528
|
+
}
|
|
1478
1529
|
}
|
|
1479
1530
|
|
|
1480
1531
|
/**
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActionBattleAnimationContext,
|
|
3
|
+
ActionBattleAnimationEntity,
|
|
4
|
+
ActionBattleAnimationKey,
|
|
5
|
+
ActionBattleAnimationOptions,
|
|
6
|
+
} from "./types";
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_DIE_ANIMATION_DELAY_MS = 500;
|
|
9
|
+
|
|
10
|
+
export interface ResolvedActionBattleAnimation {
|
|
11
|
+
animationName: string;
|
|
12
|
+
graphic?: string | string[];
|
|
13
|
+
repeat: number;
|
|
14
|
+
waitEnd: boolean;
|
|
15
|
+
delayMs?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ActionBattleAnimationDefaults {
|
|
19
|
+
animationName?: string;
|
|
20
|
+
repeat?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const DEFAULT_ANIMATION_BY_KEY: Record<ActionBattleAnimationKey, string> = {
|
|
24
|
+
attack: "attack",
|
|
25
|
+
hurt: "hurt",
|
|
26
|
+
die: "die",
|
|
27
|
+
castSkill: "skill",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export function resolveActionBattleAnimation(
|
|
31
|
+
key: ActionBattleAnimationKey,
|
|
32
|
+
entity: ActionBattleAnimationEntity,
|
|
33
|
+
animations?: ActionBattleAnimationOptions,
|
|
34
|
+
context?: ActionBattleAnimationContext,
|
|
35
|
+
defaults: ActionBattleAnimationDefaults = {}
|
|
36
|
+
): ResolvedActionBattleAnimation | null {
|
|
37
|
+
const defaultAnimationName =
|
|
38
|
+
defaults.animationName ?? DEFAULT_ANIMATION_BY_KEY[key];
|
|
39
|
+
const defaultRepeat = defaults.repeat ?? 1;
|
|
40
|
+
const hasConfiguredAnimation = animations
|
|
41
|
+
? Object.prototype.hasOwnProperty.call(animations, key)
|
|
42
|
+
: false;
|
|
43
|
+
if (!hasConfiguredAnimation && key !== "attack") {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const configured = hasConfiguredAnimation
|
|
48
|
+
? animations?.[key]
|
|
49
|
+
: defaultAnimationName;
|
|
50
|
+
const result =
|
|
51
|
+
typeof configured === "function"
|
|
52
|
+
? configured(entity, context)
|
|
53
|
+
: configured;
|
|
54
|
+
|
|
55
|
+
if (result == null) return null;
|
|
56
|
+
|
|
57
|
+
if (typeof result === "string") {
|
|
58
|
+
return {
|
|
59
|
+
animationName: result,
|
|
60
|
+
repeat: defaultRepeat,
|
|
61
|
+
waitEnd: false,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const animationName = result.animationName ?? defaultAnimationName;
|
|
66
|
+
return {
|
|
67
|
+
animationName,
|
|
68
|
+
graphic: result.graphic,
|
|
69
|
+
repeat: result.repeat ?? defaultRepeat,
|
|
70
|
+
waitEnd: result.waitEnd ?? false,
|
|
71
|
+
delayMs: result.delayMs,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function playActionBattleAnimation(
|
|
76
|
+
key: ActionBattleAnimationKey,
|
|
77
|
+
entity: ActionBattleAnimationEntity,
|
|
78
|
+
animations?: ActionBattleAnimationOptions,
|
|
79
|
+
context?: ActionBattleAnimationContext,
|
|
80
|
+
defaults: ActionBattleAnimationDefaults = {}
|
|
81
|
+
): ResolvedActionBattleAnimation | null {
|
|
82
|
+
const animation = resolveActionBattleAnimation(
|
|
83
|
+
key,
|
|
84
|
+
entity,
|
|
85
|
+
animations,
|
|
86
|
+
context,
|
|
87
|
+
defaults
|
|
88
|
+
);
|
|
89
|
+
if (!animation) return null;
|
|
90
|
+
|
|
91
|
+
if (animation.graphic !== undefined) {
|
|
92
|
+
entity.setGraphicAnimation(
|
|
93
|
+
animation.animationName,
|
|
94
|
+
animation.graphic,
|
|
95
|
+
animation.repeat
|
|
96
|
+
);
|
|
97
|
+
} else {
|
|
98
|
+
entity.setGraphicAnimation(animation.animationName, animation.repeat);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return animation;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getActionBattleAnimationRemovalDelay(
|
|
105
|
+
animation: ResolvedActionBattleAnimation | null
|
|
106
|
+
): number {
|
|
107
|
+
if (!animation) return 0;
|
|
108
|
+
if (animation.delayMs !== undefined) return animation.delayMs;
|
|
109
|
+
return animation.waitEnd ? DEFAULT_DIE_ANIMATION_DELAY_MS : 0;
|
|
110
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -24,8 +24,12 @@ export const DEFAULT_ACTION_BATTLE_OPTIONS: ActionBattleOptions = {
|
|
|
24
24
|
affects: "events",
|
|
25
25
|
allowEmptyTarget: true,
|
|
26
26
|
},
|
|
27
|
+
animations: {},
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
let currentActionBattleOptions: ActionBattleOptions =
|
|
31
|
+
DEFAULT_ACTION_BATTLE_OPTIONS;
|
|
32
|
+
|
|
29
33
|
export function normalizeActionBattleOptions(
|
|
30
34
|
options: ActionBattleOptions = {}
|
|
31
35
|
): ActionBattleOptions {
|
|
@@ -52,5 +56,17 @@ export function normalizeActionBattleOptions(
|
|
|
52
56
|
...DEFAULT_ACTION_BATTLE_OPTIONS.targeting,
|
|
53
57
|
...options.targeting,
|
|
54
58
|
},
|
|
59
|
+
animations: {
|
|
60
|
+
...DEFAULT_ACTION_BATTLE_OPTIONS.animations,
|
|
61
|
+
...options.animations,
|
|
62
|
+
},
|
|
55
63
|
};
|
|
56
64
|
}
|
|
65
|
+
|
|
66
|
+
export function setActionBattleOptions(options: ActionBattleOptions) {
|
|
67
|
+
currentActionBattleOptions = options;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getActionBattleOptions(): ActionBattleOptions {
|
|
71
|
+
return currentActionBattleOptions;
|
|
72
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,8 +7,14 @@ import type { ActionBattleOptions } from "./types";
|
|
|
7
7
|
export { BattleAi, AiState, EnemyType, AttackPattern, AiDebug, DEFAULT_KNOCKBACK } from "./ai.server";
|
|
8
8
|
|
|
9
9
|
// Types exports
|
|
10
|
-
export type { HitResult, ApplyHitHooks } from "./ai.server";
|
|
10
|
+
export type { HitResult, ApplyHitHooks, BattleAiOptions } from "./ai.server";
|
|
11
11
|
export type {
|
|
12
|
+
ActionBattleAnimationContext,
|
|
13
|
+
ActionBattleAnimationEntity,
|
|
14
|
+
ActionBattleAnimationKey,
|
|
15
|
+
ActionBattleAnimationOptions,
|
|
16
|
+
ActionBattleAnimationResolver,
|
|
17
|
+
ActionBattleAnimationResult,
|
|
12
18
|
ActionBattleOptions,
|
|
13
19
|
ActionBattleActionBarData,
|
|
14
20
|
ActionBattleActionBarItem,
|
package/src/server.ts
CHANGED
|
@@ -6,8 +6,9 @@ import {
|
|
|
6
6
|
ActionBattleActionBarSkill,
|
|
7
7
|
ActionBattleOptions,
|
|
8
8
|
} from "./types";
|
|
9
|
-
import { normalizeActionBattleOptions } from "./config";
|
|
9
|
+
import { normalizeActionBattleOptions, setActionBattleOptions } from "./config";
|
|
10
10
|
import { manhattanDistance, parseAoeMask } from "./targeting";
|
|
11
|
+
import { playActionBattleAnimation } from "./animations";
|
|
11
12
|
|
|
12
13
|
export const ACTION_BATTLE_ACTION_BAR_GUI_ID = "action-battle-action-bar";
|
|
13
14
|
|
|
@@ -330,13 +331,21 @@ const handleActionBattleSkillUse = (
|
|
|
330
331
|
target: { x: number; y: number } | undefined,
|
|
331
332
|
options: ActionBattleOptions
|
|
332
333
|
) => {
|
|
334
|
+
const skillData = resolveSkillData(player, skillId);
|
|
335
|
+
|
|
333
336
|
const map = player.getCurrentMap();
|
|
334
337
|
if (!map) {
|
|
338
|
+
playActionBattleAnimation("castSkill", player, options.animations, {
|
|
339
|
+
skill: skillData,
|
|
340
|
+
});
|
|
335
341
|
player.useSkill(skillId);
|
|
336
342
|
return;
|
|
337
343
|
}
|
|
338
344
|
const targeting = resolveSkillTargeting(player, skillId, options);
|
|
339
345
|
if (!targeting || !target) {
|
|
346
|
+
playActionBattleAnimation("castSkill", player, options.animations, {
|
|
347
|
+
skill: skillData,
|
|
348
|
+
});
|
|
340
349
|
player.useSkill(skillId);
|
|
341
350
|
return;
|
|
342
351
|
}
|
|
@@ -383,6 +392,10 @@ const handleActionBattleSkillUse = (
|
|
|
383
392
|
return;
|
|
384
393
|
}
|
|
385
394
|
|
|
395
|
+
playActionBattleAnimation("castSkill", player, options.animations, {
|
|
396
|
+
skill: skillData,
|
|
397
|
+
target: targets[0],
|
|
398
|
+
});
|
|
386
399
|
player.useSkill(skillId, targets as any);
|
|
387
400
|
};
|
|
388
401
|
|
|
@@ -390,6 +403,7 @@ export const createActionBattleServer = (
|
|
|
390
403
|
rawOptions: ActionBattleOptions = {}
|
|
391
404
|
) => {
|
|
392
405
|
const options = normalizeActionBattleOptions(rawOptions);
|
|
406
|
+
setActionBattleOptions(options);
|
|
393
407
|
return defineModule<RpgServer>({
|
|
394
408
|
player: {
|
|
395
409
|
/**
|
|
@@ -405,8 +419,7 @@ export const createActionBattleServer = (
|
|
|
405
419
|
*/
|
|
406
420
|
onInput(player: RpgPlayer, input: any) {
|
|
407
421
|
if (input.action == Control.Action) {
|
|
408
|
-
|
|
409
|
-
player.setGraphicAnimation("attack", 1);
|
|
422
|
+
playActionBattleAnimation("attack", player, options.animations);
|
|
410
423
|
|
|
411
424
|
// Get player position
|
|
412
425
|
const playerX = player.x();
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,52 @@ export type ActionBattleActionBarMode = "items" | "skills" | "both";
|
|
|
4
4
|
|
|
5
5
|
export type ActionBattleTargetingAffects = "events" | "players" | "both";
|
|
6
6
|
|
|
7
|
+
export type ActionBattleAnimationKey =
|
|
8
|
+
| "attack"
|
|
9
|
+
| "hurt"
|
|
10
|
+
| "die"
|
|
11
|
+
| "castSkill";
|
|
12
|
+
|
|
13
|
+
export type ActionBattleAnimationResult =
|
|
14
|
+
| string
|
|
15
|
+
| {
|
|
16
|
+
animationName?: string;
|
|
17
|
+
graphic?: string | string[];
|
|
18
|
+
repeat?: number;
|
|
19
|
+
waitEnd?: boolean;
|
|
20
|
+
delayMs?: number;
|
|
21
|
+
}
|
|
22
|
+
| null
|
|
23
|
+
| undefined;
|
|
24
|
+
|
|
25
|
+
export type ActionBattleAnimationEntity = {
|
|
26
|
+
setGraphicAnimation(animationName: string, repeat: number): void;
|
|
27
|
+
setGraphicAnimation(
|
|
28
|
+
animationName: string,
|
|
29
|
+
graphic: string | string[],
|
|
30
|
+
repeat: number
|
|
31
|
+
): void;
|
|
32
|
+
[key: string]: any;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export interface ActionBattleAnimationContext {
|
|
36
|
+
skill?: any;
|
|
37
|
+
attacker?: ActionBattleAnimationEntity;
|
|
38
|
+
target?: ActionBattleAnimationEntity;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ActionBattleAnimationResolver = (
|
|
42
|
+
entity: ActionBattleAnimationEntity,
|
|
43
|
+
context?: ActionBattleAnimationContext
|
|
44
|
+
) => ActionBattleAnimationResult;
|
|
45
|
+
|
|
46
|
+
export type ActionBattleAnimationOptions = Partial<
|
|
47
|
+
Record<
|
|
48
|
+
ActionBattleAnimationKey,
|
|
49
|
+
ActionBattleAnimationResult | ActionBattleAnimationResolver
|
|
50
|
+
>
|
|
51
|
+
>;
|
|
52
|
+
|
|
7
53
|
export interface ActionBattleSkillTargeting {
|
|
8
54
|
range: number;
|
|
9
55
|
aoeMask?: ActionBattleAoeMask;
|
|
@@ -49,6 +95,7 @@ export interface ActionBattleOptions {
|
|
|
49
95
|
ui?: ActionBattleUiOptions;
|
|
50
96
|
skills?: ActionBattleSkillOptions;
|
|
51
97
|
targeting?: ActionBattleTargetingOptions;
|
|
98
|
+
animations?: ActionBattleAnimationOptions;
|
|
52
99
|
}
|
|
53
100
|
|
|
54
101
|
export interface ActionBattleActionBarItem {
|