@rpgjs/action-battle 5.0.0-beta.2 → 5.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +137 -0
- package/dist/ai.server.d.ts +8 -1
- package/dist/client/index.js +19 -31
- package/dist/client/index10.js +142 -29
- package/dist/client/index11.js +25 -0
- package/dist/client/index12.js +1222 -0
- package/dist/client/index13.js +46 -0
- package/dist/client/index14.js +10 -0
- package/dist/client/index15.js +448 -0
- package/dist/client/index2.js +93 -46
- package/dist/client/index3.js +82 -1329
- package/dist/client/index4.js +305 -344
- package/dist/client/index5.js +36 -291
- package/dist/client/index6.js +99 -95
- package/dist/client/index7.js +78 -61
- package/dist/client/index8.js +57 -65
- package/dist/client/index9.js +97 -62
- package/dist/client.d.ts +3 -2
- package/dist/core/context.d.ts +5 -0
- package/dist/core/defaults.d.ts +81 -0
- package/dist/core/hit.d.ts +2 -0
- package/dist/enemies/factory.d.ts +7 -0
- package/dist/index.d.ts +9 -4
- package/dist/server/index.js +18 -31
- package/dist/server/index10.js +10 -0
- package/dist/server/index2.js +59 -345
- package/dist/server/index3.js +92 -1329
- package/dist/server/index4.js +141 -67
- package/dist/server/index5.js +24 -29
- package/dist/server/index6.js +1219 -62
- package/dist/server/index7.js +37 -0
- package/dist/server/index8.js +46 -0
- package/dist/server/index9.js +447 -0
- package/dist/server.d.ts +5 -3
- package/dist/ui/state.d.ts +20 -3
- package/package.json +5 -5
- package/src/ai.server.ts +91 -24
- package/src/animations.ts +43 -4
- package/src/canvas-engine-shim.ts +4 -0
- package/src/client.ts +122 -2
- package/src/components/action-bar.ce +5 -3
- package/src/components/attack-preview.ce +90 -0
- package/src/config.ts +30 -0
- package/src/core/context.ts +35 -0
- package/src/core/contracts.ts +123 -0
- package/src/core/defaults.ts +162 -0
- package/src/core/hit.spec.ts +58 -0
- package/src/core/hit.ts +66 -0
- package/src/enemies/factory.ts +25 -0
- package/src/index.ts +40 -0
- package/src/server.ts +235 -71
- package/src/targeting.spec.ts +24 -0
- package/src/types/canvas-engine.d.ts +4 -0
- package/src/types.ts +46 -1
- package/src/ui/state.ts +57 -0
package/README.md
CHANGED
|
@@ -7,6 +7,7 @@ The AI controller manages **behavior only** - all stats (HP, ATK, skills, items,
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **State Machine AI**: Enemies with dynamic behaviors (Idle, Alert, Combat, Flee, Stunned)
|
|
10
|
+
- **Plugin-first architecture**: Replace damage, hitboxes, knockback, hooks, and AI behaviors independently
|
|
10
11
|
- **Multiple Enemy Types**: Aggressive, Defensive, Ranged, Tank, Berserker
|
|
11
12
|
- **Attack Patterns**: Melee, Combo, Charged, Zone, Dash Attack
|
|
12
13
|
- **Skill Support**: AI can use any RPGJS skill
|
|
@@ -22,6 +23,90 @@ The AI controller manages **behavior only** - all stats (HP, ATK, skills, items,
|
|
|
22
23
|
npm install @rpgjs/action-battle
|
|
23
24
|
```
|
|
24
25
|
|
|
26
|
+
## Plugin-First Customization
|
|
27
|
+
|
|
28
|
+
`provideActionBattle()` ships with Zelda-like defaults, but each combat system
|
|
29
|
+
can be replaced without rewriting the module.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { provideActionBattle } from "@rpgjs/action-battle/server";
|
|
33
|
+
|
|
34
|
+
export default provideActionBattle({
|
|
35
|
+
attack: {
|
|
36
|
+
lockMovement: true,
|
|
37
|
+
lockDurationMs: 280,
|
|
38
|
+
hitboxes: {
|
|
39
|
+
right: { offsetX: 18, offsetY: -18, width: 42, height: 36 }
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
systems: {
|
|
43
|
+
combat: {
|
|
44
|
+
damage({ attacker, target, skill }) {
|
|
45
|
+
const raw = target.applyDamage(attacker, skill);
|
|
46
|
+
return {
|
|
47
|
+
damage: raw.damage,
|
|
48
|
+
defeated: target.hp <= 0,
|
|
49
|
+
raw
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
knockback({ attacker, target }) {
|
|
53
|
+
const dx = target.x() - attacker.x();
|
|
54
|
+
const dy = target.y() - attacker.y();
|
|
55
|
+
const distance = Math.max(1, Math.sqrt(dx * dx + dy * dy));
|
|
56
|
+
return {
|
|
57
|
+
force: 70,
|
|
58
|
+
duration: 220,
|
|
59
|
+
direction: { x: dx / distance, y: dy / distance }
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
hooks: {
|
|
63
|
+
beforeHit(context) {
|
|
64
|
+
// Return false to cancel a hit, or return a modified context.
|
|
65
|
+
return context;
|
|
66
|
+
},
|
|
67
|
+
afterHit(result) {
|
|
68
|
+
console.log(`Damage: ${result.damage}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
ai: {
|
|
73
|
+
behaviors: {
|
|
74
|
+
slime({ hpPercent }) {
|
|
75
|
+
return {
|
|
76
|
+
mode: hpPercent !== null && hpPercent < 0.25 ? "retreat" : "assault",
|
|
77
|
+
attackCooldown: 900
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The main extension contracts are:
|
|
87
|
+
|
|
88
|
+
- `ActionBattleCombatSystem`: resolves hitboxes, damage, knockback, and hooks.
|
|
89
|
+
- `ActionBattleAiBehavior`: returns lightweight AI decisions from event state.
|
|
90
|
+
- `ActionBattleHitHooks`: `beforeHit`, `afterDamage`, and `afterHit`.
|
|
91
|
+
|
|
92
|
+
Use `createActionEnemy()` when you want data-driven enemy presets:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { createActionEnemy, EnemyType } from "@rpgjs/action-battle/server";
|
|
96
|
+
|
|
97
|
+
const enemyPresets = {
|
|
98
|
+
slime: {
|
|
99
|
+
enemyType: EnemyType.Aggressive,
|
|
100
|
+
behaviorKey: "slime",
|
|
101
|
+
stats(event) {
|
|
102
|
+
event.hp = 40;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
createActionEnemy(this, "slime", enemyPresets);
|
|
108
|
+
```
|
|
109
|
+
|
|
25
110
|
## Quick Start
|
|
26
111
|
|
|
27
112
|
```typescript
|
|
@@ -496,6 +581,36 @@ The module handles player attacks via the `action` input:
|
|
|
496
581
|
// Knockback force is based on equipped weapon's knockbackForce property
|
|
497
582
|
```
|
|
498
583
|
|
|
584
|
+
By default, the player is locked in place for `350ms` when attacking, similar
|
|
585
|
+
to classic A-RPG combat where the attack resolves before movement resumes.
|
|
586
|
+
|
|
587
|
+
```ts
|
|
588
|
+
provideActionBattle({
|
|
589
|
+
attack: {
|
|
590
|
+
lockMovement: true,
|
|
591
|
+
lockDurationMs: 350,
|
|
592
|
+
showPreview: true,
|
|
593
|
+
previewDurationMs: 180,
|
|
594
|
+
previewColor: 0xfff3b0,
|
|
595
|
+
previewAccentColor: 0xffffff
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
Set `lockMovement` to `false` if your game should allow moving attacks.
|
|
601
|
+
The client also stops predicted movement immediately and shows a short slash
|
|
602
|
+
preview so the action feels responsive even before the server hit resolves. Set
|
|
603
|
+
`showPreview` to `false` if your project uses only spritesheet combat
|
|
604
|
+
animations.
|
|
605
|
+
|
|
606
|
+
Player attacks are resolved with `createMovingHitbox()` instead of a passive
|
|
607
|
+
contact collision. You can still customize the generated hitboxes with
|
|
608
|
+
`attack.hitboxes` or `attack.resolveHitboxes`.
|
|
609
|
+
|
|
610
|
+
When the action targets a normal event with no `BattleAi`, the server lets the
|
|
611
|
+
event handle `onAction` and does not create the combat hitbox. Enemy events
|
|
612
|
+
with `BattleAi` still trigger the A-RPG attack.
|
|
613
|
+
|
|
499
614
|
## Configurable Combat Animations
|
|
500
615
|
|
|
501
616
|
By default, player and AI attacks keep using the existing `attack` animation:
|
|
@@ -524,6 +639,28 @@ export default provideActionBattle({
|
|
|
524
639
|
});
|
|
525
640
|
```
|
|
526
641
|
|
|
642
|
+
RPGJS Studio stores combat animations as spritesheet media ids. If
|
|
643
|
+
`provideStudioGame()` is installed, `createStudioActionBattleAnimations()` can
|
|
644
|
+
read the project animations attached to the player at runtime. By default, the
|
|
645
|
+
helper plays Studio attack spritesheets with
|
|
646
|
+
`setGraphicAnimation("attack", graphic, 1)`:
|
|
647
|
+
|
|
648
|
+
```ts
|
|
649
|
+
import { provideActionBattle } from "@rpgjs/action-battle/server";
|
|
650
|
+
import { createStudioActionBattleAnimations } from "@rpgjs/studio/server";
|
|
651
|
+
|
|
652
|
+
export default provideActionBattle({
|
|
653
|
+
animations: createStudioActionBattleAnimations()
|
|
654
|
+
});
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
You can also pass a static Studio animation object to override the media ids
|
|
658
|
+
manually. Animation values may be media ids or media objects returned by the
|
|
659
|
+
Studio game API.
|
|
660
|
+
|
|
661
|
+
The Studio field `castSpell` is accepted as an alias for action-battle's
|
|
662
|
+
`castSkill` animation key.
|
|
663
|
+
|
|
527
664
|
For data-driven spritesheets, use resolver functions:
|
|
528
665
|
|
|
529
666
|
```ts
|
package/dist/ai.server.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { RpgEvent, RpgPlayer } from '@rpgjs/server';
|
|
2
|
+
import { ActionBattleDamageResult } from './core/contracts';
|
|
2
3
|
import { ActionBattleAnimationOptions } from './types';
|
|
3
4
|
type RpgEventWithBattleAi = RpgEvent & {
|
|
4
|
-
battleAi
|
|
5
|
+
battleAi?: BattleAi;
|
|
5
6
|
};
|
|
6
7
|
export interface BattleAiOptions {
|
|
7
8
|
enemyType?: EnemyType;
|
|
@@ -27,6 +28,7 @@ export interface BattleAiOptions {
|
|
|
27
28
|
assaultThreshold?: number;
|
|
28
29
|
retreatThreshold?: number;
|
|
29
30
|
};
|
|
31
|
+
behaviorKey?: string;
|
|
30
32
|
animations?: ActionBattleAnimationOptions;
|
|
31
33
|
/** Callback called when the AI is defeated */
|
|
32
34
|
onDefeated?: (event: RpgEvent, attacker?: RpgPlayer) => void;
|
|
@@ -277,6 +279,8 @@ export declare class BattleAi {
|
|
|
277
279
|
private lastMoveToTime;
|
|
278
280
|
private retreatCooldown;
|
|
279
281
|
private lastRetreatTime;
|
|
282
|
+
private timers;
|
|
283
|
+
private behaviorKey?;
|
|
280
284
|
/**
|
|
281
285
|
* Create a new Battle AI Controller
|
|
282
286
|
*
|
|
@@ -483,6 +487,7 @@ export declare class BattleAi {
|
|
|
483
487
|
* The actual damage is applied externally via RPGJS API.
|
|
484
488
|
*/
|
|
485
489
|
takeDamage(attacker: RpgPlayer): boolean;
|
|
490
|
+
handleDamage(attacker: RpgPlayer, damageResult: ActionBattleDamageResult): boolean;
|
|
486
491
|
/**
|
|
487
492
|
* Kill this AI
|
|
488
493
|
*
|
|
@@ -495,9 +500,11 @@ export declare class BattleAi {
|
|
|
495
500
|
*/
|
|
496
501
|
private getDistance;
|
|
497
502
|
private updateBehavior;
|
|
503
|
+
private applyCustomBehavior;
|
|
498
504
|
private handleTacticalMovement;
|
|
499
505
|
private handleAssaultMovement;
|
|
500
506
|
private requestMoveTo;
|
|
507
|
+
private schedule;
|
|
501
508
|
getHealth(): number;
|
|
502
509
|
getMaxHealth(): number;
|
|
503
510
|
getTarget(): InstanceType<typeof RpgPlayer> | null;
|
package/dist/client/index.js
CHANGED
|
@@ -1,35 +1,23 @@
|
|
|
1
|
-
import
|
|
1
|
+
import client_default, { createActionBattleClient } from "./index9.js";
|
|
2
|
+
import { DEFAULT_ZELDA_PLAYER_HITBOXES, createDefaultPlayerHitboxResolver, defaultCombatSystem, defaultEnemyBehaviors, defaultKnockbackResolver, defaultRpgjsDamageResolver } from "./index10.js";
|
|
3
|
+
import { createActionBattleSystems, getActionBattleSystems } from "./index11.js";
|
|
4
|
+
import { AiDebug, AiState, AttackPattern, BattleAi, DEFAULT_KNOCKBACK, EnemyType } from "./index12.js";
|
|
5
|
+
import { applyActionBattleHit } from "./index13.js";
|
|
6
|
+
import { createActionEnemy } from "./index14.js";
|
|
7
|
+
import { ACTION_BATTLE_ACTION_BAR_GUI_ID, DEFAULT_PLAYER_ATTACK_HITBOXES, applyPlayerHitToEvent, createActionBattleServer, getPlayerWeaponKnockbackForce, openActionBattleActionBar, updateActionBattleActionBar } from "./index15.js";
|
|
2
8
|
import { createModule } from "@rpgjs/common";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const createActionBattleServer2 = null;
|
|
9
|
+
//#region src/index.ts
|
|
10
|
+
var server = null;
|
|
11
|
+
var createActionBattleServer$1 = null;
|
|
7
12
|
function provideActionBattle(options = {}) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
]);
|
|
13
|
+
return createModule("ActionBattle", [{
|
|
14
|
+
server: createActionBattleServer$1?.(options),
|
|
15
|
+
client: createActionBattleClient?.(options)
|
|
16
|
+
}]);
|
|
14
17
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
};
|
|
19
|
-
export {
|
|
20
|
-
ACTION_BATTLE_ACTION_BAR_GUI_ID,
|
|
21
|
-
AiDebug,
|
|
22
|
-
AiState,
|
|
23
|
-
AttackPattern,
|
|
24
|
-
BattleAi,
|
|
25
|
-
DEFAULT_KNOCKBACK,
|
|
26
|
-
DEFAULT_PLAYER_ATTACK_HITBOXES,
|
|
27
|
-
EnemyType,
|
|
28
|
-
applyPlayerHitToEvent,
|
|
29
|
-
createActionBattleServer,
|
|
30
|
-
index as default,
|
|
31
|
-
getPlayerWeaponKnockbackForce,
|
|
32
|
-
openActionBattleActionBar,
|
|
33
|
-
provideActionBattle,
|
|
34
|
-
updateActionBattleActionBar
|
|
18
|
+
var src_default = {
|
|
19
|
+
server,
|
|
20
|
+
client: client_default
|
|
35
21
|
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export { ACTION_BATTLE_ACTION_BAR_GUI_ID, AiDebug, AiState, AttackPattern, BattleAi, DEFAULT_KNOCKBACK, DEFAULT_PLAYER_ATTACK_HITBOXES, DEFAULT_ZELDA_PLAYER_HITBOXES, EnemyType, applyActionBattleHit, applyPlayerHitToEvent, createActionBattleServer, createActionBattleSystems, createActionEnemy, createDefaultPlayerHitboxResolver, src_default as default, defaultCombatSystem, defaultEnemyBehaviors, defaultKnockbackResolver, defaultRpgjsDamageResolver, getActionBattleSystems, getPlayerWeaponKnockbackForce, openActionBattleActionBar, provideActionBattle, updateActionBattleActionBar };
|
package/dist/client/index10.js
CHANGED
|
@@ -1,30 +1,143 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
//#region src/core/defaults.ts
|
|
2
|
+
var DEFAULT_CORE_KNOCKBACK = {
|
|
3
|
+
force: 50,
|
|
4
|
+
duration: 300
|
|
30
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 "./index10.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 };
|