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

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.
@@ -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
- // Trigger attack animation
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 {