@rpgjs/action-battle 5.0.0-beta.11 → 5.0.0-beta.12
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/CHANGELOG.md +11 -0
- package/dist/client/ai.server.d.ts +45 -8
- package/dist/client/attack-input.d.ts +3 -0
- package/dist/client/core/action-use.d.ts +18 -0
- package/dist/client/core/ai-behavior-tree.d.ts +99 -0
- package/dist/client/core/attack-runtime.d.ts +2 -0
- package/dist/client/core/defaults.d.ts +2 -1
- package/dist/client/core/equipment.d.ts +1 -0
- package/dist/client/core/targets.d.ts +15 -0
- package/dist/client/enemies/factory.d.ts +2 -0
- package/dist/client/index.d.ts +12 -7
- package/dist/client/index.js +16 -11
- package/dist/client/index10.js +32 -56
- package/dist/client/index11.js +99 -52
- package/dist/client/index12.js +76 -103
- package/dist/client/index13.js +72 -135
- package/dist/client/index14.js +67 -23
- package/dist/client/index15.js +197 -63
- package/dist/client/index16.js +112 -1337
- package/dist/client/index17.js +193 -7
- package/dist/client/index18.js +32 -58
- package/dist/client/index19.js +70 -8
- package/dist/client/index20.js +57 -501
- package/dist/client/index21.js +69 -0
- package/dist/client/index22.js +225 -0
- package/dist/client/index23.js +16 -0
- package/dist/client/index24.js +25 -0
- package/dist/client/index25.js +107 -0
- package/dist/client/index26.js +1707 -0
- package/dist/client/index27.js +12 -0
- package/dist/client/index28.js +589 -0
- package/dist/client/index4.js +79 -38
- package/dist/client/index6.js +65 -306
- package/dist/client/index7.js +33 -33
- package/dist/client/index8.js +24 -100
- package/dist/client/index9.js +293 -61
- package/dist/client/locomotion.d.ts +16 -0
- package/dist/client/movement.d.ts +14 -0
- package/dist/client/server.d.ts +7 -3
- package/dist/client/ui.d.ts +22 -0
- package/dist/client/visual.d.ts +15 -0
- package/dist/server/ai.server.d.ts +45 -8
- package/dist/server/attack-input.d.ts +3 -0
- package/dist/server/core/action-use.d.ts +18 -0
- package/dist/server/core/ai-behavior-tree.d.ts +99 -0
- package/dist/server/core/attack-runtime.d.ts +2 -0
- package/dist/server/core/defaults.d.ts +2 -1
- package/dist/server/core/equipment.d.ts +1 -0
- package/dist/server/core/targets.d.ts +15 -0
- package/dist/server/enemies/factory.d.ts +2 -0
- package/dist/server/index.d.ts +12 -7
- package/dist/server/index.js +14 -9
- package/dist/server/index10.js +64 -1336
- package/dist/server/index11.js +33 -33
- package/dist/server/index13.js +66 -11
- package/dist/server/index14.js +206 -484
- package/dist/server/index15.js +15 -9
- package/dist/server/index16.js +26 -0
- package/dist/server/index17.js +25 -0
- package/dist/server/index18.js +107 -0
- package/dist/server/index19.js +1707 -0
- package/dist/server/index2.js +10 -2
- package/dist/server/index20.js +37 -0
- package/dist/server/index21.js +588 -0
- package/dist/server/index22.js +78 -0
- package/dist/server/index23.js +12 -0
- package/dist/server/index5.js +79 -38
- package/dist/server/index6.js +192 -129
- package/dist/server/index7.js +198 -24
- package/dist/server/index8.js +28 -66
- package/dist/server/index9.js +68 -51
- package/dist/server/locomotion.d.ts +16 -0
- package/dist/server/movement.d.ts +14 -0
- package/dist/server/server.d.ts +7 -3
- package/dist/server/ui.d.ts +22 -0
- package/dist/server/visual.d.ts +15 -0
- package/package.json +5 -5
- package/src/ai.server.spec.ts +233 -0
- package/src/ai.server.ts +627 -108
- package/src/animations.spec.ts +40 -0
- package/src/animations.ts +31 -9
- package/src/attack-input.spec.ts +51 -0
- package/src/attack-input.ts +59 -0
- package/src/client.ts +75 -62
- package/src/config.ts +84 -37
- package/src/core/action-use.spec.ts +317 -0
- package/src/core/action-use.ts +386 -0
- package/src/core/ai-behavior-tree.spec.ts +116 -0
- package/src/core/ai-behavior-tree.ts +272 -0
- package/src/core/attack-profile.spec.ts +46 -0
- package/src/core/attack-runtime.spec.ts +35 -0
- package/src/core/attack-runtime.ts +32 -0
- package/src/core/context.ts +9 -0
- package/src/core/contracts.ts +146 -1
- package/src/core/defaults.ts +56 -0
- package/src/core/equipment.ts +9 -5
- package/src/core/targets.spec.ts +112 -0
- package/src/core/targets.ts +147 -0
- package/src/enemies/factory.ts +8 -0
- package/src/index.ts +111 -2
- package/src/locomotion.spec.ts +51 -0
- package/src/locomotion.ts +48 -0
- package/src/movement.spec.ts +78 -0
- package/src/movement.ts +46 -0
- package/src/server.ts +242 -66
- package/src/types.ts +105 -35
- package/src/ui.ts +113 -0
- package/src/visual.spec.ts +166 -0
- package/src/visual.ts +285 -0
- package/README.md +0 -1242
package/dist/server/index14.js
CHANGED
|
@@ -1,503 +1,225 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import "./
|
|
3
|
-
import {
|
|
4
|
-
import { normalizeActionBattleOptions, setActionBattleOptions } from "./index5.js";
|
|
5
|
-
import { DEFAULT_ZELDA_PLAYER_HITBOXES } from "./index6.js";
|
|
6
|
-
import { getActionBattleSystems, setActionBattleSystems } from "./index7.js";
|
|
7
|
-
import "./index8.js";
|
|
8
|
-
import { ActionBattleHitTracker, createActionBattleAttackId, getNormalizedActionBattleAttackProfile, resolveActionBattleHitboxSpeed, scheduleActionBattleStartup } from "./index9.js";
|
|
9
|
-
import { DEFAULT_KNOCKBACK } from "./index10.js";
|
|
10
|
-
import { manhattanDistance, parseAoeMask } from "./index11.js";
|
|
1
|
+
import { getActionBattleOptions } from "./index5.js";
|
|
2
|
+
import { emitActionBattleClientVisual } from "./index6.js";
|
|
3
|
+
import { getActionBattleSystems } from "./index8.js";
|
|
11
4
|
import { applyActionBattleHit } from "./index12.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
var
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* for each cardinal direction. They are converted to absolute coordinates
|
|
23
|
-
* when creating the moving hitbox.
|
|
24
|
-
*/
|
|
25
|
-
var DEFAULT_PLAYER_ATTACK_HITBOXES = { ...DEFAULT_ZELDA_PLAYER_HITBOXES };
|
|
26
|
-
var beginPlayerAttackLock = (player, map, durationMs, locks) => {
|
|
27
|
-
if (durationMs <= 0) return true;
|
|
28
|
-
const runtimePlayer = player;
|
|
29
|
-
const now = Date.now();
|
|
30
|
-
if (typeof runtimePlayer.__actionBattleAttackLockedUntil === "number" && runtimePlayer.__actionBattleAttackLockedUntil > now) return false;
|
|
31
|
-
const lockId = (runtimePlayer.__actionBattleAttackLockId ?? 0) + 1;
|
|
32
|
-
runtimePlayer.__actionBattleAttackLockId = lockId;
|
|
33
|
-
runtimePlayer.__actionBattleAttackLockedUntil = now + durationMs;
|
|
34
|
-
const previousCanMove = player.canMove;
|
|
35
|
-
const previousDirectionFixed = player.directionFixed;
|
|
36
|
-
const previousAnimationFixed = player.animationFixed;
|
|
37
|
-
if (locks.movement) {
|
|
38
|
-
player.pendingInputs = [];
|
|
39
|
-
player.lastProcessedInputTs = 0;
|
|
40
|
-
map?.stopMovement?.(player);
|
|
41
|
-
player.canMove = false;
|
|
42
|
-
}
|
|
43
|
-
if (locks.direction) player.directionFixed = true;
|
|
44
|
-
setTimeout(() => {
|
|
45
|
-
if (runtimePlayer.__actionBattleAttackLockId !== lockId) return;
|
|
46
|
-
runtimePlayer.__actionBattleAttackLockedUntil = 0;
|
|
47
|
-
player.canMove = previousCanMove;
|
|
48
|
-
player.directionFixed = previousDirectionFixed;
|
|
49
|
-
player.animationFixed = previousAnimationFixed;
|
|
50
|
-
}, durationMs);
|
|
51
|
-
return true;
|
|
52
|
-
};
|
|
53
|
-
var isBattleEvent = (event) => !!event.battleAi;
|
|
54
|
-
var rectsOverlap = (a, b) => a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
|
|
55
|
-
var eventRect = (event) => {
|
|
56
|
-
const hitbox = typeof event.hitbox === "function" ? event.hitbox() : event.hitbox;
|
|
5
|
+
import { canActionBattleTarget, getActionBattleFaction, getActionBattleTargets, isActionBattleCombatEntity, isActionBattleTargetDefeated } from "./index13.js";
|
|
6
|
+
import { MAXHP } from "@rpgjs/server";
|
|
7
|
+
//#region src/core/action-use.ts
|
|
8
|
+
var projectileHandlers = /* @__PURE__ */ new Map();
|
|
9
|
+
var normalizeDirection = (direction) => {
|
|
10
|
+
const distance = Math.sqrt(direction.x * direction.x + direction.y * direction.y);
|
|
11
|
+
if (distance <= 0) return {
|
|
12
|
+
x: 0,
|
|
13
|
+
y: 1
|
|
14
|
+
};
|
|
57
15
|
return {
|
|
58
|
-
x:
|
|
59
|
-
y:
|
|
60
|
-
width: hitbox?.w ?? 32,
|
|
61
|
-
height: hitbox?.h ?? 32
|
|
16
|
+
x: direction.x / distance,
|
|
17
|
+
y: direction.y / distance
|
|
62
18
|
};
|
|
63
19
|
};
|
|
64
|
-
var
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
};
|
|
72
|
-
const collisions = map.getCollisions?.(player.id);
|
|
73
|
-
if (Array.isArray(collisions)) collisions.forEach((id) => addEvent(map.getEvent(id)));
|
|
74
|
-
const direction = typeof player.getDirection === "function" ? player.getDirection() : void 0;
|
|
75
|
-
const interactionCollisions = map.getInteractionCollisions?.(player.id, direction);
|
|
76
|
-
if (Array.isArray(interactionCollisions)) interactionCollisions.forEach((id) => addEvent(map.getEvent(id)));
|
|
77
|
-
for (const event of map.getEvents()) {
|
|
78
|
-
const rect = eventRect(event);
|
|
79
|
-
if (hitboxes.some((hitbox) => rectsOverlap(hitbox, rect))) addEvent(event);
|
|
80
|
-
}
|
|
81
|
-
return Array.from(eventsById.values());
|
|
20
|
+
var directionToTarget = (attacker, target) => {
|
|
21
|
+
const first = firstTarget(target);
|
|
22
|
+
if (!first) return void 0;
|
|
23
|
+
return normalizeDirection({
|
|
24
|
+
x: first.x() - attacker.x(),
|
|
25
|
+
y: first.y() - attacker.y()
|
|
26
|
+
});
|
|
82
27
|
};
|
|
83
|
-
var
|
|
84
|
-
|
|
85
|
-
return
|
|
28
|
+
var asArray = (value) => {
|
|
29
|
+
if (!value) return [];
|
|
30
|
+
return Array.isArray(value) ? value : [value];
|
|
31
|
+
};
|
|
32
|
+
var firstTarget = (target) => asArray(target)[0];
|
|
33
|
+
var resolveActionConfig = (usable) => usable?.action ?? usable?.actionBattle ?? usable?._skillInstance?.action ?? usable?._skillInstance?.actionBattle ?? usable?._skillData?.action ?? usable?._skillData?.actionBattle;
|
|
34
|
+
var getUseHookTarget = (usable) => usable?._skillInstance ?? usable?._skillData ?? usable;
|
|
35
|
+
var getUseHook = (usable) => {
|
|
36
|
+
const target = getUseHookTarget(usable);
|
|
37
|
+
return typeof target?.onUse === "function" ? {
|
|
38
|
+
hook: target.onUse,
|
|
39
|
+
target
|
|
40
|
+
} : void 0;
|
|
41
|
+
};
|
|
42
|
+
var isSkill = (usable, explicitSkill) => !!explicitSkill || usable?._type === "skill" || usable?.spCost !== void 0;
|
|
43
|
+
var consumeSkillUse = (attacker, skill) => {
|
|
44
|
+
const spCost = typeof skill?.spCost === "number" ? skill.spCost : 0;
|
|
45
|
+
if (spCost > 0) {
|
|
46
|
+
if (spCost > (attacker.sp ?? 0)) throw new Error(`Not enough SP to use ${skill?.id ?? skill?.name ?? "skill"}`);
|
|
47
|
+
const halfCost = attacker.hasEffect?.("HALF_SP_COST") || attacker.hasEffect?.("half_sp_cost");
|
|
48
|
+
attacker.sp -= spCost / (halfCost ? 2 : 1);
|
|
49
|
+
}
|
|
50
|
+
const hitRate = typeof skill?.hitRate === "number" ? skill.hitRate : 1;
|
|
51
|
+
if (Math.random() > hitRate) throw new Error(`Action battle skill failed: ${skill?.id ?? skill?.name ?? "skill"}`);
|
|
86
52
|
};
|
|
87
|
-
|
|
88
|
-
* Get knockback force from player's equipped weapon
|
|
89
|
-
*
|
|
90
|
-
* Retrieves the knockbackForce property from the player's equipped weapon.
|
|
91
|
-
* Falls back to DEFAULT_KNOCKBACK.force if no weapon or property is set.
|
|
92
|
-
*
|
|
93
|
-
* @param player - The player to get weapon knockback from
|
|
94
|
-
* @returns Knockback force value
|
|
95
|
-
*
|
|
96
|
-
* @example
|
|
97
|
-
* ```ts
|
|
98
|
-
* // Player with weapon having knockbackForce: 80
|
|
99
|
-
* const force = getPlayerWeaponKnockbackForce(player); // 80
|
|
100
|
-
*
|
|
101
|
-
* // No weapon equipped
|
|
102
|
-
* const force = getPlayerWeaponKnockbackForce(player); // 50 (default)
|
|
103
|
-
* ```
|
|
104
|
-
*/
|
|
105
|
-
function getPlayerWeaponKnockbackForce(player) {
|
|
106
|
-
try {
|
|
107
|
-
const equipments = player.equipments?.() || [];
|
|
108
|
-
for (const item of equipments) {
|
|
109
|
-
const itemData = player.databaseById?.(item.id());
|
|
110
|
-
if (itemData?._type === "weapon" && itemData.knockbackForce !== void 0) return itemData.knockbackForce;
|
|
111
|
-
}
|
|
112
|
-
} catch {}
|
|
113
|
-
return DEFAULT_KNOCKBACK.force;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Apply hit from player to target (event with AI)
|
|
117
|
-
*
|
|
118
|
-
* Handles damage calculation, knockback based on weapon, and visual effects.
|
|
119
|
-
* Can be customized using hooks.
|
|
120
|
-
*
|
|
121
|
-
* @param player - The attacking player
|
|
122
|
-
* @param target - The event being hit
|
|
123
|
-
* @param hooks - Optional hooks for customizing hit behavior
|
|
124
|
-
* @returns Hit result if AI exists, undefined otherwise
|
|
125
|
-
*
|
|
126
|
-
* @example
|
|
127
|
-
* ```ts
|
|
128
|
-
* // Basic hit
|
|
129
|
-
* const result = applyPlayerHitToEvent(player, event);
|
|
130
|
-
*
|
|
131
|
-
* // With custom hooks
|
|
132
|
-
* const result = applyPlayerHitToEvent(player, event, {
|
|
133
|
-
* onBeforeHit(result) {
|
|
134
|
-
* result.knockbackForce *= 2; // Double knockback
|
|
135
|
-
* return result;
|
|
136
|
-
* },
|
|
137
|
-
* onAfterHit(result) {
|
|
138
|
-
* if (result.defeated) {
|
|
139
|
-
* player.gold += 10;
|
|
140
|
-
* }
|
|
141
|
-
* }
|
|
142
|
-
* });
|
|
143
|
-
* ```
|
|
144
|
-
*/
|
|
145
|
-
function applyPlayerHitToEvent(player, target, hooks, metadata) {
|
|
146
|
-
const ai = target.battleAi;
|
|
147
|
-
if (!ai) return void 0;
|
|
53
|
+
var applyDamageEffect = (attacker, target, skill, reaction, metadata) => {
|
|
148
54
|
const systems = getActionBattleSystems();
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
...systems.combat.hooks,
|
|
153
|
-
beforeHit(context) {
|
|
154
|
-
const before = systems.combat.hooks?.beforeHit?.(context);
|
|
155
|
-
if (before === false) return false;
|
|
156
|
-
const nextContext = before || context;
|
|
157
|
-
const legacyResult = toLegacyHitResult(nextContext);
|
|
158
|
-
const modified = hooks.onBeforeHit?.(legacyResult);
|
|
159
|
-
if (!modified) return nextContext;
|
|
160
|
-
return {
|
|
161
|
-
...nextContext,
|
|
162
|
-
damage: {
|
|
163
|
-
damage: modified.damage,
|
|
164
|
-
defeated: modified.defeated,
|
|
165
|
-
raw: nextContext.damage?.raw
|
|
166
|
-
},
|
|
167
|
-
knockback: {
|
|
168
|
-
force: modified.knockbackForce,
|
|
169
|
-
duration: modified.knockbackDuration,
|
|
170
|
-
direction: nextContext.knockback?.direction
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
},
|
|
174
|
-
afterHit(result) {
|
|
175
|
-
systems.combat.hooks?.afterHit?.(result);
|
|
176
|
-
hooks.onAfterHit?.(result);
|
|
177
|
-
}
|
|
178
|
-
} : systems.combat.hooks
|
|
179
|
-
}, {
|
|
180
|
-
attacker: player,
|
|
55
|
+
attacker.applyStates?.(target, skill);
|
|
56
|
+
const result = applyActionBattleHit(systems.combat, {
|
|
57
|
+
attacker,
|
|
181
58
|
target,
|
|
182
|
-
|
|
183
|
-
reaction
|
|
184
|
-
|
|
185
|
-
if (!result.cancelled) ai.handleDamage(player, {
|
|
186
|
-
damage: result.damage,
|
|
187
|
-
defeated: result.defeated,
|
|
188
|
-
raw: result.rawDamage,
|
|
189
|
-
reaction: result.reaction
|
|
190
|
-
});
|
|
191
|
-
return result;
|
|
192
|
-
}
|
|
193
|
-
var toLegacyHitResult = (context) => ({
|
|
194
|
-
damage: context.damage?.damage ?? 0,
|
|
195
|
-
knockbackForce: context.knockback?.force ?? getPlayerWeaponKnockbackForce(context.attacker),
|
|
196
|
-
knockbackDuration: context.knockback?.duration ?? DEFAULT_KNOCKBACK.duration,
|
|
197
|
-
defeated: context.damage?.defeated ?? false,
|
|
198
|
-
attacker: context.attacker,
|
|
199
|
-
target: context.target
|
|
200
|
-
});
|
|
201
|
-
var resolvePlayerAttackHitboxes = (player, directionKey, options, profile) => {
|
|
202
|
-
const configuredHitboxes = {
|
|
203
|
-
...DEFAULT_PLAYER_ATTACK_HITBOXES,
|
|
204
|
-
...options.attack?.hitboxes,
|
|
205
|
-
...profile.hitboxes
|
|
206
|
-
};
|
|
207
|
-
const hitboxConfig = configuredHitboxes[directionKey] || configuredHitboxes.default;
|
|
208
|
-
const defaultHitboxes = [{
|
|
209
|
-
x: player.x() + hitboxConfig.offsetX,
|
|
210
|
-
y: player.y() + hitboxConfig.offsetY,
|
|
211
|
-
width: hitboxConfig.width,
|
|
212
|
-
height: hitboxConfig.height
|
|
213
|
-
}];
|
|
214
|
-
return options.attack?.resolveHitboxes?.({
|
|
215
|
-
player,
|
|
216
|
-
direction: directionKey,
|
|
217
|
-
defaultHitboxes
|
|
218
|
-
}) ?? defaultHitboxes;
|
|
219
|
-
};
|
|
220
|
-
var mergeAttackProfileOverrides = (base, override) => ({
|
|
221
|
-
...base,
|
|
222
|
-
...override,
|
|
223
|
-
reaction: {
|
|
224
|
-
...base.reaction,
|
|
225
|
-
...override.reaction
|
|
226
|
-
},
|
|
227
|
-
hitboxes: {
|
|
228
|
-
...base.hitboxes,
|
|
229
|
-
...override.hitboxes
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
var resolvePlayerAttackProfile = (player, options) => {
|
|
233
|
-
const baseProfile = getNormalizedActionBattleAttackProfile(options);
|
|
234
|
-
const weaponProfile = resolveActionBattleWeaponAttackProfile(player);
|
|
235
|
-
if (!weaponProfile) return baseProfile;
|
|
236
|
-
return normalizeActionBattleAttackProfile(mergeAttackProfileOverrides(baseProfile, weaponProfile), {
|
|
237
|
-
lockMovement: options.attack?.lockMovement,
|
|
238
|
-
lockDurationMs: options.attack?.lockDurationMs,
|
|
239
|
-
hitboxes: options.attack?.hitboxes
|
|
59
|
+
skill,
|
|
60
|
+
reaction,
|
|
61
|
+
metadata
|
|
240
62
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
};
|
|
250
|
-
var resolveSkillData = (player, skillId) => {
|
|
251
|
-
try {
|
|
252
|
-
return player.databaseById?.(skillId);
|
|
253
|
-
} catch {
|
|
254
|
-
return null;
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
var resolveSkillTargeting = (player, skillId, options) => {
|
|
258
|
-
const skillsOptions = options.skills;
|
|
259
|
-
const skillData = resolveSkillData(player, skillId);
|
|
260
|
-
if (skillsOptions?.getTargeting) return skillsOptions.getTargeting(skillData);
|
|
261
|
-
const range = skillData?.range ?? skillData?.targeting?.range ?? skillData?.targeting?.distance;
|
|
262
|
-
const aoeMask = skillData?.aoeMask ?? skillData?.targeting?.aoeMask ?? skillData?.targeting?.mask;
|
|
263
|
-
if (range === void 0 && aoeMask === void 0) return null;
|
|
264
|
-
return {
|
|
265
|
-
range: range ?? 0,
|
|
266
|
-
aoeMask
|
|
267
|
-
};
|
|
268
|
-
};
|
|
269
|
-
var normalizeMaskRows = (mask) => {
|
|
270
|
-
if (!mask) return [];
|
|
271
|
-
if (Array.isArray(mask)) return mask;
|
|
272
|
-
return mask.trim().split("\n").map((row) => row.replace(/\r/g, ""));
|
|
273
|
-
};
|
|
274
|
-
var buildActionBarData = (player, options) => {
|
|
275
|
-
return {
|
|
276
|
-
items: (player.items?.() || []).map((item) => {
|
|
277
|
-
const id = item.id?.() ?? item.id;
|
|
278
|
-
const data = resolveItemData(player, id);
|
|
279
|
-
const name = resolveSignal(data?.name) ?? resolveSignal(item.name) ?? id;
|
|
280
|
-
const description = resolveSignal(data?.description) ?? resolveSignal(item.description) ?? "";
|
|
281
|
-
const icon = resolveSignal(data?.icon) ?? resolveSignal(item.icon);
|
|
282
|
-
const quantity = resolveSignal(item.quantity) ?? 1;
|
|
283
|
-
const consumable = resolveSignal(data?.consumable);
|
|
284
|
-
const itemType = resolveSignal(data?._type);
|
|
285
|
-
return {
|
|
286
|
-
id,
|
|
287
|
-
name,
|
|
288
|
-
description,
|
|
289
|
-
icon,
|
|
290
|
-
quantity,
|
|
291
|
-
usable: quantity > 0 && consumable !== false && (itemType ? itemType === "item" : true)
|
|
292
|
-
};
|
|
293
|
-
}),
|
|
294
|
-
skills: (player.skills?.() || []).map((skill) => {
|
|
295
|
-
const id = skill.id?.() ?? skill.id;
|
|
296
|
-
const data = resolveSkillData(player, id) || skill;
|
|
297
|
-
const name = resolveSignal(data?.name) ?? resolveSignal(skill.name) ?? id;
|
|
298
|
-
const description = resolveSignal(data?.description) ?? resolveSignal(skill.description) ?? "";
|
|
299
|
-
const icon = resolveSignal(data?.icon) ?? resolveSignal(skill.icon);
|
|
300
|
-
const spCost = resolveSignal(data?.spCost) ?? resolveSignal(skill.spCost) ?? 0;
|
|
301
|
-
const usable = spCost <= player.sp;
|
|
302
|
-
const targeting = resolveSkillTargeting(player, id, options);
|
|
303
|
-
const skillEntry = {
|
|
304
|
-
id,
|
|
305
|
-
name,
|
|
306
|
-
description,
|
|
307
|
-
icon,
|
|
308
|
-
spCost,
|
|
309
|
-
usable,
|
|
310
|
-
range: targeting?.range ?? 0
|
|
311
|
-
};
|
|
312
|
-
if (targeting) {
|
|
313
|
-
const mask = targeting.aoeMask ?? options.skills?.defaultAoeMask;
|
|
314
|
-
if (mask) skillEntry.aoeMask = normalizeMaskRows(mask);
|
|
315
|
-
}
|
|
316
|
-
return skillEntry;
|
|
317
|
-
})
|
|
318
|
-
};
|
|
319
|
-
};
|
|
320
|
-
var ensureActionBarGui = (player, options) => {
|
|
321
|
-
const gui = player.getGui?.("action-battle-action-bar") || player.gui("action-battle-action-bar");
|
|
322
|
-
if (!gui.__actionBattleReady) {
|
|
323
|
-
gui.__actionBattleReady = true;
|
|
324
|
-
gui.on("useItem", ({ id }) => {
|
|
325
|
-
try {
|
|
326
|
-
player.useItem(id);
|
|
327
|
-
} catch {}
|
|
328
|
-
gui.update(buildActionBarData(player, options));
|
|
329
|
-
});
|
|
330
|
-
gui.on("useSkill", ({ id, target }) => {
|
|
331
|
-
handleActionBattleSkillUse(player, id, target, options);
|
|
332
|
-
gui.update(buildActionBarData(player, options));
|
|
63
|
+
if (!result.cancelled) {
|
|
64
|
+
emitActionBattleClientVisual({
|
|
65
|
+
moment: "hit",
|
|
66
|
+
entity: attacker,
|
|
67
|
+
target,
|
|
68
|
+
damage: result.damage,
|
|
69
|
+
result,
|
|
70
|
+
skill
|
|
333
71
|
});
|
|
334
|
-
|
|
335
|
-
|
|
72
|
+
target.battleAi?.handleDamage?.(attacker, {
|
|
73
|
+
damage: result.damage,
|
|
74
|
+
defeated: result.defeated,
|
|
75
|
+
raw: result.rawDamage,
|
|
76
|
+
reaction: result.reaction
|
|
336
77
|
});
|
|
337
78
|
}
|
|
338
|
-
return
|
|
339
|
-
};
|
|
340
|
-
var openActionBattleActionBar = (player, rawOptions = {}) => {
|
|
341
|
-
const options = normalizeActionBattleOptions(rawOptions);
|
|
342
|
-
ensureActionBarGui(player, options).open(buildActionBarData(player, options));
|
|
343
|
-
};
|
|
344
|
-
var updateActionBattleActionBar = (player, rawOptions = {}) => {
|
|
345
|
-
const options = normalizeActionBattleOptions(rawOptions);
|
|
346
|
-
const gui = player.getGui?.(ACTION_BATTLE_ACTION_BAR_GUI_ID);
|
|
347
|
-
if (gui) gui.update(buildActionBarData(player, options));
|
|
348
|
-
};
|
|
349
|
-
var getTileSize = (map) => ({
|
|
350
|
-
width: map?.tileWidth ?? 32,
|
|
351
|
-
height: map?.tileHeight ?? 32
|
|
352
|
-
});
|
|
353
|
-
var getEntityTile = (entity, tileSize) => {
|
|
354
|
-
const hitbox = entity.hitbox?.() || {
|
|
355
|
-
w: tileSize.width,
|
|
356
|
-
h: tileSize.height
|
|
357
|
-
};
|
|
358
|
-
return {
|
|
359
|
-
x: Math.floor((entity.x() + hitbox.w / 2) / tileSize.width),
|
|
360
|
-
y: Math.floor((entity.y() + hitbox.h / 2) / tileSize.height)
|
|
361
|
-
};
|
|
79
|
+
return result;
|
|
362
80
|
};
|
|
363
|
-
var
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
81
|
+
var buildActionContext = (input) => {
|
|
82
|
+
const action = {};
|
|
83
|
+
Object.assign(action, {
|
|
84
|
+
attacker: input.attacker,
|
|
85
|
+
user: input.attacker,
|
|
86
|
+
target: input.target,
|
|
87
|
+
usable: input.usable,
|
|
88
|
+
skill: input.skill,
|
|
89
|
+
weapon: input.weapon,
|
|
90
|
+
action: input.action,
|
|
91
|
+
pattern: input.pattern,
|
|
92
|
+
defaultEffect(target = input.target) {
|
|
93
|
+
return asArray(target).map((entry) => applyDamageEffect(input.attacker, entry, input.skill, input.profile?.reaction, {
|
|
94
|
+
actionId: input.usable?.id,
|
|
95
|
+
actionType: input.usable?._type,
|
|
96
|
+
pattern: input.pattern
|
|
97
|
+
}));
|
|
98
|
+
},
|
|
99
|
+
damage(target = input.target) {
|
|
100
|
+
const entry = firstTarget(target);
|
|
101
|
+
if (!entry) return void 0;
|
|
102
|
+
return applyDamageEffect(input.attacker, entry, input.skill, input.profile?.reaction, {
|
|
103
|
+
actionId: input.usable?.id,
|
|
104
|
+
actionType: input.usable?._type,
|
|
105
|
+
pattern: input.pattern
|
|
106
|
+
});
|
|
107
|
+
},
|
|
108
|
+
heal(target, amount) {
|
|
109
|
+
if (!target || !Number.isFinite(amount) || amount <= 0) return 0;
|
|
110
|
+
return asArray(target).reduce((total, entry) => {
|
|
111
|
+
const currentHp = Number(entry.hp ?? 0);
|
|
112
|
+
const maxHp = (typeof entry.param === "function" ? entry.param() : entry.param)?.[MAXHP] ?? Number.POSITIVE_INFINITY;
|
|
113
|
+
const nextHp = Math.min(maxHp, currentHp + amount);
|
|
114
|
+
entry.hp = nextHp;
|
|
115
|
+
emitActionBattleClientVisual({
|
|
116
|
+
moment: "hurt",
|
|
117
|
+
entity: entry,
|
|
118
|
+
target: entry,
|
|
119
|
+
damage: Math.max(0, nextHp - currentHp),
|
|
120
|
+
skill: input.skill
|
|
121
|
+
});
|
|
122
|
+
return total + nextHp - currentHp;
|
|
123
|
+
}, 0);
|
|
124
|
+
},
|
|
125
|
+
projectile(options = { type: "action" }) {
|
|
126
|
+
const map = input.attacker.getCurrentMap?.();
|
|
127
|
+
if (!map?.projectiles?.emit) return [];
|
|
128
|
+
const projectile = {
|
|
129
|
+
...input.action?.projectile ?? {},
|
|
130
|
+
...options
|
|
131
|
+
};
|
|
132
|
+
const range = projectile.range ?? input.action?.range ?? 160;
|
|
133
|
+
const speed = projectile.speed ?? 180;
|
|
134
|
+
const emitted = map.projectiles.emit({
|
|
135
|
+
type: projectile.type,
|
|
136
|
+
origin: projectile.origin,
|
|
137
|
+
direction: projectile.direction ?? directionToTarget(input.attacker, input.target),
|
|
138
|
+
spreadDegrees: projectile.spreadDegrees,
|
|
139
|
+
accuracy: projectile.accuracy,
|
|
140
|
+
trajectory: projectile.trajectory ?? {
|
|
141
|
+
type: "linear",
|
|
142
|
+
speed,
|
|
143
|
+
range
|
|
144
|
+
},
|
|
145
|
+
collision: projectile.collision,
|
|
146
|
+
repeat: projectile.repeat,
|
|
147
|
+
pattern: projectile.pattern,
|
|
148
|
+
payload: {
|
|
149
|
+
...projectile.payload,
|
|
150
|
+
actionBattle: true,
|
|
151
|
+
attackerId: input.attacker.id,
|
|
152
|
+
actionId: input.usable?.id
|
|
153
|
+
},
|
|
154
|
+
params: projectile.params,
|
|
155
|
+
canHit: ({ target }) => {
|
|
156
|
+
if (!target) return false;
|
|
157
|
+
return canActionBattleUseTarget(input.attacker, target, input.action?.target ?? "enemy", getActionBattleOptions().combat?.targets);
|
|
158
|
+
}
|
|
159
|
+
}, input.attacker);
|
|
160
|
+
for (const state of emitted) projectileHandlers.set(state.id, {
|
|
161
|
+
action,
|
|
162
|
+
onImpact: projectile.onImpact
|
|
163
|
+
});
|
|
164
|
+
return emitted;
|
|
165
|
+
}
|
|
396
166
|
});
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
167
|
+
return action;
|
|
168
|
+
};
|
|
169
|
+
var getActionBattleActionConfig = (usable) => resolveActionConfig(usable);
|
|
170
|
+
var getActionBattleActionRange = (usable) => resolveActionConfig(usable)?.range;
|
|
171
|
+
var canActionBattleUseTarget = (attacker, target, actionTarget = "enemy", options = {}) => {
|
|
172
|
+
if (isActionBattleTargetDefeated(target)) return false;
|
|
173
|
+
if (actionTarget === "self") return attacker === target;
|
|
174
|
+
if (actionTarget === "any") return attacker === target || isActionBattleCombatEntity(target);
|
|
175
|
+
if (attacker === target || !isActionBattleCombatEntity(target)) return false;
|
|
176
|
+
if (actionTarget === "ally") {
|
|
177
|
+
const attackerFaction = getActionBattleFaction(attacker, options);
|
|
178
|
+
const targetFaction = getActionBattleFaction(target, options);
|
|
179
|
+
return !!attackerFaction && attackerFaction === targetFaction;
|
|
180
|
+
}
|
|
181
|
+
return canActionBattleTarget(attacker, target, getActionBattleTargets(attacker, "hostile"), options);
|
|
182
|
+
};
|
|
183
|
+
var shouldUseActionBattleUsable = (usable, explicitSkill) => {
|
|
184
|
+
if (!usable) return false;
|
|
185
|
+
return isSkill(usable, explicitSkill) || !!getUseHook(usable) || !!resolveActionConfig(usable);
|
|
186
|
+
};
|
|
187
|
+
var executeActionBattleUse = (input) => {
|
|
188
|
+
if (!shouldUseActionBattleUsable(input.usable, input.skill)) return false;
|
|
189
|
+
const actionConfig = resolveActionConfig(input.usable);
|
|
190
|
+
if (isSkill(input.usable, input.skill)) consumeSkillUse(input.attacker, input.skill ?? input.usable);
|
|
191
|
+
const action = buildActionContext({
|
|
192
|
+
...input,
|
|
193
|
+
action: actionConfig
|
|
401
194
|
});
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
skill:
|
|
405
|
-
|
|
195
|
+
const hook = getUseHook(input.usable);
|
|
196
|
+
if (input.playVisual !== false) emitActionBattleClientVisual({
|
|
197
|
+
moment: input.skill ? "castSkill" : "attack",
|
|
198
|
+
entity: input.attacker,
|
|
199
|
+
skill: input.skill,
|
|
200
|
+
target: firstTarget(input.target)
|
|
406
201
|
});
|
|
407
|
-
|
|
202
|
+
if (hook) {
|
|
203
|
+
hook.hook.call(hook.target, input.attacker, input.target, action);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
if (actionConfig?.mode === "projectile") {
|
|
207
|
+
action.projectile(actionConfig.projectile);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
action.defaultEffect(input.target);
|
|
211
|
+
return true;
|
|
408
212
|
};
|
|
409
|
-
var
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
* that can damage AI enemies within range and knockback the event.
|
|
420
|
-
* Knockback force is based on the player's equipped weapon.
|
|
421
|
-
* Triggers attack animation and visual effects.
|
|
422
|
-
*
|
|
423
|
-
* @param player - The player performing the action
|
|
424
|
-
* @param input - Input data containing pressed keys
|
|
425
|
-
*/
|
|
426
|
-
onInput(player, input) {
|
|
427
|
-
if (input.action == Control.Action) {
|
|
428
|
-
const map = player.getCurrentMap();
|
|
429
|
-
const direction = player.getDirection();
|
|
430
|
-
const attackProfile = resolvePlayerAttackProfile(player, options);
|
|
431
|
-
const hitboxes = resolvePlayerAttackHitboxes(player, direction, options, attackProfile);
|
|
432
|
-
if (isActionReservedForNormalEvent(player, map, hitboxes)) return;
|
|
433
|
-
const lockMovement = attackProfile.movementLock;
|
|
434
|
-
const lockDirection = attackProfile.directionLock;
|
|
435
|
-
const lockDurationMs = attackProfile.totalDurationMs ?? DEFAULT_ATTACK_LOCK_DURATION_MS;
|
|
436
|
-
const actionLocked = (lockMovement || lockDirection) && lockDurationMs > 0;
|
|
437
|
-
if (actionLocked && !beginPlayerAttackLock(player, map, Math.max(0, lockDurationMs), {
|
|
438
|
-
movement: lockMovement,
|
|
439
|
-
direction: lockDirection
|
|
440
|
-
})) return;
|
|
441
|
-
playActionBattleAnimation("attack", player, options.animations);
|
|
442
|
-
if (actionLocked) player.animationFixed = true;
|
|
443
|
-
const attackId = createActionBattleAttackId(player.id, attackProfile.id);
|
|
444
|
-
const hitTracker = new ActionBattleHitTracker(attackProfile.hitPolicy);
|
|
445
|
-
if (options.debug?.attacks) console.log("[ActionBattle] player attack", {
|
|
446
|
-
attackId,
|
|
447
|
-
playerId: player.id,
|
|
448
|
-
profile: attackProfile.id,
|
|
449
|
-
hitboxes
|
|
450
|
-
});
|
|
451
|
-
scheduleActionBattleStartup(attackProfile, () => {
|
|
452
|
-
map?.createMovingHitbox(hitboxes, { speed: resolveActionBattleHitboxSpeed(attackProfile, hitboxes.length) }).subscribe({ next(hits) {
|
|
453
|
-
hits.forEach((hit) => {
|
|
454
|
-
if (hit instanceof RpgEvent) {
|
|
455
|
-
if (!hitTracker.tryHit(hit)) return;
|
|
456
|
-
if (applyPlayerHitToEvent(player, hit, void 0, {
|
|
457
|
-
attackId,
|
|
458
|
-
attackProfileId: attackProfile.id,
|
|
459
|
-
reaction: attackProfile.reaction
|
|
460
|
-
})?.defeated) console.log(`Player ${player.id} defeated AI ${hit.id}`);
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
} });
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
},
|
|
467
|
-
onConnected(player) {
|
|
468
|
-
if (options.ui?.actionBar?.enabled && options.ui?.actionBar?.autoOpen) openActionBattleActionBar(player, options);
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
event: {
|
|
472
|
-
/**
|
|
473
|
-
* Handle player detection when entering AI vision
|
|
474
|
-
*
|
|
475
|
-
* Called when a player enters an AI event's vision range.
|
|
476
|
-
* The AI will start pursuing and attacking the player.
|
|
477
|
-
*
|
|
478
|
-
* @param event - The AI event
|
|
479
|
-
* @param player - The player entering vision
|
|
480
|
-
* @param shape - The vision shape
|
|
481
|
-
*/
|
|
482
|
-
onDetectInShape(event, player, shape) {
|
|
483
|
-
event.battleAi?.onDetectInShape(player, shape);
|
|
484
|
-
},
|
|
485
|
-
/**
|
|
486
|
-
* Handle player leaving AI vision
|
|
487
|
-
*
|
|
488
|
-
* Called when a player leaves an AI event's vision range.
|
|
489
|
-
* The AI will stop pursuing the player.
|
|
490
|
-
*
|
|
491
|
-
* @param event - The AI event
|
|
492
|
-
* @param player - The player leaving vision
|
|
493
|
-
* @param shape - The vision shape
|
|
494
|
-
*/
|
|
495
|
-
onDetectOutShape(event, player, shape) {
|
|
496
|
-
event.battleAi?.onDetectOutShape(player, shape);
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
});
|
|
213
|
+
var handleActionBattleProjectileImpact = (context) => {
|
|
214
|
+
const handler = projectileHandlers.get(context.projectile.id);
|
|
215
|
+
if (!handler) return;
|
|
216
|
+
const target = context.target;
|
|
217
|
+
handler.action.target = target ?? handler.action.target;
|
|
218
|
+
if (handler.onImpact) handler.onImpact(context, handler.action);
|
|
219
|
+
else handler.action.defaultEffect(target ?? void 0);
|
|
220
|
+
};
|
|
221
|
+
var handleActionBattleProjectileDestroy = (projectileId) => {
|
|
222
|
+
projectileHandlers.delete(projectileId);
|
|
500
223
|
};
|
|
501
|
-
var server_default = createActionBattleServer();
|
|
502
224
|
//#endregion
|
|
503
|
-
export {
|
|
225
|
+
export { canActionBattleUseTarget, executeActionBattleUse, getActionBattleActionConfig, getActionBattleActionRange, handleActionBattleProjectileDestroy, handleActionBattleProjectileImpact, shouldUseActionBattleUsable };
|