@rpgjs/action-battle 5.0.0-beta.12 → 5.0.0-beta.14
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 +21 -0
- package/dist/client/ai.server.d.ts +12 -0
- package/dist/client/core/defaults.d.ts +1 -1
- package/dist/client/index17.js +11 -1
- package/dist/client/index21.js +2 -1
- package/dist/client/index22.js +2 -1
- package/dist/client/index26.js +316 -74
- package/dist/server/ai.server.d.ts +12 -0
- package/dist/server/core/defaults.d.ts +1 -1
- package/dist/server/index13.js +2 -1
- package/dist/server/index14.js +2 -1
- package/dist/server/index19.js +316 -74
- package/dist/server/index7.js +11 -1
- package/package.json +5 -5
- package/src/ai.server.spec.ts +147 -1
- package/src/ai.server.ts +375 -68
- package/src/core/action-use.ts +2 -1
- package/src/core/defaults.ts +16 -1
- package/src/core/hit.spec.ts +21 -0
- package/src/core/targets.spec.ts +12 -0
- package/src/core/targets.ts +4 -1
- package/src/visual.ts +1 -1
package/dist/server/index13.js
CHANGED
|
@@ -5,9 +5,10 @@ var ACTION_BATTLE_ENEMY_FACTION = "enemies";
|
|
|
5
5
|
var getBattleAi = (entity) => entity?.battleAi;
|
|
6
6
|
var getActionBattleEntityKind = (entity) => {
|
|
7
7
|
if (getBattleAi(entity)) return "event";
|
|
8
|
+
if (typeof entity?.isEvent === "function") return entity.isEvent() ? "event" : "player";
|
|
8
9
|
if (entity instanceof RpgEvent) return "event";
|
|
9
|
-
if (typeof entity?.attachShape === "function") return "event";
|
|
10
10
|
if (entity instanceof RpgPlayer) return "player";
|
|
11
|
+
if (typeof entity?.attachShape === "function") return "event";
|
|
11
12
|
return "player";
|
|
12
13
|
};
|
|
13
14
|
var isActionBattlePlayer = (entity) => getActionBattleEntityKind(entity) === "player";
|
package/dist/server/index14.js
CHANGED
|
@@ -62,9 +62,10 @@ var applyDamageEffect = (attacker, target, skill, reaction, metadata) => {
|
|
|
62
62
|
});
|
|
63
63
|
if (!result.cancelled) {
|
|
64
64
|
emitActionBattleClientVisual({
|
|
65
|
-
moment: "
|
|
65
|
+
moment: "hurt",
|
|
66
66
|
entity: attacker,
|
|
67
67
|
target,
|
|
68
|
+
attacker,
|
|
68
69
|
damage: result.damage,
|
|
69
70
|
result,
|
|
70
71
|
skill
|
package/dist/server/index19.js
CHANGED
|
@@ -14,6 +14,11 @@ import { safeActionBattleDash } from "./index17.js";
|
|
|
14
14
|
import { defineAiBehavior, defineAiTree } from "./index18.js";
|
|
15
15
|
import { MAXHP } from "@rpgjs/server";
|
|
16
16
|
//#region src/ai.server.ts
|
|
17
|
+
var resolveMoveCoordinate = (value) => {
|
|
18
|
+
const raw = typeof value === "function" ? value() : value;
|
|
19
|
+
return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
|
|
20
|
+
};
|
|
21
|
+
var createMoveSignature = (x, y) => `position:${Math.round(x)}:${Math.round(y)}`;
|
|
17
22
|
var normalizeRewardItem = (item) => {
|
|
18
23
|
if (typeof item === "string") return {
|
|
19
24
|
itemId: item,
|
|
@@ -97,14 +102,7 @@ var AiDebug = {
|
|
|
97
102
|
* @param message - Log message
|
|
98
103
|
* @param data - Optional additional data
|
|
99
104
|
*/
|
|
100
|
-
log(category, eventId, message, data) {
|
|
101
|
-
if (!this.enabled) return;
|
|
102
|
-
if (this.filterEventId && eventId !== this.filterEventId) return;
|
|
103
|
-
if (this.categories.length > 0 && !this.categories.includes(category)) return;
|
|
104
|
-
const prefix = `[AI:${category}]${eventId ? ` [${eventId.substring(0, 8)}]` : ""}`;
|
|
105
|
-
if (data !== void 0) console.log(prefix, message, data);
|
|
106
|
-
else console.log(prefix, message);
|
|
107
|
-
}
|
|
105
|
+
log(category, eventId, message, data) {}
|
|
108
106
|
};
|
|
109
107
|
/**
|
|
110
108
|
* AI State enumeration
|
|
@@ -237,6 +235,27 @@ var BattleAi = class {
|
|
|
237
235
|
debugLog(category, message, data) {
|
|
238
236
|
AiDebug.log(category, this.event.id, message, data);
|
|
239
237
|
}
|
|
238
|
+
traceLog(category, message, data) {}
|
|
239
|
+
lockActionUntil(until, reason, data) {
|
|
240
|
+
if (until <= this.actionLockedUntil) return;
|
|
241
|
+
this.actionLockedUntil = until;
|
|
242
|
+
this.traceLog("state", "action locked", {
|
|
243
|
+
reason,
|
|
244
|
+
lockedMs: Math.max(0, until - Date.now()),
|
|
245
|
+
...data
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
lockForAttack(profile, pattern) {
|
|
249
|
+
this.isMovingToTarget = false;
|
|
250
|
+
this.event.stopMoveTo();
|
|
251
|
+
this.lockActionUntil(Date.now() + profile.totalDurationMs, "attack", {
|
|
252
|
+
pattern,
|
|
253
|
+
totalDurationMs: profile.totalDurationMs,
|
|
254
|
+
startupMs: profile.startupMs,
|
|
255
|
+
activeMs: profile.activeMs,
|
|
256
|
+
recoveryMs: profile.recoveryMs
|
|
257
|
+
});
|
|
258
|
+
}
|
|
240
259
|
state = AiState.Idle;
|
|
241
260
|
stateStartTime = 0;
|
|
242
261
|
stunnedUntil = 0;
|
|
@@ -283,6 +302,11 @@ var BattleAi = class {
|
|
|
283
302
|
lastMoveToTime = 0;
|
|
284
303
|
retreatCooldown = 600;
|
|
285
304
|
lastRetreatTime = 0;
|
|
305
|
+
actionLockedUntil = 0;
|
|
306
|
+
lastActionLockTraceTime = 0;
|
|
307
|
+
lastMoveToCooldownTraceTime = 0;
|
|
308
|
+
lastMoveToCooldownTraceSignature = null;
|
|
309
|
+
lastTargetMovementSkipTraceTime = 0;
|
|
286
310
|
timers = [];
|
|
287
311
|
behaviorKey;
|
|
288
312
|
behaviorTree;
|
|
@@ -294,6 +318,7 @@ var BattleAi = class {
|
|
|
294
318
|
visionSetupRetries = 0;
|
|
295
319
|
maxVisionSetupRetries = 20;
|
|
296
320
|
destroyed = false;
|
|
321
|
+
lastNoTargetTraceTime = 0;
|
|
297
322
|
constructor(event, options = {}) {
|
|
298
323
|
options = mergeBattleAiPresetOptions(options);
|
|
299
324
|
event.battleAi = this;
|
|
@@ -407,7 +432,13 @@ var BattleAi = class {
|
|
|
407
432
|
setupVision() {
|
|
408
433
|
if (this.visionShape) return true;
|
|
409
434
|
const map = this.event.getCurrentMap?.();
|
|
410
|
-
if (map?.physic?.getEntityByUUID && !map.physic.getEntityByUUID(this.event.id))
|
|
435
|
+
if (map?.physic?.getEntityByUUID && !map.physic.getEntityByUUID(this.event.id)) {
|
|
436
|
+
this.traceLog("vision", "physics body not ready", {
|
|
437
|
+
retries: this.visionSetupRetries,
|
|
438
|
+
hasMap: !!map
|
|
439
|
+
});
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
411
442
|
const diameter = this.visionRange * 2;
|
|
412
443
|
const shape = this.event.attachShape(`vision_${this.event.id}`, {
|
|
413
444
|
radius: this.visionRange,
|
|
@@ -415,13 +446,26 @@ var BattleAi = class {
|
|
|
415
446
|
height: diameter,
|
|
416
447
|
angle: 360
|
|
417
448
|
});
|
|
418
|
-
if (!shape)
|
|
449
|
+
if (!shape) {
|
|
450
|
+
this.traceLog("vision", "attachShape returned no shape", {
|
|
451
|
+
retries: this.visionSetupRetries,
|
|
452
|
+
visionRange: this.visionRange
|
|
453
|
+
});
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
419
456
|
this.visionShape = shape;
|
|
457
|
+
this.traceLog("vision", "vision attached", {
|
|
458
|
+
shapeId: shape?.id,
|
|
459
|
+
visionRange: this.visionRange
|
|
460
|
+
});
|
|
420
461
|
return true;
|
|
421
462
|
}
|
|
422
463
|
scheduleVisionSetup() {
|
|
423
464
|
if (this.destroyed || this.setupVision()) return;
|
|
424
|
-
if (this.visionSetupRetries >= this.maxVisionSetupRetries)
|
|
465
|
+
if (this.visionSetupRetries >= this.maxVisionSetupRetries) {
|
|
466
|
+
this.traceLog("vision", "vision setup gave up", { retries: this.visionSetupRetries });
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
425
469
|
this.visionSetupRetries++;
|
|
426
470
|
this.schedule(() => {
|
|
427
471
|
if (this.destroyed || !this.event.getCurrentMap()) return;
|
|
@@ -458,9 +502,18 @@ var BattleAi = class {
|
|
|
458
502
|
[AiState.Stunned]: [AiState.Combat, AiState.Idle]
|
|
459
503
|
}[this.state].includes(newState)) {
|
|
460
504
|
this.debugLog("state", `INVALID transition ${this.state} -> ${newState}`);
|
|
505
|
+
this.traceLog("state", "invalid transition", {
|
|
506
|
+
from: this.state,
|
|
507
|
+
to: newState
|
|
508
|
+
});
|
|
461
509
|
return;
|
|
462
510
|
}
|
|
463
511
|
this.debugLog("state", `STATE change: ${this.state} -> ${newState}`);
|
|
512
|
+
this.traceLog("state", "state change", {
|
|
513
|
+
from: this.state,
|
|
514
|
+
to: newState,
|
|
515
|
+
targetId: this.target?.id
|
|
516
|
+
});
|
|
464
517
|
this.state = newState;
|
|
465
518
|
this.stateStartTime = Date.now();
|
|
466
519
|
switch (newState) {
|
|
@@ -498,6 +551,19 @@ var BattleAi = class {
|
|
|
498
551
|
if (currentTime >= this.stunnedUntil) this.changeState(AiState.Combat);
|
|
499
552
|
return;
|
|
500
553
|
}
|
|
554
|
+
if (currentTime < this.actionLockedUntil) {
|
|
555
|
+
if (this.target) this.faceTarget();
|
|
556
|
+
if (currentTime - this.lastActionLockTraceTime > 250) {
|
|
557
|
+
this.lastActionLockTraceTime = currentTime;
|
|
558
|
+
this.traceLog("state", "waiting action recovery", {
|
|
559
|
+
remainingMs: this.actionLockedUntil - currentTime,
|
|
560
|
+
state: this.state,
|
|
561
|
+
targetId: this.target?.id
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
this.checkDamageTaken();
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
501
567
|
if (this.enemyType === EnemyType.Berserker && this.event.param[MAXHP]) {
|
|
502
568
|
const hpPercent = this.event.hp / this.event.param[MAXHP];
|
|
503
569
|
const berserkerModifier = Math.max(.3, hpPercent);
|
|
@@ -554,7 +620,30 @@ var BattleAi = class {
|
|
|
554
620
|
updateAlertBehavior() {
|
|
555
621
|
if (this.target) {
|
|
556
622
|
this.faceTarget();
|
|
557
|
-
|
|
623
|
+
const distance = this.getDistance(this.event, this.target);
|
|
624
|
+
this.traceLog("movement", "alert update", {
|
|
625
|
+
targetId: this.target.id,
|
|
626
|
+
distance,
|
|
627
|
+
attackRange: this.attackRange,
|
|
628
|
+
visionRange: this.visionRange,
|
|
629
|
+
isMovingToTarget: this.isMovingToTarget
|
|
630
|
+
});
|
|
631
|
+
if (distance <= this.attackRange * 1.5) {
|
|
632
|
+
if (this.isMovingToTarget) {
|
|
633
|
+
this.isMovingToTarget = false;
|
|
634
|
+
this.event.stopMoveTo();
|
|
635
|
+
}
|
|
636
|
+
this.changeState(AiState.Combat);
|
|
637
|
+
} else if (distance <= this.visionRange * 1.5) {
|
|
638
|
+
if (!this.isMovingToTarget) {
|
|
639
|
+
this.debugLog("movement", `Alert approach (dist=${distance.toFixed(1)}, attackRange=${this.attackRange})`);
|
|
640
|
+
this.requestTargetMovement();
|
|
641
|
+
}
|
|
642
|
+
} else {
|
|
643
|
+
this.debugLog("combat", `Alert target out of range (dist=${distance.toFixed(1)})`);
|
|
644
|
+
this.clearTarget();
|
|
645
|
+
this.changeState(AiState.Idle);
|
|
646
|
+
}
|
|
558
647
|
} else this.changeState(AiState.Idle);
|
|
559
648
|
}
|
|
560
649
|
/**
|
|
@@ -567,6 +656,15 @@ var BattleAi = class {
|
|
|
567
656
|
return;
|
|
568
657
|
}
|
|
569
658
|
const distance = this.getDistance(this.event, this.target);
|
|
659
|
+
this.traceLog("combat", "combat update", {
|
|
660
|
+
targetId: this.target.id,
|
|
661
|
+
distance,
|
|
662
|
+
attackRange: this.attackRange,
|
|
663
|
+
visionRange: this.visionRange,
|
|
664
|
+
isMovingToTarget: this.isMovingToTarget,
|
|
665
|
+
behaviorEnabled: this.behaviorEnabled,
|
|
666
|
+
behaviorMode: this.behaviorMode
|
|
667
|
+
});
|
|
570
668
|
if (distance > this.visionRange * 1.5) {
|
|
571
669
|
this.debugLog("combat", `Target out of range (dist=${distance.toFixed(1)}, maxRange=${(this.visionRange * 1.5).toFixed(1)})`);
|
|
572
670
|
this.clearTarget();
|
|
@@ -606,8 +704,7 @@ var BattleAi = class {
|
|
|
606
704
|
} else if (distance > this.attackRange) {
|
|
607
705
|
if (!this.isMovingToTarget) {
|
|
608
706
|
this.debugLog("movement", `Moving to target (dist=${distance.toFixed(1)}, attackRange=${this.attackRange})`);
|
|
609
|
-
this.
|
|
610
|
-
this.requestMoveTo(this.target);
|
|
707
|
+
this.requestTargetMovement();
|
|
611
708
|
}
|
|
612
709
|
} else if (this.isMovingToTarget) {
|
|
613
710
|
this.debugLog("movement", `In range, stopping (dist=${distance.toFixed(1)})`);
|
|
@@ -617,8 +714,7 @@ var BattleAi = class {
|
|
|
617
714
|
} else if (distance > this.attackRange) {
|
|
618
715
|
if (!this.isMovingToTarget) {
|
|
619
716
|
this.debugLog("movement", `Moving to target (dist=${distance.toFixed(1)}, attackRange=${this.attackRange})`);
|
|
620
|
-
this.
|
|
621
|
-
this.requestMoveTo(this.target);
|
|
717
|
+
this.requestTargetMovement();
|
|
622
718
|
}
|
|
623
719
|
} else if (this.isMovingToTarget) {
|
|
624
720
|
this.debugLog("movement", `In range, stopping (dist=${distance.toFixed(1)})`);
|
|
@@ -741,15 +837,9 @@ var BattleAi = class {
|
|
|
741
837
|
if (!this.target) return;
|
|
742
838
|
const profile = this.getAttackProfile(AttackPattern.Melee);
|
|
743
839
|
this.faceTarget({ force: true });
|
|
840
|
+
this.lockForAttack(profile, AttackPattern.Melee);
|
|
744
841
|
this.telegraphAttack(profile);
|
|
745
|
-
|
|
746
|
-
emitActionBattleClientVisual({
|
|
747
|
-
moment: "attack",
|
|
748
|
-
entity: this.event,
|
|
749
|
-
target: this.target,
|
|
750
|
-
animations: this.animations
|
|
751
|
-
});
|
|
752
|
-
});
|
|
842
|
+
this.playAttackVisual(profile, AttackPattern.Melee);
|
|
753
843
|
this.scheduleAttackStartup(profile, () => {
|
|
754
844
|
this.executeMeleeAttack(profile, AttackPattern.Melee);
|
|
755
845
|
});
|
|
@@ -887,12 +977,12 @@ var BattleAi = class {
|
|
|
887
977
|
}
|
|
888
978
|
withActionBattleAnimationUnlocked(target, () => {
|
|
889
979
|
emitActionBattleClientVisual({
|
|
890
|
-
moment: "
|
|
980
|
+
moment: "hurt",
|
|
891
981
|
entity: this.event,
|
|
892
982
|
target,
|
|
983
|
+
attacker: this.event,
|
|
893
984
|
damage: hitResult.damage,
|
|
894
|
-
result: hitResult
|
|
895
|
-
animations: this.animations
|
|
985
|
+
result: hitResult
|
|
896
986
|
});
|
|
897
987
|
});
|
|
898
988
|
setActionBattleInvincibility(target, profile.reaction.invincibilityMs);
|
|
@@ -953,15 +1043,9 @@ var BattleAi = class {
|
|
|
953
1043
|
this.comboCount++;
|
|
954
1044
|
const profile = this.getAttackProfile(AttackPattern.Combo);
|
|
955
1045
|
this.faceTarget({ force: true });
|
|
1046
|
+
this.lockForAttack(profile, AttackPattern.Combo);
|
|
956
1047
|
this.telegraphAttack(profile);
|
|
957
|
-
|
|
958
|
-
emitActionBattleClientVisual({
|
|
959
|
-
moment: "attack",
|
|
960
|
-
entity: this.event,
|
|
961
|
-
target: this.target,
|
|
962
|
-
animations: this.animations
|
|
963
|
-
});
|
|
964
|
-
});
|
|
1048
|
+
this.playAttackVisual(profile, AttackPattern.Combo);
|
|
965
1049
|
this.scheduleAttackStartup(profile, () => {
|
|
966
1050
|
this.executeMeleeAttack(profile, AttackPattern.Combo);
|
|
967
1051
|
});
|
|
@@ -979,16 +1063,9 @@ var BattleAi = class {
|
|
|
979
1063
|
const profile = this.getAttackProfile(AttackPattern.Charged);
|
|
980
1064
|
this.chargingAttack = true;
|
|
981
1065
|
this.faceTarget({ force: true });
|
|
1066
|
+
this.lockForAttack(profile, AttackPattern.Charged);
|
|
982
1067
|
this.telegraphAttack(profile);
|
|
983
|
-
|
|
984
|
-
emitActionBattleClientVisual({
|
|
985
|
-
moment: "attack",
|
|
986
|
-
entity: this.event,
|
|
987
|
-
target: this.target,
|
|
988
|
-
animations: this.animations,
|
|
989
|
-
animationDefaults: { repeat: 2 }
|
|
990
|
-
});
|
|
991
|
-
});
|
|
1068
|
+
this.playAttackVisual(profile, AttackPattern.Charged, { repeat: 2 });
|
|
992
1069
|
this.scheduleAttackStartup(profile, () => {
|
|
993
1070
|
if (!this.target || this.state !== AiState.Combat) {
|
|
994
1071
|
this.chargingAttack = false;
|
|
@@ -1005,15 +1082,9 @@ var BattleAi = class {
|
|
|
1005
1082
|
*/
|
|
1006
1083
|
performZoneAttack() {
|
|
1007
1084
|
const profile = this.getAttackProfile(AttackPattern.Zone);
|
|
1085
|
+
this.lockForAttack(profile, AttackPattern.Zone);
|
|
1008
1086
|
this.telegraphAttack(profile);
|
|
1009
|
-
|
|
1010
|
-
emitActionBattleClientVisual({
|
|
1011
|
-
moment: "attack",
|
|
1012
|
-
entity: this.event,
|
|
1013
|
-
target: this.target ?? void 0,
|
|
1014
|
-
animations: this.animations
|
|
1015
|
-
});
|
|
1016
|
-
});
|
|
1087
|
+
this.playAttackVisual(profile, AttackPattern.Zone);
|
|
1017
1088
|
const eventX = this.event.x();
|
|
1018
1089
|
const eventY = this.event.y();
|
|
1019
1090
|
const radius = 50;
|
|
@@ -1055,7 +1126,9 @@ var BattleAi = class {
|
|
|
1055
1126
|
const dirX = dx / dist;
|
|
1056
1127
|
const dirY = dy / dist;
|
|
1057
1128
|
this.faceTarget({ force: true });
|
|
1129
|
+
this.lockForAttack(profile, AttackPattern.DashAttack);
|
|
1058
1130
|
this.telegraphAttack(profile);
|
|
1131
|
+
this.playAttackVisual(profile, AttackPattern.DashAttack);
|
|
1059
1132
|
this.scheduleAttackStartup(profile, () => {
|
|
1060
1133
|
if (!this.target || this.state !== AiState.Combat) return;
|
|
1061
1134
|
safeActionBattleDash(this.event, {
|
|
@@ -1071,6 +1144,19 @@ var BattleAi = class {
|
|
|
1071
1144
|
getAttackProfile(pattern) {
|
|
1072
1145
|
return this.attackProfiles[pattern] ?? this.attackProfiles.melee;
|
|
1073
1146
|
}
|
|
1147
|
+
playAttackVisual(profile, pattern, animationDefaults) {
|
|
1148
|
+
const moment = profile.animationKey === "castSkill" || profile.animationKey === "castSpell" ? "castSkill" : "attack";
|
|
1149
|
+
withActionBattleAnimationUnlocked(this.event, () => {
|
|
1150
|
+
emitActionBattleClientVisual({
|
|
1151
|
+
moment,
|
|
1152
|
+
entity: this.event,
|
|
1153
|
+
target: this.target ?? void 0,
|
|
1154
|
+
pattern,
|
|
1155
|
+
animations: this.animations,
|
|
1156
|
+
animationDefaults
|
|
1157
|
+
});
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1074
1160
|
telegraphAttack(profile) {
|
|
1075
1161
|
if (profile.startupMs <= 0) return;
|
|
1076
1162
|
this.event.flash({
|
|
@@ -1163,10 +1249,11 @@ var BattleAi = class {
|
|
|
1163
1249
|
const dy = this.event.y() - this.target.y();
|
|
1164
1250
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1165
1251
|
if (dist === 0) return;
|
|
1166
|
-
|
|
1167
|
-
x:
|
|
1168
|
-
y:
|
|
1169
|
-
}
|
|
1252
|
+
const fleeTarget = {
|
|
1253
|
+
x: this.event.x() + dx / dist * 200,
|
|
1254
|
+
y: this.event.y() + dy / dist * 200
|
|
1255
|
+
};
|
|
1256
|
+
this.requestMoveTo(fleeTarget);
|
|
1170
1257
|
}
|
|
1171
1258
|
/**
|
|
1172
1259
|
* Retreat from target (temporary)
|
|
@@ -1202,8 +1289,8 @@ var BattleAi = class {
|
|
|
1202
1289
|
if (this.patrolWaypoints.length === 0) return;
|
|
1203
1290
|
const waypoint = this.patrolWaypoints[this.currentPatrolIndex];
|
|
1204
1291
|
this.requestMoveTo({
|
|
1205
|
-
x:
|
|
1206
|
-
y:
|
|
1292
|
+
x: waypoint.x,
|
|
1293
|
+
y: waypoint.y
|
|
1207
1294
|
});
|
|
1208
1295
|
}
|
|
1209
1296
|
/**
|
|
@@ -1249,19 +1336,35 @@ var BattleAi = class {
|
|
|
1249
1336
|
const formationX = this.target.x() + Math.cos(angle) * formationRadius;
|
|
1250
1337
|
const formationY = this.target.y() + Math.sin(angle) * formationRadius;
|
|
1251
1338
|
if (Math.sqrt(Math.pow(this.event.x() - formationX, 2) + Math.pow(this.event.y() - formationY, 2)) > 20) this.requestMoveTo({
|
|
1252
|
-
x:
|
|
1253
|
-
y:
|
|
1339
|
+
x: formationX,
|
|
1340
|
+
y: formationY
|
|
1254
1341
|
});
|
|
1255
1342
|
}
|
|
1256
1343
|
/**
|
|
1257
1344
|
* Handle player entering vision
|
|
1258
1345
|
*/
|
|
1259
1346
|
onDetectInShape(target, shape) {
|
|
1260
|
-
|
|
1347
|
+
const canTarget = this.canTarget(target);
|
|
1348
|
+
const defeated = this.isTargetDefeated(target);
|
|
1349
|
+
this.traceLog("vision", "detect in shape", {
|
|
1350
|
+
targetId: target?.id,
|
|
1351
|
+
shapeId: shape?.id,
|
|
1352
|
+
canTarget,
|
|
1353
|
+
defeated,
|
|
1354
|
+
state: this.state,
|
|
1355
|
+
targetHp: target?.hp
|
|
1356
|
+
});
|
|
1357
|
+
if (!canTarget || defeated) return;
|
|
1261
1358
|
this.debugLog("vision", `Target ${target.id} entered vision (state=${this.state})`);
|
|
1262
1359
|
this.engageTarget(target);
|
|
1263
1360
|
}
|
|
1264
1361
|
engageTarget(target) {
|
|
1362
|
+
this.traceLog("target", "engage target", {
|
|
1363
|
+
targetId: target.id,
|
|
1364
|
+
previousTargetId: this.target?.id,
|
|
1365
|
+
state: this.state,
|
|
1366
|
+
distance: this.getDistance(this.event, target)
|
|
1367
|
+
});
|
|
1265
1368
|
this.target = target;
|
|
1266
1369
|
if (this.state === AiState.Idle) this.changeState(AiState.Alert);
|
|
1267
1370
|
else if (this.state === AiState.Alert) this.changeState(AiState.Combat);
|
|
@@ -1271,6 +1374,12 @@ var BattleAi = class {
|
|
|
1271
1374
|
*/
|
|
1272
1375
|
onDetectOutShape(target, shape) {
|
|
1273
1376
|
this.debugLog("vision", `Target ${target.id} left vision (wasTarget=${this.target === target})`);
|
|
1377
|
+
this.traceLog("vision", "detect out shape", {
|
|
1378
|
+
targetId: target?.id,
|
|
1379
|
+
shapeId: shape?.id,
|
|
1380
|
+
wasTarget: this.target === target,
|
|
1381
|
+
state: this.state
|
|
1382
|
+
});
|
|
1274
1383
|
if (this.target === target) {
|
|
1275
1384
|
this.clearTarget();
|
|
1276
1385
|
this.changeState(AiState.Idle);
|
|
@@ -1293,8 +1402,21 @@ var BattleAi = class {
|
|
|
1293
1402
|
}
|
|
1294
1403
|
handleDamage(attacker, damageResult) {
|
|
1295
1404
|
if (this.defeated) return true;
|
|
1296
|
-
const damage = damageResult.damage;
|
|
1405
|
+
const damage = Number.isFinite(damageResult.damage) ? damageResult.damage : 0;
|
|
1297
1406
|
this.debugLog("damage", `Took ${damage} damage from ${attacker.id} (HP: ${this.event.hp}/${this.event.param[MAXHP] || "?"})`);
|
|
1407
|
+
const canRetaliate = attacker ? this.canTarget(attacker) : false;
|
|
1408
|
+
const attackerDefeated = this.isTargetDefeated(attacker);
|
|
1409
|
+
this.traceLog("damage", "handle damage", {
|
|
1410
|
+
attackerId: attacker?.id,
|
|
1411
|
+
damage,
|
|
1412
|
+
defeated: damageResult.defeated,
|
|
1413
|
+
eventHp: this.event.hp,
|
|
1414
|
+
maxHp: this.event.param[MAXHP],
|
|
1415
|
+
state: this.state,
|
|
1416
|
+
canRetaliate,
|
|
1417
|
+
attackerDefeated,
|
|
1418
|
+
currentTargetId: this.target?.id
|
|
1419
|
+
});
|
|
1298
1420
|
withActionBattleAnimationUnlocked(this.event, () => {
|
|
1299
1421
|
emitActionBattleClientVisual({
|
|
1300
1422
|
moment: "hurt",
|
|
@@ -1308,10 +1430,27 @@ var BattleAi = class {
|
|
|
1308
1430
|
});
|
|
1309
1431
|
});
|
|
1310
1432
|
this.recentDamageTaken += damage;
|
|
1433
|
+
if (attacker && this.canTarget(attacker) && !this.isTargetDefeated(attacker) && this.state !== AiState.Flee) {
|
|
1434
|
+
this.traceLog("target", "retaliate against attacker", {
|
|
1435
|
+
attackerId: attacker.id,
|
|
1436
|
+
previousTargetId: this.target?.id,
|
|
1437
|
+
state: this.state
|
|
1438
|
+
});
|
|
1439
|
+
this.target = attacker;
|
|
1440
|
+
if (this.state === AiState.Idle || this.state === AiState.Alert) {
|
|
1441
|
+
this.isMovingToTarget = false;
|
|
1442
|
+
this.changeState(AiState.Combat);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1311
1445
|
const reaction = damageResult.reaction;
|
|
1312
1446
|
const staggerPower = reaction?.staggerPower ?? damage;
|
|
1313
1447
|
const hitstunMs = reaction?.hitstunMs ?? this.hitstunMs;
|
|
1314
|
-
const shouldStun = staggerPower >= this.poise && hitstunMs > 0;
|
|
1448
|
+
const shouldStun = (damage > 0 || (reaction?.staggerPower ?? 0) > 0) && staggerPower >= this.poise && hitstunMs > 0;
|
|
1449
|
+
this.lockActionUntil(Date.now() + Math.max(220, hitstunMs + 120), "damage recovery", {
|
|
1450
|
+
attackerId: attacker?.id,
|
|
1451
|
+
damage,
|
|
1452
|
+
hitstunMs
|
|
1453
|
+
});
|
|
1315
1454
|
setActionBattleInvincibility(this.event, reaction?.invincibilityMs ?? this.invincibilityMs);
|
|
1316
1455
|
if (shouldStun && this.state !== AiState.Stunned && this.state !== AiState.Flee) {
|
|
1317
1456
|
this.debugLog("damage", "Stunned from damage");
|
|
@@ -1394,7 +1533,10 @@ var BattleAi = class {
|
|
|
1394
1533
|
}
|
|
1395
1534
|
findNearestTarget() {
|
|
1396
1535
|
const map = this.event.getCurrentMap();
|
|
1397
|
-
if (!map)
|
|
1536
|
+
if (!map) {
|
|
1537
|
+
this.traceLog("target", "find nearest skipped: no map");
|
|
1538
|
+
return null;
|
|
1539
|
+
}
|
|
1398
1540
|
const candidates = [];
|
|
1399
1541
|
map.getPlayers?.().forEach((player) => candidates.push(player));
|
|
1400
1542
|
map.getEvents?.().forEach((event) => candidates.push(event));
|
|
@@ -1409,6 +1551,29 @@ var BattleAi = class {
|
|
|
1409
1551
|
nearestDistance = distance;
|
|
1410
1552
|
}
|
|
1411
1553
|
}
|
|
1554
|
+
const now = Date.now();
|
|
1555
|
+
if (nearest) this.traceLog("target", "nearest target found", {
|
|
1556
|
+
targetId: nearest.id,
|
|
1557
|
+
distance: nearestDistance,
|
|
1558
|
+
visionRange: this.visionRange,
|
|
1559
|
+
candidates: candidates.length
|
|
1560
|
+
});
|
|
1561
|
+
else if (now - this.lastNoTargetTraceTime > 1e3) {
|
|
1562
|
+
this.lastNoTargetTraceTime = now;
|
|
1563
|
+
this.traceLog("target", "no target found", {
|
|
1564
|
+
visionRange: this.visionRange,
|
|
1565
|
+
candidates: candidates.map((candidate) => {
|
|
1566
|
+
const distance = this.getDistance(this.event, candidate);
|
|
1567
|
+
return {
|
|
1568
|
+
id: candidate.id,
|
|
1569
|
+
hp: candidate.hp,
|
|
1570
|
+
canTarget: this.canTarget(candidate),
|
|
1571
|
+
defeated: this.isTargetDefeated(candidate),
|
|
1572
|
+
distance
|
|
1573
|
+
};
|
|
1574
|
+
})
|
|
1575
|
+
});
|
|
1576
|
+
}
|
|
1412
1577
|
return nearest;
|
|
1413
1578
|
}
|
|
1414
1579
|
isTargetDefeated(target) {
|
|
@@ -1540,8 +1705,7 @@ var BattleAi = class {
|
|
|
1540
1705
|
return consumes;
|
|
1541
1706
|
case "moveToTarget":
|
|
1542
1707
|
if (!this.target) return false;
|
|
1543
|
-
this.
|
|
1544
|
-
this.requestMoveTo(this.target);
|
|
1708
|
+
this.requestTargetMovement();
|
|
1545
1709
|
return consumes;
|
|
1546
1710
|
case "fleeFromTarget":
|
|
1547
1711
|
if (!this.target) return false;
|
|
@@ -1564,8 +1728,7 @@ var BattleAi = class {
|
|
|
1564
1728
|
return consumes;
|
|
1565
1729
|
}
|
|
1566
1730
|
if (distance > intent.distance + tolerance) {
|
|
1567
|
-
this.
|
|
1568
|
-
this.requestMoveTo(this.target);
|
|
1731
|
+
this.requestTargetMovement();
|
|
1569
1732
|
return consumes;
|
|
1570
1733
|
}
|
|
1571
1734
|
if (this.isMovingToTarget) {
|
|
@@ -1614,8 +1777,7 @@ var BattleAi = class {
|
|
|
1614
1777
|
if (distance > maxRange) {
|
|
1615
1778
|
if (!this.isMovingToTarget) {
|
|
1616
1779
|
this.debugLog("movement", `Tactical approach (dist=${distance.toFixed(1)}, maxRange=${maxRange.toFixed(1)})`);
|
|
1617
|
-
this.
|
|
1618
|
-
this.requestMoveTo(this.target);
|
|
1780
|
+
this.requestTargetMovement();
|
|
1619
1781
|
}
|
|
1620
1782
|
return;
|
|
1621
1783
|
}
|
|
@@ -1630,8 +1792,7 @@ var BattleAi = class {
|
|
|
1630
1792
|
if (distance > this.attackRange) {
|
|
1631
1793
|
if (!this.isMovingToTarget) {
|
|
1632
1794
|
this.debugLog("movement", `Assault approach (dist=${distance.toFixed(1)}, attackRange=${this.attackRange})`);
|
|
1633
|
-
this.
|
|
1634
|
-
this.requestMoveTo(this.target);
|
|
1795
|
+
this.requestTargetMovement();
|
|
1635
1796
|
}
|
|
1636
1797
|
return;
|
|
1637
1798
|
}
|
|
@@ -1641,13 +1802,94 @@ var BattleAi = class {
|
|
|
1641
1802
|
this.event.stopMoveTo();
|
|
1642
1803
|
}
|
|
1643
1804
|
}
|
|
1805
|
+
resolveMoveTarget(target) {
|
|
1806
|
+
if (!target) return null;
|
|
1807
|
+
const targetId = target.id !== void 0 && target.id !== null ? String(target.id) : void 0;
|
|
1808
|
+
const x = resolveMoveCoordinate(target.x);
|
|
1809
|
+
const y = resolveMoveCoordinate(target.y);
|
|
1810
|
+
if (targetId) return {
|
|
1811
|
+
kind: "entity",
|
|
1812
|
+
target,
|
|
1813
|
+
id: targetId,
|
|
1814
|
+
x,
|
|
1815
|
+
y,
|
|
1816
|
+
signature: `entity:${targetId}`
|
|
1817
|
+
};
|
|
1818
|
+
if (x === void 0 || y === void 0) return null;
|
|
1819
|
+
return {
|
|
1820
|
+
kind: "position",
|
|
1821
|
+
target: {
|
|
1822
|
+
x,
|
|
1823
|
+
y
|
|
1824
|
+
},
|
|
1825
|
+
x,
|
|
1826
|
+
y,
|
|
1827
|
+
signature: createMoveSignature(x, y)
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1644
1830
|
requestMoveTo(target) {
|
|
1645
1831
|
const currentTime = Date.now();
|
|
1646
|
-
|
|
1647
|
-
|
|
1832
|
+
const resolvedTarget = this.resolveMoveTarget(target);
|
|
1833
|
+
if (!resolvedTarget) {
|
|
1834
|
+
this.traceLog("movement", "moveTo skipped: invalid target", { target });
|
|
1835
|
+
return false;
|
|
1836
|
+
}
|
|
1837
|
+
if (currentTime - this.lastMoveToTime < this.moveToCooldown) {
|
|
1838
|
+
if (this.lastMoveToCooldownTraceSignature !== resolvedTarget.signature || currentTime - this.lastMoveToCooldownTraceTime > 1e3) {
|
|
1839
|
+
this.lastMoveToCooldownTraceTime = currentTime;
|
|
1840
|
+
this.lastMoveToCooldownTraceSignature = resolvedTarget.signature;
|
|
1841
|
+
this.traceLog("movement", "moveTo skipped: cooldown", {
|
|
1842
|
+
targetKind: resolvedTarget.kind,
|
|
1843
|
+
targetId: resolvedTarget.kind === "entity" ? resolvedTarget.id : void 0,
|
|
1844
|
+
targetPosition: {
|
|
1845
|
+
x: resolvedTarget.x,
|
|
1846
|
+
y: resolvedTarget.y
|
|
1847
|
+
},
|
|
1848
|
+
elapsed: currentTime - this.lastMoveToTime,
|
|
1849
|
+
moveToCooldown: this.moveToCooldown
|
|
1850
|
+
});
|
|
1851
|
+
}
|
|
1852
|
+
return false;
|
|
1853
|
+
}
|
|
1854
|
+
const map = this.event.getCurrentMap?.();
|
|
1855
|
+
const hasBody = !!map?.physic?.getEntityByUUID?.(this.event.id) || !!map?.getBody?.(this.event.id);
|
|
1856
|
+
this.traceLog("movement", "moveTo requested", {
|
|
1857
|
+
targetKind: resolvedTarget.kind,
|
|
1858
|
+
targetId: resolvedTarget.kind === "entity" ? resolvedTarget.id : void 0,
|
|
1859
|
+
eventPosition: {
|
|
1860
|
+
x: this.event.x?.(),
|
|
1861
|
+
y: this.event.y?.()
|
|
1862
|
+
},
|
|
1863
|
+
targetPosition: {
|
|
1864
|
+
x: resolvedTarget.x,
|
|
1865
|
+
y: resolvedTarget.y
|
|
1866
|
+
},
|
|
1867
|
+
hasMap: !!map,
|
|
1868
|
+
hasMovementBody: hasBody
|
|
1869
|
+
});
|
|
1870
|
+
this.event.moveTo(resolvedTarget.target);
|
|
1648
1871
|
this.lastMoveToTime = currentTime;
|
|
1649
1872
|
return true;
|
|
1650
1873
|
}
|
|
1874
|
+
requestTargetMovement(target = this.target) {
|
|
1875
|
+
if (!target) {
|
|
1876
|
+
this.traceLog("movement", "target movement skipped: no target");
|
|
1877
|
+
return false;
|
|
1878
|
+
}
|
|
1879
|
+
const started = this.requestMoveTo(target);
|
|
1880
|
+
if (started) this.isMovingToTarget = true;
|
|
1881
|
+
else {
|
|
1882
|
+
const now = Date.now();
|
|
1883
|
+
if (now - this.lastTargetMovementSkipTraceTime > 1e3) {
|
|
1884
|
+
this.lastTargetMovementSkipTraceTime = now;
|
|
1885
|
+
this.traceLog("movement", "target movement did not start", {
|
|
1886
|
+
targetId: target.id,
|
|
1887
|
+
isMovingToTarget: this.isMovingToTarget
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
return started;
|
|
1892
|
+
}
|
|
1651
1893
|
schedule(callback, delay) {
|
|
1652
1894
|
const timer = setTimeout(() => {
|
|
1653
1895
|
this.timers = this.timers.filter((entry) => entry !== timer);
|
package/dist/server/index7.js
CHANGED
|
@@ -80,9 +80,19 @@ var createDefaultPlayerHitboxResolver = (hitboxes = DEFAULT_ZELDA_PLAYER_HITBOXE
|
|
|
80
80
|
};
|
|
81
81
|
var defaultRpgjsDamageResolver = (context) => {
|
|
82
82
|
const target = context.target;
|
|
83
|
+
const previousHp = typeof target.hp === "number" && Number.isFinite(target.hp) ? target.hp : void 0;
|
|
83
84
|
const raw = target.applyDamage(context.attacker, context.skill);
|
|
85
|
+
const resolvedDamage = Number(raw?.damage ?? 0);
|
|
86
|
+
if (!Number.isFinite(resolvedDamage)) {
|
|
87
|
+
if (previousHp !== void 0) target.hp = previousHp;
|
|
88
|
+
return {
|
|
89
|
+
damage: 0,
|
|
90
|
+
defeated: false,
|
|
91
|
+
raw
|
|
92
|
+
};
|
|
93
|
+
}
|
|
84
94
|
return {
|
|
85
|
-
damage:
|
|
95
|
+
damage: resolvedDamage,
|
|
86
96
|
defeated: target.hp <= 0,
|
|
87
97
|
raw
|
|
88
98
|
};
|