hrbattle 1.0.7 → 1.0.8

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.
Files changed (48) hide show
  1. package/dist/battle/battleCore.d.ts +5 -5
  2. package/dist/battle/battleCore.js +7 -7
  3. package/dist/battle/config/jsonConfigLoader.d.ts +1 -1
  4. package/dist/battle/config/sampleData.d.ts +1 -1
  5. package/dist/battle/effectSystem.d.ts +2 -2
  6. package/dist/battle/eventBus.d.ts +1 -1
  7. package/dist/battle/formula.d.ts +2 -2
  8. package/dist/battle/logger.d.ts +1 -1
  9. package/dist/battle/script/designedScripts.d.ts +1 -1
  10. package/dist/battle/script/designedScripts.js +4 -4
  11. package/dist/battle/script/monsterScripts.d.ts +1 -1
  12. package/dist/battle/script/monsterScripts18xx.d.ts +1 -1
  13. package/dist/battle/script/monsterScripts19xx.d.ts +1 -1
  14. package/dist/battle/script/monsterScripts19xxPart2.d.ts +1 -1
  15. package/dist/battle/script/sampleScripts.d.ts +1 -1
  16. package/dist/battle/skillEngine.d.ts +2 -2
  17. package/dist/battle/skillEngine.js +2 -2
  18. package/dist/battle/targeting.d.ts +2 -2
  19. package/dist/battle/turnbar.d.ts +1 -1
  20. package/dist/cocos-adapter/BattleFacade.d.ts +2 -2
  21. package/dist/cocos-adapter/BattleFacade.js +1 -1
  22. package/dist/cocos-adapter/ReusableBattleFacade.d.ts +3 -3
  23. package/dist/cocos-adapter/ReusableBattleFacade.js +3 -3
  24. package/dist/cocos-adapter/clientBattleResult.d.ts +2 -2
  25. package/dist/cocos-adapter/clientBattleResult.js +47 -11
  26. package/dist/index.d.ts +13 -13
  27. package/dist/index.js +10 -10
  28. package/package.json +1 -1
  29. package/src/battle/battleCore.ts +10 -10
  30. package/src/battle/config/jsonConfigLoader.ts +1 -1
  31. package/src/battle/config/sampleData.ts +1 -1
  32. package/src/battle/effectSystem.ts +2 -2
  33. package/src/battle/eventBus.ts +1 -1
  34. package/src/battle/formula.ts +2 -2
  35. package/src/battle/logger.ts +1 -1
  36. package/src/battle/script/designedScripts.ts +5 -5
  37. package/src/battle/script/monsterScripts.ts +5 -5
  38. package/src/battle/script/monsterScripts18xx.ts +4 -4
  39. package/src/battle/script/monsterScripts19xx.ts +5 -5
  40. package/src/battle/script/monsterScripts19xxPart2.ts +17 -17
  41. package/src/battle/script/sampleScripts.ts +1 -1
  42. package/src/battle/skillEngine.ts +4 -4
  43. package/src/battle/targeting.ts +2 -2
  44. package/src/battle/turnbar.ts +1 -1
  45. package/src/cocos-adapter/BattleFacade.ts +2 -2
  46. package/src/cocos-adapter/ReusableBattleFacade.ts +5 -5
  47. package/src/cocos-adapter/clientBattleResult.ts +49 -13
  48. package/src/index.ts +13 -13
@@ -1,8 +1,8 @@
1
- import { BattleEventBus } from "./eventBus";
2
- import { EffectSystem } from "./effectSystem";
3
- import { BattleLogger } from "./logger";
4
- import { ConfigSkillEngine, ScriptSkillEngine } from "./skillEngine";
5
- import type { BattleConfig, BattleInitUnit, ISkillScript, SkillDefinition, TriggerContext, UnitId, UnitModel } from "./types";
1
+ import { BattleEventBus } from "./eventBus.js";
2
+ import { EffectSystem } from "./effectSystem.js";
3
+ import { BattleLogger } from "./logger.js";
4
+ import { ConfigSkillEngine, ScriptSkillEngine } from "./skillEngine.js";
5
+ import type { BattleConfig, BattleInitUnit, ISkillScript, SkillDefinition, TriggerContext, UnitId, UnitModel } from "./types.js";
6
6
  export interface BattleResult {
7
7
  winner: 1 | 2 | 0;
8
8
  totalTurns: number;
@@ -1,10 +1,10 @@
1
- import { BattleEventBus } from "./eventBus";
2
- import { EffectSystem } from "./effectSystem";
3
- import { BattleLogger } from "./logger";
4
- import { SeedRng } from "./random";
5
- import { repression } from "./formula";
6
- import { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./skillEngine";
7
- import { TurnBarSystem } from "./turnbar";
1
+ import { BattleEventBus } from "./eventBus.js";
2
+ import { EffectSystem } from "./effectSystem.js";
3
+ import { BattleLogger } from "./logger.js";
4
+ import { SeedRng } from "./random.js";
5
+ import { repression } from "./formula.js";
6
+ import { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./skillEngine.js";
7
+ import { TurnBarSystem } from "./turnbar.js";
8
8
  export class BattleCore {
9
9
  initUnits;
10
10
  config;
@@ -1,4 +1,4 @@
1
- import type { BattleInitUnit, BuffConfig, SkillDefinition, SkillTemplate, UnitStats } from "../types";
1
+ import type { BattleInitUnit, BuffConfig, SkillDefinition, SkillTemplate, UnitStats } from "../types.js";
2
2
  type JsonHeroSkillBook = {
3
3
  heroId: string;
4
4
  heroName: string;
@@ -1,3 +1,3 @@
1
- import type { BuffConfig, SkillTemplate } from "../types";
1
+ import type { BuffConfig, SkillTemplate } from "../types.js";
2
2
  export declare const sampleBuffs: BuffConfig[];
3
3
  export declare const sampleTemplates: SkillTemplate[];
@@ -1,5 +1,5 @@
1
- import type { BuffConfig, BuffInstance, UnitId, UnitModel, UnitStats } from "./types";
2
- import { BattleEventBus } from "./eventBus";
1
+ import type { BuffConfig, BuffInstance, UnitId, UnitModel, UnitStats } from "./types.js";
2
+ import { BattleEventBus } from "./eventBus.js";
3
3
  export declare class EffectSystem {
4
4
  private readonly eventBus;
5
5
  private buffs;
@@ -1,4 +1,4 @@
1
- import type { BattleEvent, BattleEventName } from "./types";
1
+ import type { BattleEvent, BattleEventName } from "./types.js";
2
2
  type EventHandler<T> = (payload: T) => void;
3
3
  export declare class BattleEventBus {
4
4
  private handlers;
@@ -1,5 +1,5 @@
1
- import type { ElementType, SkillType, UnitModel } from "./types";
2
- import { SeedRng } from "./random";
1
+ import type { ElementType, SkillType, UnitModel } from "./types.js";
2
+ import { SeedRng } from "./random.js";
3
3
  export interface DamageResult {
4
4
  finalDam: number;
5
5
  baseDam: number;
@@ -1,4 +1,4 @@
1
- import type { BattleActionLog, BattleSnapshot, ElementType, UnitId, UnitModel, UnitStats } from "./types";
1
+ import type { BattleActionLog, BattleSnapshot, ElementType, UnitId, UnitModel, UnitStats } from "./types.js";
2
2
  export declare class BattleLogger {
3
3
  readonly actionLogs: BattleActionLog[];
4
4
  readonly snapshots: BattleSnapshot[];
@@ -1,2 +1,2 @@
1
- import type { ISkillScript } from "../types";
1
+ import type { ISkillScript } from "../types.js";
2
2
  export declare const designedScripts: Record<string, ISkillScript>;
@@ -1,7 +1,7 @@
1
- import { monsterScripts16xx } from "./monsterScripts";
2
- import { monsterScripts18xx } from "./monsterScripts18xx";
3
- import { monsterScripts19xxPart1 } from "./monsterScripts19xx";
4
- import { monsterScripts19xxPart2 } from "./monsterScripts19xxPart2";
1
+ import { monsterScripts16xx } from "./monsterScripts.js";
2
+ import { monsterScripts18xx } from "./monsterScripts18xx.js";
3
+ import { monsterScripts19xxPart1 } from "./monsterScripts19xx.js";
4
+ import { monsterScripts19xxPart2 } from "./monsterScripts19xxPart2.js";
5
5
  const ailishaOriginalPositions = new Map();
6
6
  function buildEmptyResult(ctx, targets = ctx.targets) {
7
7
  return {
@@ -1,2 +1,2 @@
1
- import type { ISkillScript } from "../types";
1
+ import type { ISkillScript } from "../types.js";
2
2
  export declare const monsterScripts16xx: Record<string, ISkillScript>;
@@ -1,2 +1,2 @@
1
- import type { ISkillScript } from "../types";
1
+ import type { ISkillScript } from "../types.js";
2
2
  export declare const monsterScripts18xx: Record<string, ISkillScript>;
@@ -1,2 +1,2 @@
1
- import type { ISkillScript } from "../types";
1
+ import type { ISkillScript } from "../types.js";
2
2
  export declare const monsterScripts19xxPart1: Record<string, ISkillScript>;
@@ -1,2 +1,2 @@
1
- import type { ISkillScript } from "../types";
1
+ import type { ISkillScript } from "../types.js";
2
2
  export declare const monsterScripts19xxPart2: Record<string, ISkillScript>;
@@ -1,4 +1,4 @@
1
- import type { ISkillScript } from "../types";
1
+ import type { ISkillScript } from "../types.js";
2
2
  /**
3
3
  * 导出所有示例脚本
4
4
  */
@@ -1,5 +1,5 @@
1
- import type { BattleScriptApi, ElementType, ISkillScript, ScriptRuntimeContext, SkillDefinition, SkillResult, SkillTemplate, TriggerContext, UnitModel } from "./types";
2
- import { SeedRng } from "./random";
1
+ import type { BattleScriptApi, ElementType, ISkillScript, ScriptRuntimeContext, SkillDefinition, SkillResult, SkillTemplate, TriggerContext, UnitModel } from "./types.js";
2
+ import { SeedRng } from "./random.js";
3
3
  interface ApplyDamageResult {
4
4
  damage: number;
5
5
  imbalanceDelta: number;
@@ -1,5 +1,5 @@
1
- import { calcDamage, calcEffectProbability, calcHealOrShield } from "./formula";
2
- import { selectTargets } from "./targeting";
1
+ import { calcDamage, calcEffectProbability, calcHealOrShield } from "./formula.js";
2
+ import { selectTargets } from "./targeting.js";
3
3
  export class ScriptSkillEngine {
4
4
  scripts = new Map(); // 存储已注册的技能脚本
5
5
  registerScript(scriptId, script) {
@@ -1,3 +1,3 @@
1
- import { SeedRng } from "./random";
2
- import type { TargetRule, UnitModel } from "./types";
1
+ import { SeedRng } from "./random.js";
2
+ import type { TargetRule, UnitModel } from "./types.js";
3
3
  export declare function selectTargets(rule: TargetRule, caster: UnitModel, allUnits: UnitModel[], rng: SeedRng): UnitModel[];
@@ -1,4 +1,4 @@
1
- import type { UnitModel, UnitId } from "./types";
1
+ import type { UnitModel, UnitId } from "./types.js";
2
2
  export declare class TurnBarSystem {
3
3
  private extraTurnQueue;
4
4
  grantExtraTurn(unitId: UnitId): void;
@@ -1,5 +1,5 @@
1
- import { BattleCore, type BattleResult } from "../battle/battleCore";
2
- import type { BattleConfig, BattleInitUnit } from "../battle/types";
1
+ import { BattleCore, type BattleResult } from "../battle/battleCore.js";
2
+ import type { BattleConfig, BattleInitUnit } from "../battle/types.js";
3
3
  export declare class BattleFacade {
4
4
  private core;
5
5
  start(units: BattleInitUnit[], config: BattleConfig, initialize?: (core: BattleCore) => void): BattleResult;
@@ -1,4 +1,4 @@
1
- import { BattleCore } from "../battle/battleCore";
1
+ import { BattleCore } from "../battle/battleCore.js";
2
2
  export class BattleFacade {
3
3
  core;
4
4
  start(units, config, initialize) {
@@ -1,6 +1,6 @@
1
- import type { BattleResult } from "../battle/battleCore";
2
- import { type BattleJsonConfigBundle, type HeroInput } from "../battle/config/jsonConfigLoader";
3
- import type { BattleConfig, BattleInitUnit, ISkillScript } from "../battle/types";
1
+ import type { BattleResult } from "../battle/battleCore.js";
2
+ import { type BattleJsonConfigBundle, type HeroInput } from "../battle/config/jsonConfigLoader.js";
3
+ import type { BattleConfig, BattleInitUnit, ISkillScript } from "../battle/types.js";
4
4
  export interface ReusableBattleFacadeOptions {
5
5
  bundle?: BattleJsonConfigBundle;
6
6
  scripts?: Record<string, ISkillScript>;
@@ -1,6 +1,6 @@
1
- import { buildBattleUnitsFromJsonBundle, loadBattleJsonConfigBundle, validateBattleJsonConfigBundle } from "../battle/config/jsonConfigLoader";
2
- import { designedScripts } from "../battle/script/designedScripts";
3
- import { BattleFacade } from "./BattleFacade";
1
+ import { buildBattleUnitsFromJsonBundle, loadBattleJsonConfigBundle, validateBattleJsonConfigBundle } from "../battle/config/jsonConfigLoader.js";
2
+ import { designedScripts } from "../battle/script/designedScripts.js";
3
+ import { BattleFacade } from "./BattleFacade.js";
4
4
  export class ReusableBattleFacade {
5
5
  facade = new BattleFacade();
6
6
  bundle;
@@ -1,5 +1,5 @@
1
- import type { BattleResult } from "../battle/battleCore";
2
- import type { BuffConfig } from "../battle/types";
1
+ import type { BattleResult } from "../battle/battleCore.js";
2
+ import type { BuffConfig } from "../battle/types.js";
3
3
  export interface ClientPositionChange {
4
4
  unitId: string;
5
5
  from: number;
@@ -166,7 +166,11 @@ function diffRuntimeStates(prevSnapshot, nextSnapshot, hits, dotDamages, imbalan
166
166
  const dotDamageMap = new Map();
167
167
  for (const dot of dotDamages ?? []) {
168
168
  const buffMap = dotDamageMap.get(dot.targetId) ?? new Map();
169
- buffMap.set(dot.buffId, { value: dot.value, element: dot.element });
169
+ const prev = buffMap.get(dot.buffId);
170
+ buffMap.set(dot.buffId, {
171
+ value: (prev?.value ?? 0) + dot.value,
172
+ element: dot.element ?? prev?.element
173
+ });
170
174
  dotDamageMap.set(dot.targetId, buffMap);
171
175
  }
172
176
  const imbalanceChangeMap = new Map();
@@ -197,18 +201,50 @@ function diffRuntimeStates(prevSnapshot, nextSnapshot, hits, dotDamages, imbalan
197
201
  let sourceActorId = null;
198
202
  let sourceSkillId = null;
199
203
  let source = "other_runtime_change";
200
- if (!impactedByHit && unitDotMap && relatedBuffIds.length > 0 && stat === "Hp" && delta < 0) {
201
- for (const buffId of relatedBuffIds) {
202
- if (unitDotMap.has(buffId)) {
203
- const dotInfo = unitDotMap.get(buffId);
204
- if (dotInfo.value === -delta) {
205
- sourceBuffId = buffId;
206
- element = dotInfo.element ?? null;
207
- source = "buff_settlement";
208
- break;
209
- }
204
+ // HP 在同一快照内可能同时包含 DOT 结算与技能命中,需拆分避免净值混淆。
205
+ if (stat === "Hp" && delta < 0 && unitDotMap && unitDotMap.size > 0) {
206
+ let remainingLoss = -delta;
207
+ const orderedBuffIds = [
208
+ ...relatedBuffIds.filter((buffId) => unitDotMap.has(buffId)),
209
+ ...[...unitDotMap.keys()].filter((buffId) => !relatedBuffIds.includes(buffId))
210
+ ];
211
+ for (const buffId of orderedBuffIds) {
212
+ if (remainingLoss <= 0) {
213
+ break;
214
+ }
215
+ const dotInfo = unitDotMap.get(buffId);
216
+ if (!dotInfo) {
217
+ continue;
218
+ }
219
+ const consumed = Math.min(remainingLoss, Math.max(0, dotInfo.value));
220
+ if (consumed <= 0) {
221
+ continue;
210
222
  }
223
+ deltas.push({
224
+ unitId: nextUnit.id,
225
+ stat: "Hp",
226
+ delta: -consumed,
227
+ source: "buff_settlement",
228
+ sourceBuffId: buffId,
229
+ element: dotInfo.element ?? null,
230
+ sourceActorId: null,
231
+ sourceSkillId: null
232
+ });
233
+ remainingLoss -= consumed;
211
234
  }
235
+ if (remainingLoss > 0) {
236
+ deltas.push({
237
+ unitId: nextUnit.id,
238
+ stat: "Hp",
239
+ delta: -remainingLoss,
240
+ source: impactedByHit ? "other_runtime_change" : "other_runtime_change",
241
+ sourceBuffId: null,
242
+ element: null,
243
+ sourceActorId: null,
244
+ sourceSkillId: null
245
+ });
246
+ }
247
+ continue;
212
248
  }
213
249
  if (stat === "Imbalance") {
214
250
  const exactMatchedChange = (imbalanceChangeMap.get(nextUnit.id) ?? []).find((change) => change.delta === delta);
package/dist/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
- export { BattleCore } from "./battle/battleCore";
2
- export { BattleFacade } from "./cocos-adapter/BattleFacade";
3
- export { ReusableBattleFacade } from "./cocos-adapter/ReusableBattleFacade";
4
- export { buildClientBattleResult } from "./cocos-adapter/clientBattleResult";
5
- export type { BuildClientBattleResultInput, ClientAction, ClientActionStateRef, ClientBattleResult, ClientImbalanceChange, ClientPanelBuffDelta, ClientPositionChange, ClientStateSettlementDelta, ClientStatChange, ClientTurnSettlement } from "./cocos-adapter/clientBattleResult";
6
- export { BattleEventBus } from "./battle/eventBus";
7
- export { EffectSystem } from "./battle/effectSystem";
8
- export { calcDamage, calcEffectProbability, calcHealOrShield } from "./battle/formula";
9
- export { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./battle/skillEngine";
10
- export { designedScripts } from "./battle/script/designedScripts";
11
- export { loadBattleJsonConfigBundle, validateBattleJsonConfigBundle, buildBattleUnitsFromJsonBundle } from "./battle/config/jsonConfigLoader";
12
- export type { BattleJsonConfigBundle, HeroInput } from "./battle/config/jsonConfigLoader";
13
- export type { BattleScriptApi, BattleConfig, BattleInitUnit, BuffConfig, ElementType, ISkillScript, SkillDefinition, SkillExecMode, SkillTemplate, SkillType, TargetRule, UnitModel, UnitStats } from "./battle/types";
1
+ export { BattleCore } from "./battle/battleCore.js";
2
+ export { BattleFacade } from "./cocos-adapter/BattleFacade.js";
3
+ export { ReusableBattleFacade } from "./cocos-adapter/ReusableBattleFacade.js";
4
+ export { buildClientBattleResult } from "./cocos-adapter/clientBattleResult.js";
5
+ export type { BuildClientBattleResultInput, ClientAction, ClientActionStateRef, ClientBattleResult, ClientImbalanceChange, ClientPanelBuffDelta, ClientPositionChange, ClientStateSettlementDelta, ClientStatChange, ClientTurnSettlement } from "./cocos-adapter/clientBattleResult.js";
6
+ export { BattleEventBus } from "./battle/eventBus.js";
7
+ export { EffectSystem } from "./battle/effectSystem.js";
8
+ export { calcDamage, calcEffectProbability, calcHealOrShield } from "./battle/formula.js";
9
+ export { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./battle/skillEngine.js";
10
+ export { designedScripts } from "./battle/script/designedScripts.js";
11
+ export { loadBattleJsonConfigBundle, validateBattleJsonConfigBundle, buildBattleUnitsFromJsonBundle } from "./battle/config/jsonConfigLoader.js";
12
+ export type { BattleJsonConfigBundle, HeroInput } from "./battle/config/jsonConfigLoader.js";
13
+ export type { BattleScriptApi, BattleConfig, BattleInitUnit, BuffConfig, ElementType, ISkillScript, SkillDefinition, SkillExecMode, SkillTemplate, SkillType, TargetRule, UnitModel, UnitStats } from "./battle/types.js";
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- export { BattleCore } from "./battle/battleCore";
2
- export { BattleFacade } from "./cocos-adapter/BattleFacade";
3
- export { ReusableBattleFacade } from "./cocos-adapter/ReusableBattleFacade";
4
- export { buildClientBattleResult } from "./cocos-adapter/clientBattleResult";
5
- export { BattleEventBus } from "./battle/eventBus";
6
- export { EffectSystem } from "./battle/effectSystem";
7
- export { calcDamage, calcEffectProbability, calcHealOrShield } from "./battle/formula";
8
- export { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./battle/skillEngine";
9
- export { designedScripts } from "./battle/script/designedScripts";
10
- export { loadBattleJsonConfigBundle, validateBattleJsonConfigBundle, buildBattleUnitsFromJsonBundle } from "./battle/config/jsonConfigLoader";
1
+ export { BattleCore } from "./battle/battleCore.js";
2
+ export { BattleFacade } from "./cocos-adapter/BattleFacade.js";
3
+ export { ReusableBattleFacade } from "./cocos-adapter/ReusableBattleFacade.js";
4
+ export { buildClientBattleResult } from "./cocos-adapter/clientBattleResult.js";
5
+ export { BattleEventBus } from "./battle/eventBus.js";
6
+ export { EffectSystem } from "./battle/effectSystem.js";
7
+ export { calcDamage, calcEffectProbability, calcHealOrShield } from "./battle/formula.js";
8
+ export { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./battle/skillEngine.js";
9
+ export { designedScripts } from "./battle/script/designedScripts.js";
10
+ export { loadBattleJsonConfigBundle, validateBattleJsonConfigBundle, buildBattleUnitsFromJsonBundle } from "./battle/config/jsonConfigLoader.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hrbattle",
3
- "version": "1.0.7",
3
+ "version": "1.0.8",
4
4
  "description": "一个基于回合制战斗引擎,支持技能脚本、buff系统和效果解析。",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,11 +1,11 @@
1
- import { BattleEventBus } from "./eventBus";
2
- import { EffectSystem } from "./effectSystem";
3
- import { BattleLogger } from "./logger";
4
- import { SeedRng } from "./random";
5
- import { repression } from "./formula";
6
- import { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./skillEngine";
7
- import { TurnBarSystem } from "./turnbar";
8
- import type { BattleConfig, BattleInitUnit, ElementType, ISkillScript, SkillDefinition, SkillResult, TriggerContext, UnitId, UnitModel } from "./types";
1
+ import { BattleEventBus } from "./eventBus.js";
2
+ import { EffectSystem } from "./effectSystem.js";
3
+ import { BattleLogger } from "./logger.js";
4
+ import { SeedRng } from "./random.js";
5
+ import { repression } from "./formula.js";
6
+ import { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./skillEngine.js";
7
+ import { TurnBarSystem } from "./turnbar.js";
8
+ import type { BattleConfig, BattleInitUnit, ElementType, ISkillScript, SkillDefinition, SkillResult, TriggerContext, UnitId, UnitModel } from "./types.js";
9
9
 
10
10
  interface SkillRuntimeSlot {
11
11
  definition: SkillDefinition;
@@ -780,8 +780,8 @@ export class BattleCore {
780
780
  } else if (rep === -1) {
781
781
  stagger = Math.floor(stagger * 7000 / 10000);
782
782
  }
783
- const takenRate = 10000 + this.effectSystem.getImbalanceTakenRate(target.id);
784
- stagger = Math.max(0, Math.floor(stagger * takenRate / 10000));
783
+ const takenRate = 10000 + this.effectSystem.getImbalanceTakenRate(target.id);
784
+ stagger = Math.max(0, Math.floor(stagger * takenRate / 10000));
785
785
  const max = this.computeImbalanceMax(target);
786
786
  const from = target.runtime.Imbalance;
787
787
  target.runtime.Imbalance = Math.min(max, from + stagger);
@@ -1,4 +1,4 @@
1
- import type { BattleInitUnit, BuffConfig, SkillDefinition, SkillTemplate, UnitStats } from "../types";
1
+ import type { BattleInitUnit, BuffConfig, SkillDefinition, SkillTemplate, UnitStats } from "../types.js";
2
2
 
3
3
  import designedBuffs from "./json/designed-buffs.json";
4
4
  import designedHeroSkillBooks from "./json/designed-hero-skill-books.json";
@@ -1,4 +1,4 @@
1
- import type { BuffConfig, SkillTemplate } from "../types";
1
+ import type { BuffConfig, SkillTemplate } from "../types.js";
2
2
 
3
3
  export const sampleBuffs: BuffConfig[] = [
4
4
  {
@@ -1,5 +1,5 @@
1
- import type { BuffConfig, BuffInstance, UnitId, UnitModel, UnitStats } from "./types";
2
- import { BattleEventBus } from "./eventBus";
1
+ import type { BuffConfig, BuffInstance, UnitId, UnitModel, UnitStats } from "./types.js";
2
+ import { BattleEventBus } from "./eventBus.js";
3
3
 
4
4
  const FOREVER = 999999999;
5
5
  const SKIP_ROUND_END = new Set([-999, -99999999]);
@@ -1,4 +1,4 @@
1
- import type { BattleEvent, BattleEventName } from "./types";
1
+ import type { BattleEvent, BattleEventName } from "./types.js";
2
2
 
3
3
  type EventHandler<T> = (payload: T) => void;
4
4
 
@@ -1,5 +1,5 @@
1
- import type { ElementType, SkillType, UnitModel } from "./types";
2
- import { SeedRng } from "./random";
1
+ import type { ElementType, SkillType, UnitModel } from "./types.js";
2
+ import { SeedRng } from "./random.js";
3
3
 
4
4
  const DEF_PARTICLE = 300;
5
5
 
@@ -1,4 +1,4 @@
1
- import type { BattleActionLog, BattleSnapshot, ElementType, UnitId, UnitModel, UnitStats } from "./types";
1
+ import type { BattleActionLog, BattleSnapshot, ElementType, UnitId, UnitModel, UnitStats } from "./types.js";
2
2
 
3
3
  export class BattleLogger {
4
4
  readonly actionLogs: BattleActionLog[] = [];
@@ -1,8 +1,8 @@
1
- import type { ISkillScript, ScriptRuntimeContext, SkillHitDetail, SkillResult } from "../types";
2
- import { monsterScripts16xx } from "./monsterScripts";
3
- import { monsterScripts18xx } from "./monsterScripts18xx";
4
- import { monsterScripts19xxPart1 } from "./monsterScripts19xx";
5
- import { monsterScripts19xxPart2 } from "./monsterScripts19xxPart2";
1
+ import type { ISkillScript, ScriptRuntimeContext, SkillHitDetail, SkillResult } from "../types.js";
2
+ import { monsterScripts16xx } from "./monsterScripts.js";
3
+ import { monsterScripts18xx } from "./monsterScripts18xx.js";
4
+ import { monsterScripts19xxPart1 } from "./monsterScripts19xx.js";
5
+ import { monsterScripts19xxPart2 } from "./monsterScripts19xxPart2.js";
6
6
 
7
7
  const ailishaOriginalPositions = new Map<string, number>();
8
8
 
@@ -1,4 +1,4 @@
1
- import type { BattleInitUnit, ISkillScript, ScriptRuntimeContext, SkillHitDetail, SkillResult } from "../types";
1
+ import type { BattleInitUnit, ISkillScript, ScriptRuntimeContext, SkillHitDetail, SkillResult } from "../types.js";
2
2
 
3
3
  function buildEmptyResult(ctx: ScriptRuntimeContext, targets = ctx.targets): SkillResult {
4
4
  return {
@@ -17,7 +17,7 @@ function buildDamageHit(ctx: ScriptRuntimeContext, targetId: string, finalValue:
17
17
  targetId,
18
18
  effectType: "damage",
19
19
  element: ctx.api.getUnit(ctx.caster)?.element ?? "fire",
20
- skillType: ctx.skillType,
20
+ skillType: ctx.skillType,
21
21
  isCritical: false,
22
22
  baseValue: finalValue,
23
23
  criticalValue: finalValue,
@@ -34,7 +34,7 @@ function buildBuffHit(ctx: ScriptRuntimeContext, targetId: string, buffId: strin
34
34
  targetId,
35
35
  effectType: "buff",
36
36
  element: ctx.api.getUnit(ctx.caster)?.element ?? "fire",
37
- skillType: ctx.skillType,
37
+ skillType: ctx.skillType,
38
38
  isCritical: false,
39
39
  baseValue: 0,
40
40
  criticalValue: 0,
@@ -50,7 +50,7 @@ function buildHealHit(ctx: ScriptRuntimeContext, targetId: string, finalValue: n
50
50
  targetId,
51
51
  effectType: "heal",
52
52
  element: ctx.api.getUnit(ctx.caster)?.element ?? "fire",
53
- skillType: ctx.skillType,
53
+ skillType: ctx.skillType,
54
54
  isCritical: false,
55
55
  baseValue: finalValue,
56
56
  criticalValue: finalValue,
@@ -64,7 +64,7 @@ function buildShieldHit(ctx: ScriptRuntimeContext, targetId: string, finalValue:
64
64
  targetId,
65
65
  effectType: "shield",
66
66
  element: ctx.api.getUnit(ctx.caster)?.element ?? "fire",
67
- skillType: ctx.skillType,
67
+ skillType: ctx.skillType,
68
68
  isCritical: false,
69
69
  baseValue: finalValue,
70
70
  criticalValue: finalValue,
@@ -1,16 +1,16 @@
1
- import type { ISkillScript, ScriptRuntimeContext, SkillHitDetail, SkillResult } from "../types";
1
+ import type { ISkillScript, ScriptRuntimeContext, SkillHitDetail, SkillResult } from "../types.js";
2
2
 
3
3
  function buildEmptyResult(ctx: ScriptRuntimeContext, targets = ctx.targets): SkillResult {
4
4
  return { skillId: ctx.skillId, casterId: ctx.caster, targets, totalDamage: 0, totalHeal: 0, totalShield: 0, triggeredBy: ctx.trigger };
5
5
  }
6
6
  function buildDamageHit(ctx: ScriptRuntimeContext, targetId: string, v: number, imbalanceDelta?: number, triggeredImbalanceBreak?: boolean): SkillHitDetail {
7
- return { targetId, effectType: "damage", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0, imbalanceDelta, triggeredImbalanceBreak, damageBreakdown: { preMitigation: v, postMitigation: v, shieldAbsorbed: 0, hpDamage: v } };
7
+ return { targetId, effectType: "damage", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0, imbalanceDelta, triggeredImbalanceBreak, damageBreakdown: { preMitigation: v, postMitigation: v, shieldAbsorbed: 0, hpDamage: v } };
8
8
  }
9
9
  function buildBuffHit(ctx: ScriptRuntimeContext, targetId: string, buffId: string, resisted = false): SkillHitDetail {
10
- return { targetId, effectType: "buff", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: 0, criticalValue: 0, finalValue: 0, isRepression: 0, appliedBuffId: resisted ? undefined : buffId, resisted };
10
+ return { targetId, effectType: "buff", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: 0, criticalValue: 0, finalValue: 0, isRepression: 0, appliedBuffId: resisted ? undefined : buffId, resisted };
11
11
  }
12
12
  function buildHealHit(ctx: ScriptRuntimeContext, targetId: string, v: number): SkillHitDetail {
13
- return { targetId, effectType: "heal", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0 };
13
+ return { targetId, effectType: "heal", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0 };
14
14
  }
15
15
 
16
16
  // ============================================================
@@ -1,19 +1,19 @@
1
- import type { BattleInitUnit, ISkillScript, ScriptRuntimeContext, SkillHitDetail, SkillResult } from "../types";
1
+ import type { BattleInitUnit, ISkillScript, ScriptRuntimeContext, SkillHitDetail, SkillResult } from "../types.js";
2
2
 
3
3
  function empty(ctx: ScriptRuntimeContext, targets = ctx.targets): SkillResult {
4
4
  return { skillId: ctx.skillId, casterId: ctx.caster, targets, totalDamage: 0, totalHeal: 0, totalShield: 0, triggeredBy: ctx.trigger };
5
5
  }
6
6
  function dmgHit(ctx: ScriptRuntimeContext, tid: string, v: number, ib?: number, tib?: boolean): SkillHitDetail {
7
- return { targetId: tid, effectType: "damage", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0, imbalanceDelta: ib, triggeredImbalanceBreak: tib, damageBreakdown: { preMitigation: v, postMitigation: v, shieldAbsorbed: 0, hpDamage: v } };
7
+ return { targetId: tid, effectType: "damage", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0, imbalanceDelta: ib, triggeredImbalanceBreak: tib, damageBreakdown: { preMitigation: v, postMitigation: v, shieldAbsorbed: 0, hpDamage: v } };
8
8
  }
9
9
  function buffHit(ctx: ScriptRuntimeContext, tid: string, bid: string, r = false): SkillHitDetail {
10
- return { targetId: tid, effectType: "buff", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: 0, criticalValue: 0, finalValue: 0, isRepression: 0, appliedBuffId: r ? undefined : bid, resisted: r };
10
+ return { targetId: tid, effectType: "buff", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: 0, criticalValue: 0, finalValue: 0, isRepression: 0, appliedBuffId: r ? undefined : bid, resisted: r };
11
11
  }
12
12
  function healHit(ctx: ScriptRuntimeContext, tid: string, v: number): SkillHitDetail {
13
- return { targetId: tid, effectType: "heal", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0 };
13
+ return { targetId: tid, effectType: "heal", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0 };
14
14
  }
15
15
  function shieldHit(ctx: ScriptRuntimeContext, tid: string, v: number): SkillHitDetail {
16
- return { targetId: tid, effectType: "shield", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0 };
16
+ return { targetId: tid, effectType: "shield", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0 };
17
17
  }
18
18
 
19
19
  // 19011 骨墩墩 普攻:80%火属性伤害+赋予自身【反击】(反击触发的普攻不赋予反击)
@@ -1,19 +1,19 @@
1
- import type { BattleInitUnit, ISkillScript, ScriptRuntimeContext, SkillHitDetail, SkillResult } from "../types";
1
+ import type { BattleInitUnit, ISkillScript, ScriptRuntimeContext, SkillHitDetail, SkillResult } from "../types.js";
2
2
 
3
3
  function empty(ctx: ScriptRuntimeContext, targets = ctx.targets): SkillResult {
4
4
  return { skillId: ctx.skillId, casterId: ctx.caster, targets, totalDamage: 0, totalHeal: 0, totalShield: 0, triggeredBy: ctx.trigger };
5
5
  }
6
6
  function dmgHit(ctx: ScriptRuntimeContext, tid: string, v: number, ib?: number, tib?: boolean): SkillHitDetail {
7
- return { targetId: tid, effectType: "damage", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0, imbalanceDelta: ib, triggeredImbalanceBreak: tib, damageBreakdown: { preMitigation: v, postMitigation: v, shieldAbsorbed: 0, hpDamage: v } };
7
+ return { targetId: tid, effectType: "damage", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0, imbalanceDelta: ib, triggeredImbalanceBreak: tib, damageBreakdown: { preMitigation: v, postMitigation: v, shieldAbsorbed: 0, hpDamage: v } };
8
8
  }
9
9
  function buffHit(ctx: ScriptRuntimeContext, tid: string, bid: string, r = false): SkillHitDetail {
10
- return { targetId: tid, effectType: "buff", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: 0, criticalValue: 0, finalValue: 0, isRepression: 0, appliedBuffId: r ? undefined : bid, resisted: r };
10
+ return { targetId: tid, effectType: "buff", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: 0, criticalValue: 0, finalValue: 0, isRepression: 0, appliedBuffId: r ? undefined : bid, resisted: r };
11
11
  }
12
12
  function healHit(ctx: ScriptRuntimeContext, tid: string, v: number): SkillHitDetail {
13
- return { targetId: tid, effectType: "heal", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0 };
13
+ return { targetId: tid, effectType: "heal", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0 };
14
14
  }
15
15
  function shieldHit(ctx: ScriptRuntimeContext, tid: string, v: number): SkillHitDetail {
16
- return { targetId: tid, effectType: "shield", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0 };
16
+ return { targetId: tid, effectType: "shield", element: ctx.api.getUnit(ctx.caster)?.element ?? "fire", skillType: ctx.skillType, isCritical: false, baseValue: v, criticalValue: v, finalValue: v, isRepression: 0 };
17
17
  }
18
18
 
19
19
  // 19042 大风 技能1:对全体敌人造成50%风属性伤害,并消除敌人20%行动值持续2回合
@@ -82,12 +82,12 @@ const m19052Script: ISkillScript = {
82
82
  // 驱散1个负面效果(简化:移除第一个debuff)
83
83
  const debuffs = ctx.api.getBuffs(ally.id).filter(b => {
84
84
  const c = b.config;
85
- return (c.defDelta && c.defDelta < 0) || (c.defenseRateDelta && c.defenseRateDelta < 0) ||
86
- (c.speedRateDelta && c.speedRateDelta < 0) ||
85
+ return (c.defDelta && c.defDelta < 0) || (c.defenseRateDelta && c.defenseRateDelta < 0) ||
86
+ (c.speedRateDelta && c.speedRateDelta < 0) ||
87
87
  (c.vulnerabilityDelta && c.vulnerabilityDelta > 0) || c.stun || c.dotRatio ||
88
- (c.effectResDelta && c.effectResDelta < 0) ||
89
- (c.allElementResistDelta && c.allElementResistDelta < 0) ||
90
- (c.imbalanceTakenRateDelta && c.imbalanceTakenRateDelta > 0);
88
+ (c.effectResDelta && c.effectResDelta < 0) ||
89
+ (c.allElementResistDelta && c.allElementResistDelta < 0) ||
90
+ (c.imbalanceTakenRateDelta && c.imbalanceTakenRateDelta > 0);
91
91
  });
92
92
  if (debuffs.length > 0) {
93
93
  ctx.api.removeBuff(ally.id, debuffs[0].id);
@@ -325,12 +325,12 @@ const m19074Script: ISkillScript = {
325
325
  const newBuff = buffs.find(b => b.id === data.buffId);
326
326
  if (newBuff) {
327
327
  const c = newBuff.config;
328
- const isDebuff = (c.defDelta && c.defDelta < 0) || (c.defenseRateDelta && c.defenseRateDelta < 0) ||
329
- (c.speedRateDelta && c.speedRateDelta < 0) ||
328
+ const isDebuff = (c.defDelta && c.defDelta < 0) || (c.defenseRateDelta && c.defenseRateDelta < 0) ||
329
+ (c.speedRateDelta && c.speedRateDelta < 0) ||
330
330
  (c.vulnerabilityDelta && c.vulnerabilityDelta > 0) || c.stun || c.dotRatio ||
331
- (c.effectResDelta && c.effectResDelta < 0) ||
332
- (c.allElementResistDelta && c.allElementResistDelta < 0) ||
333
- (c.imbalanceTakenRateDelta && c.imbalanceTakenRateDelta > 0);
331
+ (c.effectResDelta && c.effectResDelta < 0) ||
332
+ (c.allElementResistDelta && c.allElementResistDelta < 0) ||
333
+ (c.imbalanceTakenRateDelta && c.imbalanceTakenRateDelta > 0);
334
334
  if (isDebuff) {
335
335
  ctx.api.removeBuff(ctx.caster, data.buffId);
336
336
  }
@@ -361,8 +361,8 @@ const m19075Script: ISkillScript = {
361
361
  (c.defenseRateDelta && c.defenseRateDelta > 0) ||
362
362
  (c.speedRateDelta && c.speedRateDelta > 0) ||
363
363
  (c.damageRateDelta && c.damageRateDelta > 0) ||
364
- (c.critRateDelta && c.critRateDelta > 0) ||
365
- (c.assistRateDelta && c.assistRateDelta > 0);
364
+ (c.critRateDelta && c.critRateDelta > 0) ||
365
+ (c.assistRateDelta && c.assistRateDelta > 0);
366
366
  if (isBeneficial) {
367
367
  ctx.api.removeBuff(ctx.caster, b.id);
368
368
  }
@@ -1,4 +1,4 @@
1
- import type { ISkillScript, ScriptRuntimeContext, SkillResult } from "../types";
1
+ import type { ISkillScript, ScriptRuntimeContext, SkillResult } from "../types.js";
2
2
 
3
3
  /**
4
4
  * 示例:支持技能升级的手写技能脚本
@@ -1,5 +1,5 @@
1
- import { calcDamage, calcEffectProbability, calcHealOrShield } from "./formula";
2
- import { selectTargets } from "./targeting";
1
+ import { calcDamage, calcEffectProbability, calcHealOrShield } from "./formula.js";
2
+ import { selectTargets } from "./targeting.js";
3
3
  import type {
4
4
  BattleScriptApi,
5
5
  ElementType,
@@ -12,8 +12,8 @@ import type {
12
12
  SkillTemplate,
13
13
  TriggerContext,
14
14
  UnitModel
15
- } from "./types";
16
- import { SeedRng } from "./random";
15
+ } from "./types.js";
16
+ import { SeedRng } from "./random.js";
17
17
 
18
18
  interface ApplyDamageResult {
19
19
  damage: number;
@@ -1,5 +1,5 @@
1
- import { SeedRng } from "./random";
2
- import type { TargetRule, UnitModel } from "./types";
1
+ import { SeedRng } from "./random.js";
2
+ import type { TargetRule, UnitModel } from "./types.js";
3
3
 
4
4
  function living(units: UnitModel[]): UnitModel[] {
5
5
  return units.filter((u) => u.runtime.alive);
@@ -1,4 +1,4 @@
1
- import type { UnitModel, UnitId } from "./types";
1
+ import type { UnitModel, UnitId } from "./types.js";
2
2
 
3
3
  const ACTION_THRESHOLD = 1000000;
4
4
 
@@ -1,5 +1,5 @@
1
- import { BattleCore, type BattleResult } from "../battle/battleCore";
2
- import type { BattleConfig, BattleInitUnit } from "../battle/types";
1
+ import { BattleCore, type BattleResult } from "../battle/battleCore.js";
2
+ import type { BattleConfig, BattleInitUnit } from "../battle/types.js";
3
3
 
4
4
  export class BattleFacade {
5
5
  private core: BattleCore | undefined;
@@ -1,14 +1,14 @@
1
- import type { BattleResult } from "../battle/battleCore";
1
+ import type { BattleResult } from "../battle/battleCore.js";
2
2
  import {
3
3
  buildBattleUnitsFromJsonBundle,
4
4
  loadBattleJsonConfigBundle,
5
5
  validateBattleJsonConfigBundle,
6
6
  type BattleJsonConfigBundle,
7
7
  type HeroInput
8
- } from "../battle/config/jsonConfigLoader";
9
- import { designedScripts } from "../battle/script/designedScripts";
10
- import type { BattleConfig, BattleInitUnit, ISkillScript } from "../battle/types";
11
- import { BattleFacade } from "./BattleFacade";
8
+ } from "../battle/config/jsonConfigLoader.js";
9
+ import { designedScripts } from "../battle/script/designedScripts.js";
10
+ import type { BattleConfig, BattleInitUnit, ISkillScript } from "../battle/types.js";
11
+ import { BattleFacade } from "./BattleFacade.js";
12
12
 
13
13
  export interface ReusableBattleFacadeOptions {
14
14
  bundle?: BattleJsonConfigBundle;
@@ -1,5 +1,5 @@
1
- import type { BattleResult } from "../battle/battleCore";
2
- import type { BuffConfig } from "../battle/types";
1
+ import type { BattleResult } from "../battle/battleCore.js";
2
+ import type { BuffConfig } from "../battle/types.js";
3
3
 
4
4
  export interface ClientPositionChange {
5
5
  unitId: string;
@@ -328,7 +328,11 @@ function diffRuntimeStates(
328
328
  const dotDamageMap = new Map<string, Map<string, { value: number; element?: string }>>();
329
329
  for (const dot of dotDamages ?? []) {
330
330
  const buffMap = dotDamageMap.get(dot.targetId) ?? new Map<string, { value: number; element?: string }>();
331
- buffMap.set(dot.buffId, { value: dot.value, element: dot.element });
331
+ const prev = buffMap.get(dot.buffId);
332
+ buffMap.set(dot.buffId, {
333
+ value: (prev?.value ?? 0) + dot.value,
334
+ element: dot.element ?? prev?.element
335
+ });
332
336
  dotDamageMap.set(dot.targetId, buffMap);
333
337
  }
334
338
  const imbalanceChangeMap = new Map<string, Array<NonNullable<typeof imbalanceChanges>[number]>>();
@@ -359,18 +363,50 @@ function diffRuntimeStates(
359
363
  let sourceActorId: string | null = null;
360
364
  let sourceSkillId: string | null = null;
361
365
  let source: ClientStateSettlementDelta["source"] = "other_runtime_change";
362
- if (!impactedByHit && unitDotMap && relatedBuffIds.length > 0 && stat === "Hp" && delta < 0) {
363
- for (const buffId of relatedBuffIds) {
364
- if (unitDotMap.has(buffId)) {
365
- const dotInfo = unitDotMap.get(buffId)!;
366
- if (dotInfo.value === -delta) {
367
- sourceBuffId = buffId;
368
- element = dotInfo.element ?? null;
369
- source = "buff_settlement";
370
- break;
371
- }
366
+ // HP 在同一快照内可能同时包含 DOT 结算与技能命中,需拆分避免净值混淆。
367
+ if (stat === "Hp" && delta < 0 && unitDotMap && unitDotMap.size > 0) {
368
+ let remainingLoss = -delta;
369
+ const orderedBuffIds = [
370
+ ...relatedBuffIds.filter((buffId) => unitDotMap.has(buffId)),
371
+ ...[...unitDotMap.keys()].filter((buffId) => !relatedBuffIds.includes(buffId))
372
+ ];
373
+ for (const buffId of orderedBuffIds) {
374
+ if (remainingLoss <= 0) {
375
+ break;
376
+ }
377
+ const dotInfo = unitDotMap.get(buffId);
378
+ if (!dotInfo) {
379
+ continue;
380
+ }
381
+ const consumed = Math.min(remainingLoss, Math.max(0, dotInfo.value));
382
+ if (consumed <= 0) {
383
+ continue;
372
384
  }
385
+ deltas.push({
386
+ unitId: nextUnit.id,
387
+ stat: "Hp",
388
+ delta: -consumed,
389
+ source: "buff_settlement",
390
+ sourceBuffId: buffId,
391
+ element: dotInfo.element ?? null,
392
+ sourceActorId: null,
393
+ sourceSkillId: null
394
+ });
395
+ remainingLoss -= consumed;
373
396
  }
397
+ if (remainingLoss > 0) {
398
+ deltas.push({
399
+ unitId: nextUnit.id,
400
+ stat: "Hp",
401
+ delta: -remainingLoss,
402
+ source: impactedByHit ? "other_runtime_change" : "other_runtime_change",
403
+ sourceBuffId: null,
404
+ element: null,
405
+ sourceActorId: null,
406
+ sourceSkillId: null
407
+ });
408
+ }
409
+ continue;
374
410
  }
375
411
  if (stat === "Imbalance") {
376
412
  const exactMatchedChange = (imbalanceChangeMap.get(nextUnit.id) ?? []).find((change) => change.delta === delta);
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
- export { BattleCore } from "./battle/battleCore";
2
- export { BattleFacade } from "./cocos-adapter/BattleFacade";
3
- export { ReusableBattleFacade } from "./cocos-adapter/ReusableBattleFacade";
4
- export { buildClientBattleResult } from "./cocos-adapter/clientBattleResult";
1
+ export { BattleCore } from "./battle/battleCore.js";
2
+ export { BattleFacade } from "./cocos-adapter/BattleFacade.js";
3
+ export { ReusableBattleFacade } from "./cocos-adapter/ReusableBattleFacade.js";
4
+ export { buildClientBattleResult } from "./cocos-adapter/clientBattleResult.js";
5
5
  export type {
6
6
  BuildClientBattleResultInput,
7
7
  ClientAction,
@@ -13,21 +13,21 @@ export type {
13
13
  ClientStateSettlementDelta,
14
14
  ClientStatChange,
15
15
  ClientTurnSettlement
16
- } from "./cocos-adapter/clientBattleResult";
17
- export { BattleEventBus } from "./battle/eventBus";
18
- export { EffectSystem } from "./battle/effectSystem";
19
- export { calcDamage, calcEffectProbability, calcHealOrShield } from "./battle/formula";
20
- export { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./battle/skillEngine";
21
- export { designedScripts } from "./battle/script/designedScripts";
16
+ } from "./cocos-adapter/clientBattleResult.js";
17
+ export { BattleEventBus } from "./battle/eventBus.js";
18
+ export { EffectSystem } from "./battle/effectSystem.js";
19
+ export { calcDamage, calcEffectProbability, calcHealOrShield } from "./battle/formula.js";
20
+ export { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./battle/skillEngine.js";
21
+ export { designedScripts } from "./battle/script/designedScripts.js";
22
22
  export {
23
23
  loadBattleJsonConfigBundle,
24
24
  validateBattleJsonConfigBundle,
25
25
  buildBattleUnitsFromJsonBundle
26
- } from "./battle/config/jsonConfigLoader";
26
+ } from "./battle/config/jsonConfigLoader.js";
27
27
  export type {
28
28
  BattleJsonConfigBundle,
29
29
  HeroInput
30
- } from "./battle/config/jsonConfigLoader";
30
+ } from "./battle/config/jsonConfigLoader.js";
31
31
  export type {
32
32
  BattleScriptApi,
33
33
  BattleConfig,
@@ -42,4 +42,4 @@ export type {
42
42
  TargetRule,
43
43
  UnitModel,
44
44
  UnitStats
45
- } from "./battle/types";
45
+ } from "./battle/types.js";