@rpgjs/action-battle 5.0.0-alpha.28
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 +784 -0
- package/dist/ai.server.d.ts +482 -0
- package/dist/client/index.js +25 -0
- package/dist/client/index2.js +13 -0
- package/dist/client/index3.js +1129 -0
- package/dist/client/index4.js +137 -0
- package/dist/client.d.ts +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/server/index.js +25 -0
- package/dist/server/index2.js +138 -0
- package/dist/server/index3.js +1128 -0
- package/dist/server.d.ts +93 -0
- package/package.json +45 -0
- package/src/ai.server.ts +1430 -0
- package/src/client.ts +11 -0
- package/src/index.ts +21 -0
- package/src/server.ts +227 -0
- package/vite.config.ts +3 -0
package/src/client.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import {PrebuiltComponentAnimations, RpgClient } from "@rpgjs/client";
|
|
2
|
+
import { defineModule } from "@rpgjs/common";
|
|
3
|
+
|
|
4
|
+
export default defineModule<RpgClient>({
|
|
5
|
+
componentAnimations: [
|
|
6
|
+
{
|
|
7
|
+
id: 'hit',
|
|
8
|
+
component: PrebuiltComponentAnimations.Hit
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import server from "./server";
|
|
2
|
+
import client from "./client";
|
|
3
|
+
import { createModule } from "@rpgjs/common";
|
|
4
|
+
|
|
5
|
+
// AI exports
|
|
6
|
+
export { BattleAi, AiState, EnemyType, AttackPattern, AiDebug, DEFAULT_KNOCKBACK } from "./ai.server";
|
|
7
|
+
|
|
8
|
+
// Types exports
|
|
9
|
+
export type { HitResult, ApplyHitHooks } from "./ai.server";
|
|
10
|
+
|
|
11
|
+
// Server exports
|
|
12
|
+
export { DEFAULT_PLAYER_ATTACK_HITBOXES, getPlayerWeaponKnockbackForce, applyPlayerHitToEvent } from "./server";
|
|
13
|
+
|
|
14
|
+
export function provideActionBattle() {
|
|
15
|
+
return createModule("ActionBattle", [
|
|
16
|
+
{
|
|
17
|
+
server,
|
|
18
|
+
client,
|
|
19
|
+
},
|
|
20
|
+
]);
|
|
21
|
+
}
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { RpgEvent, RpgPlayer, type RpgServer } from "@rpgjs/server";
|
|
2
|
+
import { defineModule } from "@rpgjs/common";
|
|
3
|
+
import { BattleAi, HitResult, ApplyHitHooks, DEFAULT_KNOCKBACK } from "./ai.server";
|
|
4
|
+
|
|
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 const DEFAULT_PLAYER_ATTACK_HITBOXES = {
|
|
13
|
+
up: { offsetX: -16, offsetY: -48, width: 32, height: 32 },
|
|
14
|
+
down: { offsetX: -16, offsetY: 16, width: 32, height: 32 },
|
|
15
|
+
left: { offsetX: -48, offsetY: -16, width: 32, height: 32 },
|
|
16
|
+
right: { offsetX: 16, offsetY: -16, width: 32, height: 32 },
|
|
17
|
+
default: { offsetX: 0, offsetY: -32, width: 32, height: 32 }
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get knockback force from player's equipped weapon
|
|
22
|
+
*
|
|
23
|
+
* Retrieves the knockbackForce property from the player's equipped weapon.
|
|
24
|
+
* Falls back to DEFAULT_KNOCKBACK.force if no weapon or property is set.
|
|
25
|
+
*
|
|
26
|
+
* @param player - The player to get weapon knockback from
|
|
27
|
+
* @returns Knockback force value
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* // Player with weapon having knockbackForce: 80
|
|
32
|
+
* const force = getPlayerWeaponKnockbackForce(player); // 80
|
|
33
|
+
*
|
|
34
|
+
* // No weapon equipped
|
|
35
|
+
* const force = getPlayerWeaponKnockbackForce(player); // 50 (default)
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export function getPlayerWeaponKnockbackForce(player: RpgPlayer): number {
|
|
39
|
+
try {
|
|
40
|
+
const equipments = player.equipments?.() || [];
|
|
41
|
+
for (const item of equipments) {
|
|
42
|
+
const itemData = (player as any).databaseById?.(item.id());
|
|
43
|
+
if (itemData?._type === 'weapon' && itemData.knockbackForce !== undefined) {
|
|
44
|
+
return itemData.knockbackForce;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
// If error, return default
|
|
49
|
+
}
|
|
50
|
+
return DEFAULT_KNOCKBACK.force;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Apply hit from player to target (event with AI)
|
|
55
|
+
*
|
|
56
|
+
* Handles damage calculation, knockback based on weapon, and visual effects.
|
|
57
|
+
* Can be customized using hooks.
|
|
58
|
+
*
|
|
59
|
+
* @param player - The attacking player
|
|
60
|
+
* @param target - The event being hit
|
|
61
|
+
* @param hooks - Optional hooks for customizing hit behavior
|
|
62
|
+
* @returns Hit result if AI exists, undefined otherwise
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```ts
|
|
66
|
+
* // Basic hit
|
|
67
|
+
* const result = applyPlayerHitToEvent(player, event);
|
|
68
|
+
*
|
|
69
|
+
* // With custom hooks
|
|
70
|
+
* const result = applyPlayerHitToEvent(player, event, {
|
|
71
|
+
* onBeforeHit(result) {
|
|
72
|
+
* result.knockbackForce *= 2; // Double knockback
|
|
73
|
+
* return result;
|
|
74
|
+
* },
|
|
75
|
+
* onAfterHit(result) {
|
|
76
|
+
* if (result.defeated) {
|
|
77
|
+
* player.gold += 10;
|
|
78
|
+
* }
|
|
79
|
+
* }
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function applyPlayerHitToEvent(
|
|
84
|
+
player: RpgPlayer,
|
|
85
|
+
target: RpgEvent,
|
|
86
|
+
hooks?: ApplyHitHooks
|
|
87
|
+
): HitResult | undefined {
|
|
88
|
+
const ai = (target as any).battleAi as BattleAi;
|
|
89
|
+
if (!ai) return undefined;
|
|
90
|
+
|
|
91
|
+
// Get knockback force from player's weapon
|
|
92
|
+
const knockbackForce = getPlayerWeaponKnockbackForce(player);
|
|
93
|
+
|
|
94
|
+
// Apply damage to AI
|
|
95
|
+
const defeated = ai.takeDamage(player);
|
|
96
|
+
|
|
97
|
+
// Calculate knockback direction (away from player)
|
|
98
|
+
const dx = target.x() - player.x();
|
|
99
|
+
const dy = target.y() - player.y();
|
|
100
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
101
|
+
|
|
102
|
+
// Create hit result
|
|
103
|
+
let hitResult: HitResult = {
|
|
104
|
+
damage: 0, // Will be set by takeDamage internally
|
|
105
|
+
knockbackForce,
|
|
106
|
+
knockbackDuration: DEFAULT_KNOCKBACK.duration,
|
|
107
|
+
defeated,
|
|
108
|
+
attacker: player,
|
|
109
|
+
target
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Call onBeforeHit hook
|
|
113
|
+
if (hooks?.onBeforeHit) {
|
|
114
|
+
const modified = hooks.onBeforeHit(hitResult);
|
|
115
|
+
if (modified) {
|
|
116
|
+
hitResult = modified;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Apply knockback only if not defeated (entity still exists)
|
|
121
|
+
if (!hitResult.defeated && hitResult.knockbackForce > 0 && distance > 0) {
|
|
122
|
+
const knockbackDirection = {
|
|
123
|
+
x: dx / distance,
|
|
124
|
+
y: dy / distance
|
|
125
|
+
};
|
|
126
|
+
target.knockback(knockbackDirection, hitResult.knockbackForce, hitResult.knockbackDuration);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Call onAfterHit hook
|
|
130
|
+
if (hooks?.onAfterHit) {
|
|
131
|
+
hooks.onAfterHit(hitResult);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return hitResult;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default defineModule<RpgServer>({
|
|
138
|
+
player: {
|
|
139
|
+
/**
|
|
140
|
+
* Handle player input for combat actions
|
|
141
|
+
*
|
|
142
|
+
* When a player presses the action key, create an attack hitbox
|
|
143
|
+
* that can damage AI enemies within range and knockback the event.
|
|
144
|
+
* Knockback force is based on the player's equipped weapon.
|
|
145
|
+
* Triggers attack animation and visual effects.
|
|
146
|
+
*
|
|
147
|
+
* @param player - The player performing the action
|
|
148
|
+
* @param input - Input data containing pressed keys
|
|
149
|
+
*/
|
|
150
|
+
onInput(player: RpgPlayer, input: any) {
|
|
151
|
+
if (input.action) {
|
|
152
|
+
// Trigger attack animation
|
|
153
|
+
player.setAnimation('attack', 1);
|
|
154
|
+
|
|
155
|
+
// Get player position
|
|
156
|
+
const playerX = player.x();
|
|
157
|
+
const playerY = player.y();
|
|
158
|
+
const direction = player.getDirection();
|
|
159
|
+
|
|
160
|
+
// Convert Direction enum to string key
|
|
161
|
+
const directionKey = direction as string;
|
|
162
|
+
|
|
163
|
+
// Get hitbox configuration for the direction
|
|
164
|
+
const hitboxConfig = DEFAULT_PLAYER_ATTACK_HITBOXES[directionKey as keyof typeof DEFAULT_PLAYER_ATTACK_HITBOXES] || DEFAULT_PLAYER_ATTACK_HITBOXES.default;
|
|
165
|
+
|
|
166
|
+
// Convert relative hitbox to absolute coordinates
|
|
167
|
+
const hitboxes: Array<{
|
|
168
|
+
x: number;
|
|
169
|
+
y: number;
|
|
170
|
+
width: number;
|
|
171
|
+
height: number;
|
|
172
|
+
}> = [{
|
|
173
|
+
x: playerX + hitboxConfig.offsetX,
|
|
174
|
+
y: playerY + hitboxConfig.offsetY,
|
|
175
|
+
width: hitboxConfig.width,
|
|
176
|
+
height: hitboxConfig.height
|
|
177
|
+
}];
|
|
178
|
+
|
|
179
|
+
const map = player.getCurrentMap();
|
|
180
|
+
|
|
181
|
+
map?.createMovingHitbox(hitboxes, { speed: 3 }).subscribe({
|
|
182
|
+
next(hits) {
|
|
183
|
+
hits.forEach((hit) => {
|
|
184
|
+
if (hit instanceof RpgEvent) {
|
|
185
|
+
const result = applyPlayerHitToEvent(player, hit);
|
|
186
|
+
if (result?.defeated) {
|
|
187
|
+
console.log(`Player ${player.id} defeated AI ${hit.id}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
event: {
|
|
197
|
+
/**
|
|
198
|
+
* Handle player detection when entering AI vision
|
|
199
|
+
*
|
|
200
|
+
* Called when a player enters an AI event's vision range.
|
|
201
|
+
* The AI will start pursuing and attacking the player.
|
|
202
|
+
*
|
|
203
|
+
* @param event - The AI event
|
|
204
|
+
* @param player - The player entering vision
|
|
205
|
+
* @param shape - The vision shape
|
|
206
|
+
*/
|
|
207
|
+
onDetectInShape(event: RpgEvent, player: RpgPlayer, shape: any) {
|
|
208
|
+
const ai = (event as any).battleAi as BattleAi;
|
|
209
|
+
ai?.onDetectInShape(player, shape);
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Handle player leaving AI vision
|
|
214
|
+
*
|
|
215
|
+
* Called when a player leaves an AI event's vision range.
|
|
216
|
+
* The AI will stop pursuing the player.
|
|
217
|
+
*
|
|
218
|
+
* @param event - The AI event
|
|
219
|
+
* @param player - The player leaving vision
|
|
220
|
+
* @param shape - The vision shape
|
|
221
|
+
*/
|
|
222
|
+
onDetectOutShape(event: RpgEvent, player: RpgPlayer, shape: any) {
|
|
223
|
+
const ai = (event as any).battleAi as BattleAi;
|
|
224
|
+
ai?.onDetectOutShape(player, shape);
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
});
|
package/vite.config.ts
ADDED