hrbattle 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/dist/battle/battleCore.d.ts +83 -0
- package/dist/battle/battleCore.js +779 -0
- package/dist/battle/config/json/designed-buffs.json +608 -0
- package/dist/battle/config/json/designed-hero-skill-books.json +146 -0
- package/dist/battle/config/json/designed-monster-skill-books.json +308 -0
- package/dist/battle/config/json/designed-roster.json +438 -0
- package/dist/battle/config/json/designed-skill-templates.json +81 -0
- package/dist/battle/config/jsonConfigLoader.d.ts +26 -0
- package/dist/battle/config/jsonConfigLoader.js +77 -0
- package/dist/battle/effectSystem.d.ts +26 -0
- package/dist/battle/effectSystem.js +175 -0
- package/dist/battle/eventBus.d.ts +8 -0
- package/dist/battle/eventBus.js +20 -0
- package/dist/battle/formula.d.ts +19 -0
- package/dist/battle/formula.js +92 -0
- package/dist/battle/logger.d.ts +28 -0
- package/dist/battle/logger.js +66 -0
- package/dist/battle/random.d.ts +6 -0
- package/dist/battle/random.js +16 -0
- package/dist/battle/script/designedScripts.d.ts +2 -0
- package/dist/battle/script/designedScripts.js +1013 -0
- package/dist/battle/script/monsterScripts.d.ts +2 -0
- package/dist/battle/script/monsterScripts.js +277 -0
- package/dist/battle/script/monsterScripts18xx.d.ts +2 -0
- package/dist/battle/script/monsterScripts18xx.js +330 -0
- package/dist/battle/script/monsterScripts19xx.d.ts +2 -0
- package/dist/battle/script/monsterScripts19xx.js +271 -0
- package/dist/battle/script/monsterScripts19xxPart2.d.ts +2 -0
- package/dist/battle/script/monsterScripts19xxPart2.js +400 -0
- package/dist/battle/skillEngine.d.ts +36 -0
- package/dist/battle/skillEngine.js +246 -0
- package/dist/battle/targeting.d.ts +3 -0
- package/dist/battle/targeting.js +38 -0
- package/dist/battle/turnbar.d.ts +8 -0
- package/dist/battle/turnbar.js +40 -0
- package/dist/battle/types.d.ts +302 -0
- package/dist/battle/types.js +1 -0
- package/dist/cocos-adapter/BattleFacade.d.ts +8 -0
- package/dist/cocos-adapter/BattleFacade.js +22 -0
- package/dist/cocos-adapter/clientBattleResult.d.ts +127 -0
- package/dist/cocos-adapter/clientBattleResult.js +413 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +8 -0
- package/package.json +32 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BattleCore } from "../battle/battleCore";
|
|
2
|
+
export class BattleFacade {
|
|
3
|
+
core;
|
|
4
|
+
start(units, config, initialize) {
|
|
5
|
+
const normalizedUnits = units.map((unit) => ({
|
|
6
|
+
...unit,
|
|
7
|
+
id: this.toInstanceId(unit)
|
|
8
|
+
}));
|
|
9
|
+
this.core = new BattleCore(normalizedUnits, config);
|
|
10
|
+
initialize?.(this.core);
|
|
11
|
+
return this.core.run();
|
|
12
|
+
}
|
|
13
|
+
stop() {
|
|
14
|
+
this.core = undefined;
|
|
15
|
+
}
|
|
16
|
+
toInstanceId(unit) {
|
|
17
|
+
if (/^t[12]_p\d+_.+/.test(unit.id)) {
|
|
18
|
+
return unit.id;
|
|
19
|
+
}
|
|
20
|
+
return `t${unit.teamId}_p${unit.position}_${unit.id}`;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { BattleResult } from "../battle/battleCore";
|
|
2
|
+
import type { BuffConfig } from "../battle/types";
|
|
3
|
+
export interface ClientPositionChange {
|
|
4
|
+
unitId: string;
|
|
5
|
+
from: number;
|
|
6
|
+
to: number;
|
|
7
|
+
reason: "position_shift";
|
|
8
|
+
}
|
|
9
|
+
export interface ClientActionStateRef {
|
|
10
|
+
snapshotIndex: number | null;
|
|
11
|
+
}
|
|
12
|
+
export interface ClientBuffChange {
|
|
13
|
+
unitId: string;
|
|
14
|
+
buffId: string;
|
|
15
|
+
name: string;
|
|
16
|
+
changeType: "added" | "removed" | "updated";
|
|
17
|
+
fromStacks: number;
|
|
18
|
+
toStacks: number;
|
|
19
|
+
fromRemainingRounds: number;
|
|
20
|
+
toRemainingRounds: number;
|
|
21
|
+
sourceId: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ClientStatChange {
|
|
24
|
+
unitId: string;
|
|
25
|
+
stat: "Att" | "Def" | "Spd" | "pCTR" | "pCTD" | "pHE" | "pEHR" | "pERes" | "pIM" | "pITM" | "pVulnerability";
|
|
26
|
+
from: number;
|
|
27
|
+
to: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ClientPanelBuffDelta {
|
|
30
|
+
unitId: string;
|
|
31
|
+
buffId: string;
|
|
32
|
+
name: string;
|
|
33
|
+
stat: ClientStatChange["stat"];
|
|
34
|
+
delta: number;
|
|
35
|
+
mode: "on_first_gain";
|
|
36
|
+
}
|
|
37
|
+
export interface ClientStateSettlementDelta {
|
|
38
|
+
unitId: string;
|
|
39
|
+
stat: "Hp" | "AP" | "Energy" | "Shield" | "Imbalance";
|
|
40
|
+
delta: number;
|
|
41
|
+
source: "buff_settlement" | "other_runtime_change" | "imbalance_turn_clear" | "imbalance_skill_effect";
|
|
42
|
+
sourceBuffId: string | null;
|
|
43
|
+
element: string | null;
|
|
44
|
+
sourceActorId: string | null;
|
|
45
|
+
sourceSkillId: string | null;
|
|
46
|
+
}
|
|
47
|
+
export interface ClientImbalanceBreak {
|
|
48
|
+
targetId: string;
|
|
49
|
+
attackerId: string;
|
|
50
|
+
element: string;
|
|
51
|
+
}
|
|
52
|
+
export interface ClientImbalanceChange {
|
|
53
|
+
targetId: string;
|
|
54
|
+
delta: number;
|
|
55
|
+
reason: "turn_start_clear" | "skill_effect";
|
|
56
|
+
sourceActorId: string | null;
|
|
57
|
+
sourceSkillId: string | null;
|
|
58
|
+
}
|
|
59
|
+
export interface ClientTurnSettlement {
|
|
60
|
+
turn: number;
|
|
61
|
+
snapshotIndex: number;
|
|
62
|
+
positionChanges: ClientPositionChange[];
|
|
63
|
+
stateSettlementDeltas: ClientStateSettlementDelta[];
|
|
64
|
+
imbalanceChanges: ClientImbalanceChange[];
|
|
65
|
+
dotDamages: Array<{
|
|
66
|
+
targetId: string;
|
|
67
|
+
value: number;
|
|
68
|
+
buffId: string;
|
|
69
|
+
element: string | null;
|
|
70
|
+
}>;
|
|
71
|
+
}
|
|
72
|
+
type ClientSnapshot = Omit<BattleResult["logs"]["snapshots"][number], "dotDamages" | "imbalanceBreaks" | "imbalanceChanges"> & {
|
|
73
|
+
dotDamages: Array<{
|
|
74
|
+
targetId: string;
|
|
75
|
+
value: number;
|
|
76
|
+
buffId: string;
|
|
77
|
+
element: string | null;
|
|
78
|
+
}>;
|
|
79
|
+
imbalanceBreaks: Array<{
|
|
80
|
+
targetId: string;
|
|
81
|
+
attackerId: string;
|
|
82
|
+
element: string;
|
|
83
|
+
}>;
|
|
84
|
+
imbalanceChanges: ClientImbalanceChange[];
|
|
85
|
+
};
|
|
86
|
+
export type ClientAction = BattleResult["logs"]["actionLogs"][number] & {
|
|
87
|
+
actionIndex: number;
|
|
88
|
+
stateRef: ClientActionStateRef;
|
|
89
|
+
buffChanges: ClientBuffChange[];
|
|
90
|
+
panelBuffDeltas: ClientPanelBuffDelta[];
|
|
91
|
+
stateRuntimeDeltas: ClientStateSettlementDelta[];
|
|
92
|
+
imbalanceBreaks: ClientImbalanceBreak[];
|
|
93
|
+
imbalanceChanges: ClientImbalanceChange[];
|
|
94
|
+
};
|
|
95
|
+
export interface ClientBattleResult {
|
|
96
|
+
meta: {
|
|
97
|
+
seed: number;
|
|
98
|
+
maxTurns: number;
|
|
99
|
+
generatedAt: string;
|
|
100
|
+
};
|
|
101
|
+
lineup: {
|
|
102
|
+
team1: string[];
|
|
103
|
+
team2: string[];
|
|
104
|
+
};
|
|
105
|
+
summary: {
|
|
106
|
+
winner: 1 | 2 | 0;
|
|
107
|
+
totalTurns: number;
|
|
108
|
+
actionCount: number;
|
|
109
|
+
snapshotCount: number;
|
|
110
|
+
errorCount: number;
|
|
111
|
+
};
|
|
112
|
+
actions: ClientAction[];
|
|
113
|
+
turnSettlements: ClientTurnSettlement[];
|
|
114
|
+
snapshots: ClientSnapshot[];
|
|
115
|
+
errors: BattleResult["logs"]["errors"];
|
|
116
|
+
finalUnits: BattleResult["logs"]["snapshots"][number]["units"];
|
|
117
|
+
}
|
|
118
|
+
export interface BuildClientBattleResultInput {
|
|
119
|
+
battleResult: BattleResult;
|
|
120
|
+
seed: number;
|
|
121
|
+
maxTurns: number;
|
|
122
|
+
team1HeroIds: string[];
|
|
123
|
+
team2HeroIds: string[];
|
|
124
|
+
buffConfigs?: BuffConfig[];
|
|
125
|
+
}
|
|
126
|
+
export declare function buildClientBattleResult(input: BuildClientBattleResultInput): ClientBattleResult;
|
|
127
|
+
export {};
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
function toPositionMap(snapshot) {
|
|
2
|
+
return new Map((snapshot?.units ?? []).map((u) => [u.id, u.position]));
|
|
3
|
+
}
|
|
4
|
+
function diffPositions(prev, next) {
|
|
5
|
+
const changes = [];
|
|
6
|
+
for (const [unitId, from] of prev.entries()) {
|
|
7
|
+
const to = next.get(unitId);
|
|
8
|
+
if (to === undefined || to === from) {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
changes.push({
|
|
12
|
+
unitId,
|
|
13
|
+
from,
|
|
14
|
+
to,
|
|
15
|
+
reason: "position_shift"
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return changes;
|
|
19
|
+
}
|
|
20
|
+
function buildAfterSkillSnapshotIndices(snapshots) {
|
|
21
|
+
const indices = [];
|
|
22
|
+
for (let i = 0; i < snapshots.length; i++) {
|
|
23
|
+
if (snapshots[i].phase === "afterSkill") {
|
|
24
|
+
indices.push(i);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return indices;
|
|
28
|
+
}
|
|
29
|
+
function buildTurnEndSnapshotIndexByTurn(snapshots) {
|
|
30
|
+
const map = new Map();
|
|
31
|
+
for (let i = 0; i < snapshots.length; i++) {
|
|
32
|
+
if (snapshots[i].phase === "turnEnd") {
|
|
33
|
+
map.set(snapshots[i].turn, i);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return map;
|
|
37
|
+
}
|
|
38
|
+
function unitMap(snapshot) {
|
|
39
|
+
return new Map((snapshot?.units ?? []).map((u) => [u.id, u]));
|
|
40
|
+
}
|
|
41
|
+
function diffBuffs(prevSnapshot, nextSnapshot) {
|
|
42
|
+
const changes = [];
|
|
43
|
+
const prevUnits = unitMap(prevSnapshot);
|
|
44
|
+
const nextUnits = unitMap(nextSnapshot);
|
|
45
|
+
for (const [unitId, nextUnit] of nextUnits.entries()) {
|
|
46
|
+
const prevUnit = prevUnits.get(unitId);
|
|
47
|
+
const prevBuffs = new Map((prevUnit?.buffs ?? []).map((b) => [b.id, b]));
|
|
48
|
+
const nextBuffs = new Map((nextUnit.buffs ?? []).map((b) => [b.id, b]));
|
|
49
|
+
for (const [buffId, nextBuff] of nextBuffs.entries()) {
|
|
50
|
+
const prevBuff = prevBuffs.get(buffId);
|
|
51
|
+
if (!prevBuff) {
|
|
52
|
+
changes.push({
|
|
53
|
+
unitId,
|
|
54
|
+
buffId,
|
|
55
|
+
name: nextBuff.name,
|
|
56
|
+
changeType: "added",
|
|
57
|
+
fromStacks: 0,
|
|
58
|
+
toStacks: nextBuff.stacks,
|
|
59
|
+
fromRemainingRounds: 0,
|
|
60
|
+
toRemainingRounds: nextBuff.remainingRounds,
|
|
61
|
+
sourceId: nextBuff.sourceId
|
|
62
|
+
});
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (prevBuff.stacks !== nextBuff.stacks || prevBuff.remainingRounds !== nextBuff.remainingRounds) {
|
|
66
|
+
changes.push({
|
|
67
|
+
unitId,
|
|
68
|
+
buffId,
|
|
69
|
+
name: nextBuff.name,
|
|
70
|
+
changeType: "updated",
|
|
71
|
+
fromStacks: prevBuff.stacks,
|
|
72
|
+
toStacks: nextBuff.stacks,
|
|
73
|
+
fromRemainingRounds: prevBuff.remainingRounds,
|
|
74
|
+
toRemainingRounds: nextBuff.remainingRounds,
|
|
75
|
+
sourceId: nextBuff.sourceId
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
for (const [buffId, prevBuff] of prevBuffs.entries()) {
|
|
80
|
+
if (nextBuffs.has(buffId)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
changes.push({
|
|
84
|
+
unitId,
|
|
85
|
+
buffId,
|
|
86
|
+
name: prevBuff.name,
|
|
87
|
+
changeType: "removed",
|
|
88
|
+
fromStacks: prevBuff.stacks,
|
|
89
|
+
toStacks: 0,
|
|
90
|
+
fromRemainingRounds: prevBuff.remainingRounds,
|
|
91
|
+
toRemainingRounds: 0,
|
|
92
|
+
sourceId: prevBuff.sourceId
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return changes;
|
|
97
|
+
}
|
|
98
|
+
function diffStats(prevSnapshot, nextSnapshot) {
|
|
99
|
+
const changes = [];
|
|
100
|
+
const prevUnits = unitMap(prevSnapshot);
|
|
101
|
+
const nextUnits = unitMap(nextSnapshot);
|
|
102
|
+
const stats = ["Att", "Def", "Spd", "pCTR", "pCTD", "pHE", "pEHR", "pERes", "pIM", "pITM", "pVulnerability"];
|
|
103
|
+
for (const [unitId, nextUnit] of nextUnits.entries()) {
|
|
104
|
+
const prevUnit = prevUnits.get(unitId);
|
|
105
|
+
for (const stat of stats) {
|
|
106
|
+
const from = prevUnit?.effectiveAllStats?.[stat] ?? 0;
|
|
107
|
+
const to = nextUnit.effectiveAllStats[stat];
|
|
108
|
+
if (from !== to) {
|
|
109
|
+
changes.push({ unitId, stat, from, to });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return changes;
|
|
114
|
+
}
|
|
115
|
+
function buildTurnSettlements(snapshots) {
|
|
116
|
+
const settlements = [];
|
|
117
|
+
for (let i = 0; i < snapshots.length; i++) {
|
|
118
|
+
const current = snapshots[i];
|
|
119
|
+
if (current.phase !== "turnEnd") {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const prev = i > 0 ? snapshots[i - 1] : undefined;
|
|
123
|
+
settlements.push({
|
|
124
|
+
turn: current.turn,
|
|
125
|
+
snapshotIndex: i,
|
|
126
|
+
positionChanges: diffPositions(toPositionMap(prev), toPositionMap(current)),
|
|
127
|
+
stateSettlementDeltas: diffRuntimeStates(prev, current, undefined, current.dotDamages, current.imbalanceChanges),
|
|
128
|
+
imbalanceChanges: (current.imbalanceChanges ?? []).map((change) => ({
|
|
129
|
+
targetId: change.targetId,
|
|
130
|
+
delta: change.delta,
|
|
131
|
+
reason: change.reason,
|
|
132
|
+
sourceActorId: change.sourceActorId ?? null,
|
|
133
|
+
sourceSkillId: change.sourceSkillId ?? null
|
|
134
|
+
})),
|
|
135
|
+
dotDamages: (current.dotDamages ?? []).map((dot) => ({
|
|
136
|
+
targetId: dot.targetId,
|
|
137
|
+
value: dot.value,
|
|
138
|
+
buffId: dot.buffId,
|
|
139
|
+
element: dot.element ?? null
|
|
140
|
+
}))
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
return settlements;
|
|
144
|
+
}
|
|
145
|
+
function impactedRuntimeStatsByHits(hits) {
|
|
146
|
+
const map = new Map();
|
|
147
|
+
for (const hit of hits ?? []) {
|
|
148
|
+
const stats = map.get(hit.targetId) ?? new Set();
|
|
149
|
+
if (hit.effectType === "damage" || hit.effectType === "heal") {
|
|
150
|
+
stats.add("Hp");
|
|
151
|
+
}
|
|
152
|
+
else if (hit.effectType === "shield") {
|
|
153
|
+
stats.add("Shield");
|
|
154
|
+
}
|
|
155
|
+
map.set(hit.targetId, stats);
|
|
156
|
+
}
|
|
157
|
+
return map;
|
|
158
|
+
}
|
|
159
|
+
function diffRuntimeStates(prevSnapshot, nextSnapshot, hits, dotDamages, imbalanceChanges) {
|
|
160
|
+
if (!nextSnapshot) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
const deltas = [];
|
|
164
|
+
const prevUnits = unitMap(prevSnapshot);
|
|
165
|
+
const hitImpacts = impactedRuntimeStatsByHits(hits);
|
|
166
|
+
const dotDamageMap = new Map();
|
|
167
|
+
for (const dot of dotDamages ?? []) {
|
|
168
|
+
const buffMap = dotDamageMap.get(dot.targetId) ?? new Map();
|
|
169
|
+
buffMap.set(dot.buffId, { value: dot.value, element: dot.element });
|
|
170
|
+
dotDamageMap.set(dot.targetId, buffMap);
|
|
171
|
+
}
|
|
172
|
+
const imbalanceChangeMap = new Map();
|
|
173
|
+
for (const change of imbalanceChanges ?? []) {
|
|
174
|
+
const list = imbalanceChangeMap.get(change.targetId) ?? [];
|
|
175
|
+
list.push(change);
|
|
176
|
+
imbalanceChangeMap.set(change.targetId, list);
|
|
177
|
+
}
|
|
178
|
+
for (const nextUnit of nextSnapshot.units) {
|
|
179
|
+
const prevUnit = prevUnits.get(nextUnit.id);
|
|
180
|
+
const relatedBuffIds = [...new Set([...(prevUnit?.buffs ?? []), ...(nextUnit.buffs ?? [])].map((b) => b.id))];
|
|
181
|
+
const fields = [
|
|
182
|
+
["Hp", prevUnit?.hp ?? nextUnit.hp, nextUnit.hp],
|
|
183
|
+
["AP", prevUnit?.ap ?? nextUnit.ap, nextUnit.ap],
|
|
184
|
+
["Energy", prevUnit?.energy ?? nextUnit.energy, nextUnit.energy],
|
|
185
|
+
["Shield", prevUnit?.shield ?? nextUnit.shield, nextUnit.shield],
|
|
186
|
+
["Imbalance", prevUnit?.imbalance ?? nextUnit.imbalance, nextUnit.imbalance]
|
|
187
|
+
];
|
|
188
|
+
for (const [stat, from, to] of fields) {
|
|
189
|
+
const delta = to - from;
|
|
190
|
+
if (delta === 0) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const impactedByHit = hitImpacts.get(nextUnit.id)?.has(stat) ?? false;
|
|
194
|
+
const unitDotMap = dotDamageMap.get(nextUnit.id);
|
|
195
|
+
let sourceBuffId = null;
|
|
196
|
+
let element = null;
|
|
197
|
+
let sourceActorId = null;
|
|
198
|
+
let sourceSkillId = null;
|
|
199
|
+
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
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (stat === "Imbalance") {
|
|
214
|
+
const exactMatchedChange = (imbalanceChangeMap.get(nextUnit.id) ?? []).find((change) => change.delta === delta);
|
|
215
|
+
if (exactMatchedChange?.reason === "turn_start_clear") {
|
|
216
|
+
source = "imbalance_turn_clear";
|
|
217
|
+
sourceActorId = exactMatchedChange.sourceActorId ?? null;
|
|
218
|
+
sourceSkillId = exactMatchedChange.sourceSkillId ?? null;
|
|
219
|
+
}
|
|
220
|
+
else if (exactMatchedChange?.reason === "skill_effect") {
|
|
221
|
+
source = "imbalance_skill_effect";
|
|
222
|
+
sourceActorId = exactMatchedChange.sourceActorId ?? null;
|
|
223
|
+
sourceSkillId = exactMatchedChange.sourceSkillId ?? null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
deltas.push({
|
|
227
|
+
unitId: nextUnit.id,
|
|
228
|
+
stat,
|
|
229
|
+
delta,
|
|
230
|
+
source,
|
|
231
|
+
sourceBuffId,
|
|
232
|
+
element,
|
|
233
|
+
sourceActorId,
|
|
234
|
+
sourceSkillId
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return deltas;
|
|
239
|
+
}
|
|
240
|
+
function statFromBuffConfig(config) {
|
|
241
|
+
const stats = new Set();
|
|
242
|
+
if (config.attackRateDelta) {
|
|
243
|
+
stats.add("Att");
|
|
244
|
+
}
|
|
245
|
+
if (config.defenseRateDelta) {
|
|
246
|
+
stats.add("Def");
|
|
247
|
+
}
|
|
248
|
+
if (config.speedRateDelta) {
|
|
249
|
+
stats.add("Spd");
|
|
250
|
+
}
|
|
251
|
+
if (config.critRateDelta) {
|
|
252
|
+
stats.add("pCTR");
|
|
253
|
+
}
|
|
254
|
+
if (config.critDamageDelta) {
|
|
255
|
+
stats.add("pCTD");
|
|
256
|
+
}
|
|
257
|
+
if (config.healEffectDelta) {
|
|
258
|
+
stats.add("pHE");
|
|
259
|
+
}
|
|
260
|
+
if (config.effectHitDelta) {
|
|
261
|
+
stats.add("pEHR");
|
|
262
|
+
}
|
|
263
|
+
if (config.effectResDelta) {
|
|
264
|
+
stats.add("pERes");
|
|
265
|
+
}
|
|
266
|
+
if (config.vulnerabilityDelta) {
|
|
267
|
+
stats.add("pVulnerability");
|
|
268
|
+
}
|
|
269
|
+
return stats;
|
|
270
|
+
}
|
|
271
|
+
function buildBuffAffectStatsMap(buffConfigs) {
|
|
272
|
+
const map = new Map();
|
|
273
|
+
for (const config of buffConfigs) {
|
|
274
|
+
map.set(config.id, statFromBuffConfig(config));
|
|
275
|
+
}
|
|
276
|
+
return map;
|
|
277
|
+
}
|
|
278
|
+
function toPanelBuffDeltas(buffChanges, statChanges, buffAffectStatsMap) {
|
|
279
|
+
const added = buffChanges.filter((b) => b.changeType === "added");
|
|
280
|
+
if (added.length === 0) {
|
|
281
|
+
return [];
|
|
282
|
+
}
|
|
283
|
+
const addedByUnit = new Map();
|
|
284
|
+
for (const buff of added) {
|
|
285
|
+
const list = addedByUnit.get(buff.unitId) ?? [];
|
|
286
|
+
list.push(buff);
|
|
287
|
+
addedByUnit.set(buff.unitId, list);
|
|
288
|
+
}
|
|
289
|
+
const result = [];
|
|
290
|
+
for (const stat of statChanges) {
|
|
291
|
+
const delta = stat.to - stat.from;
|
|
292
|
+
if (delta === 0) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
const candidates = addedByUnit.get(stat.unitId) ?? [];
|
|
296
|
+
if (candidates.length === 0) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const preferred = candidates.find((item) => buffAffectStatsMap.get(item.buffId)?.has(stat.stat)) ??
|
|
300
|
+
(stat.stat === "pVulnerability" ? candidates.find((item) => item.buffId.includes("vulnerability")) : undefined) ??
|
|
301
|
+
candidates[0];
|
|
302
|
+
result.push({
|
|
303
|
+
unitId: stat.unitId,
|
|
304
|
+
buffId: preferred.buffId,
|
|
305
|
+
name: preferred.name,
|
|
306
|
+
stat: stat.stat,
|
|
307
|
+
delta,
|
|
308
|
+
mode: "on_first_gain"
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
function buildClientActions(battleResult, buffAffectStatsMap) {
|
|
314
|
+
const snapshots = battleResult.logs.snapshots;
|
|
315
|
+
const afterSkillIndices = buildAfterSkillSnapshotIndices(snapshots);
|
|
316
|
+
const turnEndIndexByTurn = buildTurnEndSnapshotIndexByTurn(snapshots);
|
|
317
|
+
const firstSnapshot = snapshots[0];
|
|
318
|
+
let currentSnapshotIndex = 0;
|
|
319
|
+
let afterSkillCursor = 0;
|
|
320
|
+
let currentTurn = firstSnapshot?.turn ?? 0;
|
|
321
|
+
const actions = [];
|
|
322
|
+
for (let actionIndex = 0; actionIndex < battleResult.logs.actionLogs.length; actionIndex++) {
|
|
323
|
+
const action = battleResult.logs.actionLogs[actionIndex];
|
|
324
|
+
if (action.turn > currentTurn) {
|
|
325
|
+
const prevTurnEndIndex = turnEndIndexByTurn.get(action.turn - 1);
|
|
326
|
+
if (prevTurnEndIndex !== undefined) {
|
|
327
|
+
currentSnapshotIndex = prevTurnEndIndex;
|
|
328
|
+
}
|
|
329
|
+
currentTurn = action.turn;
|
|
330
|
+
}
|
|
331
|
+
const snapshotIndex = afterSkillIndices[afterSkillCursor] ?? null;
|
|
332
|
+
const snapshot = snapshotIndex === null ? undefined : snapshots[snapshotIndex];
|
|
333
|
+
afterSkillCursor += 1;
|
|
334
|
+
const buffChanges = diffBuffs(snapshots[currentSnapshotIndex], snapshot);
|
|
335
|
+
const statChanges = diffStats(snapshots[currentSnapshotIndex], snapshot);
|
|
336
|
+
actions.push({
|
|
337
|
+
...action,
|
|
338
|
+
actionIndex,
|
|
339
|
+
buffChanges,
|
|
340
|
+
panelBuffDeltas: toPanelBuffDeltas(buffChanges, statChanges, buffAffectStatsMap),
|
|
341
|
+
stateRuntimeDeltas: diffRuntimeStates(snapshots[currentSnapshotIndex], snapshot, action.result.hits, snapshot?.dotDamages, snapshot?.imbalanceChanges),
|
|
342
|
+
imbalanceBreaks: (snapshot?.imbalanceBreaks ?? []).map((ib) => ({
|
|
343
|
+
targetId: ib.targetId,
|
|
344
|
+
attackerId: ib.attackerId,
|
|
345
|
+
element: ib.element
|
|
346
|
+
})),
|
|
347
|
+
imbalanceChanges: (snapshot?.imbalanceChanges ?? []).map((change) => ({
|
|
348
|
+
targetId: change.targetId,
|
|
349
|
+
delta: change.delta,
|
|
350
|
+
reason: change.reason,
|
|
351
|
+
sourceActorId: change.sourceActorId ?? null,
|
|
352
|
+
sourceSkillId: change.sourceSkillId ?? null
|
|
353
|
+
})),
|
|
354
|
+
stateRef: {
|
|
355
|
+
snapshotIndex
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
if (snapshotIndex !== null) {
|
|
359
|
+
currentSnapshotIndex = snapshotIndex;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return actions;
|
|
363
|
+
}
|
|
364
|
+
export function buildClientBattleResult(input) {
|
|
365
|
+
const finalSnapshot = [...input.battleResult.logs.snapshots].reverse().find((item) => item.phase === "turnEnd");
|
|
366
|
+
const buffAffectStatsMap = buildBuffAffectStatsMap(input.buffConfigs ?? []);
|
|
367
|
+
const actions = buildClientActions(input.battleResult, buffAffectStatsMap);
|
|
368
|
+
const turnSettlements = buildTurnSettlements(input.battleResult.logs.snapshots);
|
|
369
|
+
const snapshots = input.battleResult.logs.snapshots.map((snapshot) => ({
|
|
370
|
+
...snapshot,
|
|
371
|
+
dotDamages: (snapshot.dotDamages ?? []).map((dot) => ({
|
|
372
|
+
targetId: dot.targetId,
|
|
373
|
+
value: dot.value,
|
|
374
|
+
buffId: dot.buffId,
|
|
375
|
+
element: dot.element ?? null
|
|
376
|
+
})),
|
|
377
|
+
imbalanceBreaks: (snapshot.imbalanceBreaks ?? []).map((ib) => ({
|
|
378
|
+
targetId: ib.targetId,
|
|
379
|
+
attackerId: ib.attackerId,
|
|
380
|
+
element: ib.element
|
|
381
|
+
})),
|
|
382
|
+
imbalanceChanges: (snapshot.imbalanceChanges ?? []).map((change) => ({
|
|
383
|
+
targetId: change.targetId,
|
|
384
|
+
delta: change.delta,
|
|
385
|
+
reason: change.reason,
|
|
386
|
+
sourceActorId: change.sourceActorId ?? null,
|
|
387
|
+
sourceSkillId: change.sourceSkillId ?? null
|
|
388
|
+
}))
|
|
389
|
+
}));
|
|
390
|
+
return {
|
|
391
|
+
meta: {
|
|
392
|
+
seed: input.seed,
|
|
393
|
+
maxTurns: input.maxTurns,
|
|
394
|
+
generatedAt: new Date().toISOString()
|
|
395
|
+
},
|
|
396
|
+
lineup: {
|
|
397
|
+
team1: input.team1HeroIds,
|
|
398
|
+
team2: input.team2HeroIds
|
|
399
|
+
},
|
|
400
|
+
summary: {
|
|
401
|
+
winner: input.battleResult.winner,
|
|
402
|
+
totalTurns: input.battleResult.totalTurns,
|
|
403
|
+
actionCount: actions.length,
|
|
404
|
+
snapshotCount: input.battleResult.logs.snapshots.length,
|
|
405
|
+
errorCount: input.battleResult.logs.errors.length
|
|
406
|
+
},
|
|
407
|
+
actions,
|
|
408
|
+
turnSettlements,
|
|
409
|
+
snapshots,
|
|
410
|
+
errors: input.battleResult.logs.errors,
|
|
411
|
+
finalUnits: finalSnapshot?.units ?? []
|
|
412
|
+
};
|
|
413
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { BattleCore } from "./battle/battleCore";
|
|
2
|
+
export { BattleFacade } from "./cocos-adapter/BattleFacade";
|
|
3
|
+
export { buildClientBattleResult } from "./cocos-adapter/clientBattleResult";
|
|
4
|
+
export type { BuildClientBattleResultInput, ClientAction, ClientActionStateRef, ClientBattleResult, ClientImbalanceChange, ClientPanelBuffDelta, ClientPositionChange, ClientStateSettlementDelta, ClientStatChange, ClientTurnSettlement } 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 type { BattleScriptApi, BattleConfig, BattleInitUnit, BuffConfig, ISkillScript, SkillDefinition, SkillExecMode, SkillTemplate, UnitModel, UnitStats } from "./battle/types";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { BattleCore } from "./battle/battleCore";
|
|
2
|
+
export { BattleFacade } from "./cocos-adapter/BattleFacade";
|
|
3
|
+
export { buildClientBattleResult } from "./cocos-adapter/clientBattleResult";
|
|
4
|
+
export { BattleEventBus } from "./battle/eventBus";
|
|
5
|
+
export { EffectSystem } from "./battle/effectSystem";
|
|
6
|
+
export { calcDamage, calcEffectProbability, calcHealOrShield } from "./battle/formula";
|
|
7
|
+
export { ConfigSkillEngine, ScriptSkillEngine, SkillExecutor } from "./battle/skillEngine";
|
|
8
|
+
export { designedScripts } from "./battle/script/designedScripts";
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hrbattle",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "一个基于回合制战斗引擎,支持技能脚本、buff系统和效果解析。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"prepublishOnly": "npm run build",
|
|
14
|
+
"battle:result": "vitest run tests/battleResultOutput.test.ts"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"battle",
|
|
18
|
+
"turn-based",
|
|
19
|
+
"game",
|
|
20
|
+
"rpg",
|
|
21
|
+
"skill",
|
|
22
|
+
"buff"
|
|
23
|
+
],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.15.3",
|
|
27
|
+
"tsx": "^4.19.4",
|
|
28
|
+
"typescript": "^5.8.3",
|
|
29
|
+
"vitest": "^3.1.2",
|
|
30
|
+
"xlsx": "^0.18.5"
|
|
31
|
+
}
|
|
32
|
+
}
|