poi-plugin-gimmick-tracker 0.1.0 → 0.1.1
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 +1 -1
- package/i18n/zh-CN.json +18 -9
- package/index.js +254 -27
- package/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# poi-plugin-gimmick-tracker
|
|
2
2
|
|
|
3
|
-
面向 poi 11.1.0 的舰队 Collection
|
|
3
|
+
面向 poi 11.1.0 的舰队 Collection“解密·削甲记录”插件。插件平时在后台记录当前出击事实;检测到解密或削甲生效信号时聚焦内嵌页签,展示待确认卡片。插件不使用独立窗口,不联网,也不依赖 `poi-plugin-battle-detail` 的战斗分组。
|
|
4
4
|
|
|
5
5
|
## 能做什么
|
|
6
6
|
|
package/i18n/zh-CN.json
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
"title": "
|
|
5
|
-
"subtitle": "
|
|
2
|
+
"解密·削甲记录": "解密·削甲记录",
|
|
3
|
+
"记录出击路线,并在解密或削甲生效后提醒确认": "记录出击路线,并在解密或削甲生效后提醒确认",
|
|
4
|
+
"title": "解密·削甲记录",
|
|
5
|
+
"subtitle": "自动记下路线、战斗和空袭;检测到解密或削甲生效后,请你确认刚完成的条件。数据只保存在本机。",
|
|
6
|
+
"running": "运行中,正在接收 Poi 战斗数据",
|
|
7
|
+
"stopped": "监听已停止",
|
|
8
|
+
"receivedThisRun": "本次加载收到响应",
|
|
9
|
+
"trackedThisRun": "本次加载已处理",
|
|
10
|
+
"recordedSorties": "累计出击",
|
|
11
|
+
"observations": "累计记录",
|
|
12
|
+
"lastResponse": "最近收到",
|
|
13
|
+
"lastPageSwitch": "最近自动切页请求",
|
|
14
|
+
"unresolvedNodes": "仍有 {count} 条旧记录暂时只能显示 #编号;FCD 点位数据就绪后会自动回填。",
|
|
6
15
|
"activeMap": "当前海域",
|
|
7
|
-
"pendingReviews": "
|
|
16
|
+
"pendingReviews": "待确认记录",
|
|
8
17
|
"pending": "待确认",
|
|
9
|
-
"noPending": "
|
|
18
|
+
"noPending": "暂无待确认记录。普通出击只在后台记录,不会打断你。",
|
|
10
19
|
"unknownMap": "未知海域",
|
|
11
20
|
"route": "本次路线",
|
|
12
21
|
"incompleteRoute": "未捕获到完整出击:插件可能在出击中途加载,或仅收到了回港信号。",
|
|
@@ -24,9 +33,9 @@
|
|
|
24
33
|
"importNeedMap": "尚未识别当前海域,请先捕获一次地图出击。",
|
|
25
34
|
"importSuccess": "已识别 {count} 条条件。",
|
|
26
35
|
"noMatch": "未识别到受支持的条件格式。",
|
|
27
|
-
"completed": "
|
|
28
|
-
"lastTriggeredSortie": "
|
|
29
|
-
"triggerHistory": "
|
|
36
|
+
"completed": "按海域查看解密·削甲进度",
|
|
37
|
+
"lastTriggeredSortie": "上一次生效出击",
|
|
38
|
+
"triggerHistory": "每次检测到生效的出击",
|
|
30
39
|
"noCompleted": "尚未导入攻略条件,也没有自由完成记录。",
|
|
31
40
|
"observed": "观测",
|
|
32
41
|
"confirmed": "已确认",
|
package/index.js
CHANGED
|
@@ -25,7 +25,6 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
}) : target, mod));
|
|
26
26
|
|
|
27
27
|
//#endregion
|
|
28
|
-
let views_create_store = require("views/create-store");
|
|
29
28
|
let views_env = require("views/env");
|
|
30
29
|
let fs = require("fs");
|
|
31
30
|
let path = require("path");
|
|
@@ -624,6 +623,77 @@ function refreshPendingCandidates(state) {
|
|
|
624
623
|
review.candidates = buildReviewCandidates(state, review.mapId, review.observationIds);
|
|
625
624
|
}
|
|
626
625
|
}
|
|
626
|
+
function isFallbackLabel(label, nodeNo) {
|
|
627
|
+
return nodeNo != null && label === `#${nodeNo}`;
|
|
628
|
+
}
|
|
629
|
+
function relabelResolvedNodes(previous, nodeResolver, now) {
|
|
630
|
+
const state = cloneState(previous);
|
|
631
|
+
let changed = false;
|
|
632
|
+
const resolve = (item) => {
|
|
633
|
+
if (!isFallbackLabel(item.nodeLabel, item.nodeNo)) return;
|
|
634
|
+
const label = safeNodeLabel(nodeResolver, item, item.nodeNo);
|
|
635
|
+
if (label === item.nodeLabel || label === `#${item.nodeNo}`) return;
|
|
636
|
+
item.nodeLabel = label;
|
|
637
|
+
changed = true;
|
|
638
|
+
};
|
|
639
|
+
for (const observation of state.observations) resolve(observation);
|
|
640
|
+
for (const visit of state.activeSortie?.route ?? []) resolve(visit);
|
|
641
|
+
for (const event of state.activeSortie?.timeline ?? []) {
|
|
642
|
+
if (event.nodeNo == null || event.nodeLabel == null) continue;
|
|
643
|
+
resolve({
|
|
644
|
+
mapArea: state.activeSortie.mapArea,
|
|
645
|
+
mapNo: state.activeSortie.mapNo,
|
|
646
|
+
mapId: state.activeSortie.mapId,
|
|
647
|
+
nodeNo: event.nodeNo,
|
|
648
|
+
get nodeLabel() {
|
|
649
|
+
return event.nodeLabel;
|
|
650
|
+
},
|
|
651
|
+
set nodeLabel(value) {
|
|
652
|
+
event.nodeLabel = value;
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
for (const review of state.reviews) {
|
|
657
|
+
for (const visit of review.route) resolve(visit);
|
|
658
|
+
for (const event of review.timeline) {
|
|
659
|
+
if (event.nodeNo == null || event.nodeLabel == null) continue;
|
|
660
|
+
resolve({
|
|
661
|
+
mapArea: review.mapArea,
|
|
662
|
+
mapNo: review.mapNo,
|
|
663
|
+
mapId: review.mapId,
|
|
664
|
+
nodeNo: event.nodeNo,
|
|
665
|
+
get nodeLabel() {
|
|
666
|
+
return event.nodeLabel;
|
|
667
|
+
},
|
|
668
|
+
set nodeLabel(value) {
|
|
669
|
+
event.nodeLabel = value;
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (!changed) return previous;
|
|
675
|
+
for (const condition of state.guideConditions) condition.observedCount = state.observations.filter((observation) => observationMatches(condition, observation)).length;
|
|
676
|
+
for (const completion of state.freeCompletions) {
|
|
677
|
+
const observation = state.observations.find((item) => item.id === completion.observationId);
|
|
678
|
+
if (observation != null) completion.label = observationLabel(observation);
|
|
679
|
+
}
|
|
680
|
+
for (const review of state.reviews) {
|
|
681
|
+
if (review.status === "pending") {
|
|
682
|
+
review.candidates = buildReviewCandidates(state, review.mapId, review.observationIds);
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
for (const candidate of review.candidates) {
|
|
686
|
+
const condition = state.guideConditions.find((item) => item.id === candidate.conditionId);
|
|
687
|
+
const observation = state.observations.find((item) => item.id === candidate.observationId);
|
|
688
|
+
if (condition != null) {
|
|
689
|
+
candidate.label = guideConditionLabel(condition);
|
|
690
|
+
candidate.observedCount = condition.observedCount;
|
|
691
|
+
} else if (observation != null) candidate.label = observationLabel(observation);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
state.updatedAt = Math.max(state.updatedAt, now);
|
|
695
|
+
return state;
|
|
696
|
+
}
|
|
627
697
|
function importGuideConditions(previous, text, map, now) {
|
|
628
698
|
const parsed = parseGuideText(text);
|
|
629
699
|
if (parsed.length === 0) return {
|
|
@@ -748,8 +818,8 @@ function hydrateState(value) {
|
|
|
748
818
|
};
|
|
749
819
|
}
|
|
750
820
|
var FileTrackerStorage = class {
|
|
751
|
-
constructor(appDataPath) {
|
|
752
|
-
this.appDataPath = appDataPath;
|
|
821
|
+
constructor(appDataPath$1) {
|
|
822
|
+
this.appDataPath = appDataPath$1;
|
|
753
823
|
}
|
|
754
824
|
statePath(memberId) {
|
|
755
825
|
const safe = normalizeMemberId(memberId);
|
|
@@ -808,23 +878,59 @@ var TrackerController = class {
|
|
|
808
878
|
eventTarget;
|
|
809
879
|
memberId;
|
|
810
880
|
started = false;
|
|
881
|
+
runtime = {
|
|
882
|
+
started: false,
|
|
883
|
+
receivedCount: 0,
|
|
884
|
+
trackedCount: 0
|
|
885
|
+
};
|
|
886
|
+
viewSnapshot = {
|
|
887
|
+
state: this.state,
|
|
888
|
+
runtime: this.runtime
|
|
889
|
+
};
|
|
811
890
|
handleResponse = (event) => {
|
|
812
891
|
const detail = parseEvent(event);
|
|
813
892
|
if (detail == null) return;
|
|
893
|
+
this.runtime = {
|
|
894
|
+
...this.runtime,
|
|
895
|
+
receivedCount: this.runtime.receivedCount + 1,
|
|
896
|
+
lastResponseAt: detail.time,
|
|
897
|
+
lastResponsePath: detail.path
|
|
898
|
+
};
|
|
814
899
|
try {
|
|
815
900
|
const preferred = this.readPreferredMemberId();
|
|
816
901
|
const portMember = normalizeMemberId(extractPortMemberId(detail));
|
|
817
902
|
this.switchMember(portMember ?? preferred);
|
|
903
|
+
if (detail.path === "/kcsapi/api_port/port" || detail.path === "/kcsapi/api_req_map/start") {
|
|
904
|
+
const relabeled = relabelResolvedNodes(this.state, this.dependencies.nodeResolver, detail.time);
|
|
905
|
+
if (relabeled !== this.state) {
|
|
906
|
+
this.state = relabeled;
|
|
907
|
+
this.persist();
|
|
908
|
+
}
|
|
909
|
+
}
|
|
818
910
|
const result = applyGameResponse(this.state, detail, this.dependencies.nodeResolver);
|
|
819
|
-
if (!result.changed)
|
|
911
|
+
if (!result.changed) {
|
|
912
|
+
this.emit();
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
820
915
|
this.state = result.state;
|
|
916
|
+
this.runtime = {
|
|
917
|
+
...this.runtime,
|
|
918
|
+
trackedCount: this.runtime.trackedCount + 1,
|
|
919
|
+
lastTrackedAt: detail.time
|
|
920
|
+
};
|
|
821
921
|
this.persist();
|
|
822
|
-
this.emit();
|
|
823
922
|
if (result.shouldFocus) try {
|
|
824
|
-
this.dependencies.focus.focusPlugin(this.dependencies.pluginId);
|
|
923
|
+
const method = this.dependencies.focus.focusPlugin(this.dependencies.pluginId);
|
|
924
|
+
this.runtime = {
|
|
925
|
+
...this.runtime,
|
|
926
|
+
lastFocusAt: detail.time,
|
|
927
|
+
lastFocusMethod: method,
|
|
928
|
+
lastFocusPath: detail.path
|
|
929
|
+
};
|
|
825
930
|
} catch (error) {
|
|
826
931
|
this.report(error);
|
|
827
932
|
}
|
|
933
|
+
this.emit();
|
|
828
934
|
} catch (error) {
|
|
829
935
|
this.report(error);
|
|
830
936
|
}
|
|
@@ -837,6 +943,11 @@ var TrackerController = class {
|
|
|
837
943
|
this.started = true;
|
|
838
944
|
this.eventTarget = target;
|
|
839
945
|
this.switchMember(this.readPreferredMemberId());
|
|
946
|
+
this.runtime = {
|
|
947
|
+
...this.runtime,
|
|
948
|
+
started: true,
|
|
949
|
+
startedAt: this.now()
|
|
950
|
+
};
|
|
840
951
|
target.addEventListener("game.response", this.handleResponse);
|
|
841
952
|
this.emit();
|
|
842
953
|
}
|
|
@@ -845,8 +956,13 @@ var TrackerController = class {
|
|
|
845
956
|
this.eventTarget?.removeEventListener("game.response", this.handleResponse);
|
|
846
957
|
this.eventTarget = void 0;
|
|
847
958
|
this.started = false;
|
|
959
|
+
this.runtime = {
|
|
960
|
+
...this.runtime,
|
|
961
|
+
started: false
|
|
962
|
+
};
|
|
963
|
+
this.emit();
|
|
848
964
|
}
|
|
849
|
-
getSnapshot = () => this.
|
|
965
|
+
getSnapshot = () => this.viewSnapshot;
|
|
850
966
|
subscribe = (subscriber) => {
|
|
851
967
|
this.subscribers.add(subscriber);
|
|
852
968
|
return () => this.subscribers.delete(subscriber);
|
|
@@ -912,7 +1028,8 @@ var TrackerController = class {
|
|
|
912
1028
|
try {
|
|
913
1029
|
const stored = this.dependencies.storage.load(next);
|
|
914
1030
|
if (transient == null || transient.updatedAt === 0 && transient.activeSortie == null) {
|
|
915
|
-
this.state = stored;
|
|
1031
|
+
this.state = relabelResolvedNodes(stored, this.dependencies.nodeResolver, this.now());
|
|
1032
|
+
if (this.state !== stored) this.persist();
|
|
916
1033
|
return;
|
|
917
1034
|
}
|
|
918
1035
|
this.state = {
|
|
@@ -925,6 +1042,8 @@ var TrackerController = class {
|
|
|
925
1042
|
processedEventKeys: [...new Set([...stored.processedEventKeys, ...transient.processedEventKeys])].slice(-2048),
|
|
926
1043
|
updatedAt: Math.max(stored.updatedAt, transient.updatedAt)
|
|
927
1044
|
};
|
|
1045
|
+
this.state = relabelResolvedNodes(this.state, this.dependencies.nodeResolver, this.now());
|
|
1046
|
+
this.persist();
|
|
928
1047
|
} catch (error) {
|
|
929
1048
|
this.state = transient ?? createInitialState();
|
|
930
1049
|
this.report(error);
|
|
@@ -939,6 +1058,10 @@ var TrackerController = class {
|
|
|
939
1058
|
}
|
|
940
1059
|
}
|
|
941
1060
|
emit() {
|
|
1061
|
+
this.viewSnapshot = {
|
|
1062
|
+
state: this.state,
|
|
1063
|
+
runtime: { ...this.runtime }
|
|
1064
|
+
};
|
|
942
1065
|
for (const subscriber of this.subscribers) subscriber();
|
|
943
1066
|
}
|
|
944
1067
|
report(error) {
|
|
@@ -980,7 +1103,7 @@ function createFocusAdapter(dependencies) {
|
|
|
980
1103
|
} catch {}
|
|
981
1104
|
try {
|
|
982
1105
|
if (typeof dependencies.success === "function") {
|
|
983
|
-
dependencies.success("
|
|
1106
|
+
dependencies.success("检测到解密或削甲变化,请打开“解密·削甲记录”确认。");
|
|
984
1107
|
return "notice";
|
|
985
1108
|
}
|
|
986
1109
|
} catch {}
|
|
@@ -990,11 +1113,11 @@ function createFocusAdapter(dependencies) {
|
|
|
990
1113
|
|
|
991
1114
|
//#endregion
|
|
992
1115
|
//#region src/host/node-resolver.ts
|
|
993
|
-
function createFcdNodeResolver(getStore
|
|
1116
|
+
function createFcdNodeResolver(getStore) {
|
|
994
1117
|
return ({ mapArea, mapNo, apiNo }) => {
|
|
995
1118
|
const fallback = `#${apiNo}`;
|
|
996
1119
|
try {
|
|
997
|
-
const fcdMap = getStore
|
|
1120
|
+
const fcdMap = getStore("fcd.map");
|
|
998
1121
|
if (typeof fcdMap !== "object" || fcdMap == null) return fallback;
|
|
999
1122
|
const map = fcdMap[`${mapArea}-${mapNo}`];
|
|
1000
1123
|
if (typeof map !== "object" || map == null) return fallback;
|
|
@@ -1012,14 +1135,23 @@ function createFcdNodeResolver(getStore$1) {
|
|
|
1012
1135
|
//#endregion
|
|
1013
1136
|
//#region i18n/zh-CN.json
|
|
1014
1137
|
var zh_CN_default = {
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
title: "
|
|
1018
|
-
subtitle: "
|
|
1138
|
+
解密·削甲记录: "解密·削甲记录",
|
|
1139
|
+
"记录出击路线,并在解密或削甲生效后提醒确认": "记录出击路线,并在解密或削甲生效后提醒确认",
|
|
1140
|
+
title: "解密·削甲记录",
|
|
1141
|
+
subtitle: "自动记下路线、战斗和空袭;检测到解密或削甲生效后,请你确认刚完成的条件。数据只保存在本机。",
|
|
1142
|
+
running: "运行中,正在接收 Poi 战斗数据",
|
|
1143
|
+
stopped: "监听已停止",
|
|
1144
|
+
receivedThisRun: "本次加载收到响应",
|
|
1145
|
+
trackedThisRun: "本次加载已处理",
|
|
1146
|
+
recordedSorties: "累计出击",
|
|
1147
|
+
observations: "累计记录",
|
|
1148
|
+
lastResponse: "最近收到",
|
|
1149
|
+
lastPageSwitch: "最近自动切页请求",
|
|
1150
|
+
unresolvedNodes: "仍有 {count} 条旧记录暂时只能显示 #编号;FCD 点位数据就绪后会自动回填。",
|
|
1019
1151
|
activeMap: "当前海域",
|
|
1020
|
-
pendingReviews: "
|
|
1152
|
+
pendingReviews: "待确认记录",
|
|
1021
1153
|
pending: "待确认",
|
|
1022
|
-
noPending: "
|
|
1154
|
+
noPending: "暂无待确认记录。普通出击只在后台记录,不会打断你。",
|
|
1023
1155
|
unknownMap: "未知海域",
|
|
1024
1156
|
route: "本次路线",
|
|
1025
1157
|
incompleteRoute: "未捕获到完整出击:插件可能在出击中途加载,或仅收到了回港信号。",
|
|
@@ -1037,9 +1169,9 @@ var zh_CN_default = {
|
|
|
1037
1169
|
importNeedMap: "尚未识别当前海域,请先捕获一次地图出击。",
|
|
1038
1170
|
importSuccess: "已识别 {count} 条条件。",
|
|
1039
1171
|
noMatch: "未识别到受支持的条件格式。",
|
|
1040
|
-
completed: "
|
|
1041
|
-
lastTriggeredSortie: "
|
|
1042
|
-
triggerHistory: "
|
|
1172
|
+
completed: "按海域查看解密·削甲进度",
|
|
1173
|
+
lastTriggeredSortie: "上一次生效出击",
|
|
1174
|
+
triggerHistory: "每次检测到生效的出击",
|
|
1043
1175
|
noCompleted: "尚未导入攻略条件,也没有自由完成记录。",
|
|
1044
1176
|
observed: "观测",
|
|
1045
1177
|
confirmed: "已确认",
|
|
@@ -1097,9 +1229,29 @@ const styles = {
|
|
|
1097
1229
|
gap: 12,
|
|
1098
1230
|
alignItems: "center"
|
|
1099
1231
|
},
|
|
1100
|
-
muted: { color: "#738091" }
|
|
1232
|
+
muted: { color: "#738091" },
|
|
1233
|
+
statusGrid: {
|
|
1234
|
+
display: "flex",
|
|
1235
|
+
flexWrap: "wrap",
|
|
1236
|
+
gap: 8,
|
|
1237
|
+
alignItems: "center",
|
|
1238
|
+
marginTop: 10
|
|
1239
|
+
}
|
|
1101
1240
|
};
|
|
1102
1241
|
const t = (key) => zh_CN_default[key];
|
|
1242
|
+
function shortTime(value) {
|
|
1243
|
+
return value == null ? "—" : new Date(value).toLocaleTimeString();
|
|
1244
|
+
}
|
|
1245
|
+
function focusMethodLabel(method) {
|
|
1246
|
+
switch (method) {
|
|
1247
|
+
case "ipc": return "Poi IPC 已发送";
|
|
1248
|
+
case "legacy-ipc": return "兼容 IPC 已发送";
|
|
1249
|
+
case "dispatch": return "页签切换已派发";
|
|
1250
|
+
case "notice": return "已发送提醒";
|
|
1251
|
+
case "none": return "宿主未提供切页接口";
|
|
1252
|
+
default: return "尚未触发";
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1103
1255
|
function routeBadge(review, index) {
|
|
1104
1256
|
const visit = review.route[index];
|
|
1105
1257
|
const details = [visit.rank == null ? void 0 : `${visit.rank}胜`];
|
|
@@ -1219,12 +1371,14 @@ function PendingCard({ review, selected, setSelected, controller: controller$1 }
|
|
|
1219
1371
|
}
|
|
1220
1372
|
function createTrackerView(controller$1) {
|
|
1221
1373
|
return function GimmickTrackerView() {
|
|
1222
|
-
const state = (0, react.useSyncExternalStore)(controller$1.subscribe, controller$1.getSnapshot, controller$1.getSnapshot);
|
|
1374
|
+
const { state, runtime } = (0, react.useSyncExternalStore)(controller$1.subscribe, controller$1.getSnapshot, controller$1.getSnapshot);
|
|
1223
1375
|
const [guideText, setGuideText] = (0, react.useState)("");
|
|
1224
1376
|
const [message, setMessage] = (0, react.useState)("");
|
|
1225
1377
|
const [selected, setSelected] = (0, react.useState)({});
|
|
1226
1378
|
const pending$1 = state.reviews.filter((review) => review.status === "pending");
|
|
1227
1379
|
const currentMap = controller$1.currentMap();
|
|
1380
|
+
const sortieCount = (0, react.useMemo)(() => new Set(state.observations.map((item) => item.sortieId)).size, [state.observations]);
|
|
1381
|
+
const unresolvedCount = (0, react.useMemo)(() => state.observations.filter((item) => item.nodeLabel?.startsWith("#")).length, [state.observations]);
|
|
1228
1382
|
const mapIds = (0, react.useMemo)(() => [...new Set([
|
|
1229
1383
|
...state.guideConditions.map((item) => item.mapId),
|
|
1230
1384
|
...state.freeCompletions.map((item) => item.mapId),
|
|
@@ -1265,6 +1419,77 @@ function createTrackerView(controller$1) {
|
|
|
1265
1419
|
]
|
|
1266
1420
|
})]
|
|
1267
1421
|
}),
|
|
1422
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__blueprintjs_core.Callout, {
|
|
1423
|
+
style: styles.section,
|
|
1424
|
+
intent: runtime.started ? "success" : "warning",
|
|
1425
|
+
children: [
|
|
1426
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: runtime.started ? t("running") : t("stopped") }),
|
|
1427
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1428
|
+
style: styles.statusGrid,
|
|
1429
|
+
children: [
|
|
1430
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__blueprintjs_core.Tag, {
|
|
1431
|
+
minimal: true,
|
|
1432
|
+
children: [
|
|
1433
|
+
t("receivedThisRun"),
|
|
1434
|
+
": ",
|
|
1435
|
+
runtime.receivedCount
|
|
1436
|
+
]
|
|
1437
|
+
}),
|
|
1438
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__blueprintjs_core.Tag, {
|
|
1439
|
+
minimal: true,
|
|
1440
|
+
children: [
|
|
1441
|
+
t("trackedThisRun"),
|
|
1442
|
+
": ",
|
|
1443
|
+
runtime.trackedCount
|
|
1444
|
+
]
|
|
1445
|
+
}),
|
|
1446
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__blueprintjs_core.Tag, {
|
|
1447
|
+
minimal: true,
|
|
1448
|
+
children: [
|
|
1449
|
+
t("recordedSorties"),
|
|
1450
|
+
": ",
|
|
1451
|
+
sortieCount
|
|
1452
|
+
]
|
|
1453
|
+
}),
|
|
1454
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__blueprintjs_core.Tag, {
|
|
1455
|
+
minimal: true,
|
|
1456
|
+
children: [
|
|
1457
|
+
t("observations"),
|
|
1458
|
+
": ",
|
|
1459
|
+
state.observations.length
|
|
1460
|
+
]
|
|
1461
|
+
})
|
|
1462
|
+
]
|
|
1463
|
+
}),
|
|
1464
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1465
|
+
style: {
|
|
1466
|
+
...styles.muted,
|
|
1467
|
+
marginTop: 8
|
|
1468
|
+
},
|
|
1469
|
+
children: [
|
|
1470
|
+
t("lastResponse"),
|
|
1471
|
+
": ",
|
|
1472
|
+
shortTime(runtime.lastResponseAt),
|
|
1473
|
+
runtime.lastResponsePath == null ? "" : ` · ${runtime.lastResponsePath}`
|
|
1474
|
+
]
|
|
1475
|
+
}),
|
|
1476
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1477
|
+
style: styles.muted,
|
|
1478
|
+
children: [
|
|
1479
|
+
t("lastPageSwitch"),
|
|
1480
|
+
": ",
|
|
1481
|
+
shortTime(runtime.lastFocusAt),
|
|
1482
|
+
" · ",
|
|
1483
|
+
focusMethodLabel(runtime.lastFocusMethod)
|
|
1484
|
+
]
|
|
1485
|
+
})
|
|
1486
|
+
]
|
|
1487
|
+
}),
|
|
1488
|
+
unresolvedCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__blueprintjs_core.Callout, {
|
|
1489
|
+
style: styles.section,
|
|
1490
|
+
intent: "warning",
|
|
1491
|
+
children: t("unresolvedNodes").replace("{count}", String(unresolvedCount))
|
|
1492
|
+
}),
|
|
1268
1493
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
1269
1494
|
style: styles.section,
|
|
1270
1495
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { children: t("pendingReviews") }), pending$1.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__blueprintjs_core.Callout, { children: t("noPending") }) : pending$1.map((review) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PendingCard, {
|
|
@@ -1374,18 +1599,20 @@ function createTrackerView(controller$1) {
|
|
|
1374
1599
|
|
|
1375
1600
|
//#endregion
|
|
1376
1601
|
//#region index-src.ts
|
|
1602
|
+
const appDataPath = window.APPDATA_PATH ?? views_env.APPDATA_PATH;
|
|
1603
|
+
const readPoiStore = (path$1) => window.getStore?.(path$1);
|
|
1377
1604
|
const controller = new TrackerController({
|
|
1378
|
-
storage: new FileTrackerStorage(
|
|
1605
|
+
storage: new FileTrackerStorage(appDataPath),
|
|
1379
1606
|
focus: createFocusAdapter({
|
|
1380
1607
|
primaryIpc: views_env.ipc,
|
|
1381
1608
|
legacyIpc: window.ipc,
|
|
1382
|
-
dispatch:
|
|
1383
|
-
getStore:
|
|
1609
|
+
dispatch: window.dispatch,
|
|
1610
|
+
getStore: readPoiStore,
|
|
1384
1611
|
success: window.success
|
|
1385
1612
|
}),
|
|
1386
1613
|
pluginId: PLUGIN_ID,
|
|
1387
|
-
nodeResolver: createFcdNodeResolver(
|
|
1388
|
-
getMemberId: () => (
|
|
1614
|
+
nodeResolver: createFcdNodeResolver(readPoiStore),
|
|
1615
|
+
getMemberId: () => readPoiStore("info.basic.api_member_id"),
|
|
1389
1616
|
onError: (error) => console.error(`[${PLUGIN_ID}]`, error)
|
|
1390
1617
|
});
|
|
1391
1618
|
const reactClass = createTrackerView(controller);
|
package/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["AIR_TEXT_TO_STATE: Record<string, AirState>","parsed: ParsedGuideCondition[]","match: RegExpExecArray | null","RANK_SCORE: Record<VictoryRank, number>","AIR_LABEL: Record<AirState, string>","parsed: unknown","path","visit","battle: BattleReference","condition","condition: GuideCondition","completion: FreeCompletion","appDataPath: string","path","dependencies: TrackerControllerDependencies","ipc","getStore","route","styles: Record<string, React.CSSProperties>","zhCN","Tag","Card","Elevation","Callout","HTMLSelect","Button","controller","pending","TextArea","HTMLTable","APPDATA_PATH","ipc","path"],"sources":["src/core/guide-parser.ts","src/core/conditions.ts","src/core/events.ts","src/core/types.ts","src/core/state-machine.ts","src/storage/index.ts","src/controller.ts","src/host/focus.ts","src/host/node-resolver.ts","i18n/zh-CN.json","src/ui/index.tsx","index-src.ts"],"sourcesContent":["import type { AirState, ConditionKind, VictoryRank } from './types'\n\nexport interface ParsedGuideCondition {\n kind: ConditionKind\n rawText: string\n nodeLabel?: string\n requiredRank?: VictoryRank\n requiredAirState?: AirState\n requiredCount: number\n}\n\nconst AIR_TEXT_TO_STATE: Record<string, AirState> = {\n 空确: 1,\n 空優: 2,\n 空优: 2,\n 空均: 3,\n 空劣: 4,\n}\n\nconst TOKEN_PATTERN =\n /([A-Za-z]\\d*)\\s*点\\s*(到达|到達)\\s*(\\d+)\\s*次|([A-Za-z]\\d*)\\s*点\\s*(SS|S|A|B|C|D|E)\\s*胜\\s*(\\d+)\\s*次|([A-Za-z]\\d*)\\s*点\\s*(空确|空優|空优|空均|空劣)\\s*(\\d+)\\s*次|(守家|基地防空)\\s*(空确|空優|空优|空均|空劣)\\s*(\\d+)\\s*次/gi\n\nexport function normalizeNodeLabel(value: string): string {\n return value.trim().toUpperCase()\n}\n\nexport function parseGuideText(text: string): ParsedGuideCondition[] {\n const normalized = text.replace(/[,;]/g, (value) => (value === ',' ? ',' : ';'))\n const parsed: ParsedGuideCondition[] = []\n let match: RegExpExecArray | null\n TOKEN_PATTERN.lastIndex = 0\n\n while ((match = TOKEN_PATTERN.exec(normalized)) !== null) {\n const rawText = match[0].replace(/\\s+/g, '')\n if (match[1] != null) {\n parsed.push({\n kind: 'node-arrival',\n nodeLabel: normalizeNodeLabel(match[1]),\n requiredCount: Number(match[3]),\n rawText,\n })\n continue\n }\n if (match[4] != null) {\n parsed.push({\n kind: 'victory-rank',\n nodeLabel: normalizeNodeLabel(match[4]),\n requiredRank: match[5].toUpperCase() as VictoryRank,\n requiredCount: Number(match[6]),\n rawText,\n })\n continue\n }\n if (match[7] != null) {\n parsed.push({\n kind: 'node-air-state',\n nodeLabel: normalizeNodeLabel(match[7]),\n requiredAirState: AIR_TEXT_TO_STATE[match[8]],\n requiredCount: Number(match[9]),\n rawText,\n })\n continue\n }\n parsed.push({\n kind: 'base-air-defense',\n requiredAirState: AIR_TEXT_TO_STATE[match[11]],\n requiredCount: Number(match[12]),\n rawText,\n })\n }\n\n return parsed.filter((item) => Number.isInteger(item.requiredCount) && item.requiredCount > 0)\n}\n","import type { ParsedGuideCondition } from './guide-parser'\nimport { normalizeNodeLabel } from './guide-parser'\nimport type {\n AirState,\n GuideCondition,\n MapRef,\n Observation,\n ReviewCandidate,\n TrackerState,\n VictoryRank,\n} from './types'\n\nconst RANK_SCORE: Record<VictoryRank, number> = {\n E: 0,\n D: 1,\n C: 2,\n B: 3,\n A: 4,\n S: 5,\n SS: 6,\n}\n\nconst AIR_LABEL: Record<AirState, string> = {\n 1: '空确',\n 2: '空优',\n 3: '空均',\n 4: '空劣',\n}\n\nexport function airStateLabel(state: AirState): string {\n return AIR_LABEL[state]\n}\n\nexport function meetsRank(actual: VictoryRank, required: VictoryRank): boolean {\n return RANK_SCORE[actual] >= RANK_SCORE[required]\n}\n\nexport function meetsAirState(actual: AirState, required: AirState): boolean {\n return actual <= required\n}\n\nexport function conditionSignature(\n map: Pick<MapRef, 'mapId'>,\n item: Pick<\n ParsedGuideCondition,\n 'kind' | 'nodeLabel' | 'requiredRank' | 'requiredAirState'\n >,\n): string {\n return [\n map.mapId,\n item.kind,\n item.nodeLabel == null ? '' : normalizeNodeLabel(item.nodeLabel),\n item.requiredRank ?? '',\n item.requiredAirState ?? '',\n ].join(':')\n}\n\nexport function observationMatches(condition: GuideCondition, observation: Observation): boolean {\n if (condition.mapId !== observation.mapId || condition.kind !== observation.kind) return false\n if (\n condition.nodeLabel != null &&\n normalizeNodeLabel(condition.nodeLabel) !== normalizeNodeLabel(observation.nodeLabel ?? '')\n ) {\n return false\n }\n if (condition.kind === 'victory-rank') {\n return (\n observation.kind === 'victory-rank' &&\n condition.requiredRank != null &&\n meetsRank(observation.rank, condition.requiredRank)\n )\n }\n if (condition.kind === 'node-air-state' || condition.kind === 'base-air-defense') {\n return (\n (observation.kind === 'node-air-state' || observation.kind === 'base-air-defense') &&\n condition.requiredAirState != null &&\n meetsAirState(observation.airState, condition.requiredAirState)\n )\n }\n return observation.kind === 'node-arrival'\n}\n\nexport function guideConditionLabel(condition: GuideCondition): string {\n const node = condition.nodeLabel == null ? '' : `${condition.nodeLabel}点`\n switch (condition.kind) {\n case 'node-arrival':\n return `${node}到达 ${condition.requiredCount}次`\n case 'victory-rank':\n return `${node}${condition.requiredRank}胜 ${condition.requiredCount}次`\n case 'node-air-state':\n return `${node}${airStateLabel(condition.requiredAirState!)} ${condition.requiredCount}次`\n case 'base-air-defense':\n return `守家${airStateLabel(condition.requiredAirState!)} ${condition.requiredCount}次`\n }\n}\n\nexport function observationLabel(observation: Observation): string {\n switch (observation.kind) {\n case 'node-arrival':\n return `${observation.nodeLabel}点到达`\n case 'victory-rank':\n return `${observation.nodeLabel}点${observation.rank}胜`\n case 'node-air-state':\n return `${observation.nodeLabel}点${airStateLabel(observation.airState)}`\n case 'base-air-defense':\n return `守家${airStateLabel(observation.airState)}`\n }\n}\n\nexport function buildReviewCandidates(\n state: TrackerState,\n mapId: number,\n observationIds: string[],\n): ReviewCandidate[] {\n const evidence = state.observations.filter((item) => observationIds.includes(item.id))\n const guides = state.guideConditions.filter(\n (condition) => condition.mapId === mapId && !condition.confirmed,\n )\n if (guides.length > 0) {\n return guides\n .map((condition) => ({\n id: `guide:${condition.id}`,\n mode: 'guide' as const,\n kind: condition.kind,\n label: guideConditionLabel(condition),\n conditionId: condition.id,\n matchedInSortie: evidence.some((item) => observationMatches(condition, item)),\n observedCount: condition.observedCount,\n requiredCount: condition.requiredCount,\n }))\n .sort((left, right) => Number(right.matchedInSortie) - Number(left.matchedInSortie))\n }\n\n return evidence.map((observation) => ({\n id: `free:${observation.id}`,\n mode: 'free' as const,\n kind: observation.kind,\n label: observationLabel(observation),\n observationId: observation.id,\n matchedInSortie: true,\n }))\n}\n","import type { AirState, GameResponseDetail, MapRef, VictoryRank } from './types'\n\nexport const MAP_PATHS = new Set([\n '/kcsapi/api_req_map/start',\n '/kcsapi/api_req_map/next',\n '/kcsapi/api_req_map/air_raid',\n])\n\nconst BATTLE_SUFFIXES = new Set([\n 'battle',\n 'airbattle',\n 'ld_airbattle',\n 'night_to_day',\n 'sp_midnight',\n 'midnight',\n 'midnight_battle',\n 'battle_water',\n 'each_battle',\n 'each_battle_water',\n 'ec_battle',\n 'ec_midnight_battle',\n 'ec_night_to_day',\n])\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nexport function asRecord(value: unknown): Record<string, unknown> | undefined {\n if (isRecord(value)) return value\n if (typeof value !== 'string') return undefined\n try {\n const parsed: unknown = JSON.parse(value)\n return isRecord(parsed) ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\nexport function asNumber(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim() !== '') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n }\n return undefined\n}\n\nexport function isRelevantPath(path: string): boolean {\n return (\n path === '/kcsapi/api_port/port' ||\n MAP_PATHS.has(path) ||\n isBattlePath(path) ||\n isBattleResultPath(path)\n )\n}\n\nexport function isBattleResultPath(path: string): boolean {\n return path.startsWith('/kcsapi/') && path.endsWith('/battleresult')\n}\n\nexport function isBattlePath(path: string): boolean {\n if (!/^\\/kcsapi\\/api_req_(sortie|combined_battle|battle_midnight)\\//.test(path)) return false\n return BATTLE_SUFFIXES.has(path.slice(path.lastIndexOf('/') + 1))\n}\n\nexport function eventKey(detail: GameResponseDetail): string {\n return `${detail.path}|${detail.time}`\n}\n\nexport function extractMapRef(\n detail: GameResponseDetail,\n fallback?: Pick<MapRef, 'mapArea' | 'mapNo'>,\n): MapRef {\n const mapArea =\n asNumber(detail.body.api_maparea_id) ??\n asNumber(detail.postBody?.api_maparea_id) ??\n fallback?.mapArea ??\n 0\n const mapNo =\n asNumber(detail.body.api_mapinfo_no) ??\n asNumber(detail.postBody?.api_mapinfo_no) ??\n fallback?.mapNo ??\n 0\n return { mapArea, mapNo, mapId: mapArea * 10 + mapNo }\n}\n\nexport function extractNodeNo(body: Record<string, unknown>): number | undefined {\n return asNumber(body.api_no)\n}\n\nexport function extractBattleAirState(body: Record<string, unknown>): AirState | undefined {\n const kouku = asRecord(body.api_kouku)\n const stage1 = asRecord(kouku?.api_stage1)\n const value = asNumber(stage1?.api_disp_seiku)\n return value != null && value >= 1 && value <= 4 ? (value as AirState) : undefined\n}\n\nexport function extractVictoryRank(body: Record<string, unknown>): VictoryRank | undefined {\n const value = typeof body.api_win_rank === 'string' ? body.api_win_rank.toUpperCase() : ''\n return ['SS', 'S', 'A', 'B', 'C', 'D', 'E'].includes(value)\n ? (value as VictoryRank)\n : undefined\n}\n\nexport function extractDestructionBattles(body: Record<string, unknown>): Record<string, unknown>[] {\n const raw = body.api_destruction_battle\n const values = Array.isArray(raw) ? raw : raw == null ? [] : [raw]\n return values.map(asRecord).filter((item): item is Record<string, unknown> => item != null)\n}\n\nexport function extractDefenseAirState(entry: Record<string, unknown>): AirState | undefined {\n const attack = asRecord(entry.api_air_base_attack)\n const stage1 = asRecord(attack?.api_stage1)\n const value = asNumber(stage1?.api_disp_seiku)\n return value != null && value >= 1 && value <= 4 ? (value as AirState) : undefined\n}\n\nexport function hasPortSignal(detail: GameResponseDetail): boolean {\n const eventObject = asRecord(detail.body.api_event_object)\n return detail.path === '/kcsapi/api_port/port' && asNumber(eventObject?.api_m_flag2) === 1\n}\n\nexport function hasBattleResultSignal(detail: GameResponseDetail): boolean {\n return isBattleResultPath(detail.path) && asNumber(detail.body.api_m2) === 1\n}\n\nexport function hasDefenseSignal(entry: Record<string, unknown>): boolean {\n return asNumber(entry.api_m2) === 1\n}\n\nexport function extractPortMemberId(detail: GameResponseDetail): unknown {\n if (detail.path !== '/kcsapi/api_port/port') return undefined\n return asRecord(detail.body.api_basic)?.api_member_id\n}\n","export const PLUGIN_ID = 'poi-plugin-gimmick-tracker'\nexport const SCHEMA_VERSION = 1 as const\n\nexport type AirState = 1 | 2 | 3 | 4\nexport type VictoryRank = 'SS' | 'S' | 'A' | 'B' | 'C' | 'D' | 'E'\nexport type ConditionKind =\n | 'node-arrival'\n | 'victory-rank'\n | 'node-air-state'\n | 'base-air-defense'\n\nexport interface MapRef {\n mapArea: number\n mapNo: number\n mapId: number\n}\n\nexport interface BattleReference {\n timestamp: number\n path: string\n kind?: 'battle' | 'fleet-air-raid' | 'base-air-defense'\n}\n\nexport interface BaseDefenseEvent {\n timestamp: number\n path: string\n airState?: AirState\n}\n\nexport type TimelineEventKind = 'node' | 'fleet-air-raid' | 'base-air-defense'\n\nexport interface SortieTimelineEvent {\n id: string\n kind: TimelineEventKind\n occurredAt: number\n sourcePath: string\n nodeVisitId?: string\n nodeNo?: number\n nodeLabel?: string\n airState?: AirState\n}\n\nexport interface NodeVisit extends MapRef {\n id: string\n nodeNo: number\n nodeLabel: string\n arrivedAt: number\n sourcePath: string\n battle?: BattleReference\n battleEvents?: BattleReference[]\n baseDefenseEvents?: BaseDefenseEvent[]\n rank?: VictoryRank\n airState?: AirState\n}\n\ninterface ObservationBase extends MapRef {\n id: string\n kind: ConditionKind\n observedAt: number\n sourcePath: string\n sortieId: string\n nodeNo?: number\n nodeLabel?: string\n battle?: BattleReference\n}\n\nexport interface NodeArrivalObservation extends ObservationBase {\n kind: 'node-arrival'\n nodeNo: number\n nodeLabel: string\n}\n\nexport interface VictoryObservation extends ObservationBase {\n kind: 'victory-rank'\n nodeNo: number\n nodeLabel: string\n rank: VictoryRank\n}\n\nexport interface NodeAirObservation extends ObservationBase {\n kind: 'node-air-state'\n nodeNo: number\n nodeLabel: string\n airState: AirState\n}\n\nexport interface BaseAirDefenseObservation extends ObservationBase {\n kind: 'base-air-defense'\n airState: AirState\n}\n\nexport type Observation =\n | NodeArrivalObservation\n | VictoryObservation\n | NodeAirObservation\n | BaseAirDefenseObservation\n\nexport interface GuideCondition extends MapRef {\n id: string\n kind: ConditionKind\n rawText: string\n nodeLabel?: string\n requiredRank?: VictoryRank\n requiredAirState?: AirState\n requiredCount: number\n observedCount: number\n confirmed: boolean\n confirmedAt?: number\n confirmedByReviewId?: string\n createdAt: number\n}\n\nexport interface ReviewCandidate {\n id: string\n mode: 'guide' | 'free'\n kind: ConditionKind\n label: string\n conditionId?: string\n observationId?: string\n matchedInSortie: boolean\n observedCount?: number\n requiredCount?: number\n}\n\nexport type TriggerSource = 'port' | 'battleresult' | 'air-defense'\nexport type ReviewStatus = 'pending' | 'confirmed' | 'ignored'\n\nexport interface PendingReview extends MapRef {\n id: string\n sortieId: string\n startedAt: number\n detectedAt: number\n triggerSources: TriggerSource[]\n sealed: boolean\n routeComplete: boolean\n route: NodeVisit[]\n timeline: SortieTimelineEvent[]\n observationIds: string[]\n candidates: ReviewCandidate[]\n status: ReviewStatus\n selectedCandidateIds?: string[]\n resolvedAt?: number\n}\n\nexport interface FreeCompletion extends MapRef {\n id: string\n kind: ConditionKind\n label: string\n reviewId: string\n observationId: string\n confirmedAt: number\n}\n\nexport interface ActiveSortie extends MapRef {\n id: string\n startedAt: number\n captureComplete: boolean\n route: NodeVisit[]\n timeline: SortieTimelineEvent[]\n observationIds: string[]\n lastBattle?: BattleReference\n}\n\nexport interface TrackerState {\n schemaVersion: typeof SCHEMA_VERSION\n activeSortie?: ActiveSortie\n observations: Observation[]\n guideConditions: GuideCondition[]\n reviews: PendingReview[]\n freeCompletions: FreeCompletion[]\n processedEventKeys: string[]\n updatedAt: number\n}\n\nexport interface GameResponseDetail {\n method?: string\n path: string\n body: Record<string, unknown>\n postBody?: Record<string, unknown>\n time: number\n}\n\nexport type NodeResolver = (input: {\n mapArea: number\n mapNo: number\n apiNo: number\n}) => string\n\nexport function createInitialState(now = 0): TrackerState {\n return {\n schemaVersion: SCHEMA_VERSION,\n observations: [],\n guideConditions: [],\n reviews: [],\n freeCompletions: [],\n processedEventKeys: [],\n updatedAt: now,\n }\n}\n\nexport function cloneState(state: TrackerState): TrackerState {\n return JSON.parse(JSON.stringify(state)) as TrackerState\n}\n\nexport function mapLabel(map: Pick<MapRef, 'mapArea' | 'mapNo' | 'mapId'>): string {\n return map.mapArea > 0 && map.mapNo > 0 ? `${map.mapArea}-${map.mapNo}` : `地图 #${map.mapId}`\n}\n","import {\n buildReviewCandidates,\n conditionSignature,\n observationMatches,\n} from './conditions'\nimport {\n eventKey,\n extractBattleAirState,\n extractDefenseAirState,\n extractDestructionBattles,\n extractMapRef,\n extractNodeNo,\n extractVictoryRank,\n hasBattleResultSignal,\n hasDefenseSignal,\n hasPortSignal,\n isBattlePath,\n isBattleResultPath,\n isRelevantPath,\n MAP_PATHS,\n} from './events'\nimport { parseGuideText } from './guide-parser'\nimport type {\n ActiveSortie,\n BaseAirDefenseObservation,\n BattleReference,\n FreeCompletion,\n GameResponseDetail,\n GuideCondition,\n MapRef,\n NodeAirObservation,\n NodeArrivalObservation,\n NodeResolver,\n Observation,\n PendingReview,\n TrackerState,\n TriggerSource,\n VictoryObservation,\n} from './types'\nimport { cloneState } from './types'\n\nexport interface TransitionResult {\n state: TrackerState\n changed: boolean\n shouldFocus: boolean\n}\n\nexport interface ImportGuideResult {\n state: TrackerState\n importedCount: number\n}\n\nconst MAX_PROCESSED_EVENT_KEYS = 2048\n\nfunction safeNodeLabel(resolver: NodeResolver, map: MapRef, apiNo: number): string {\n try {\n const value = resolver({ mapArea: map.mapArea, mapNo: map.mapNo, apiNo }).trim()\n return value === '' ? `#${apiNo}` : value\n } catch {\n return `#${apiNo}`\n }\n}\n\nfunction createSortie(\n detail: GameResponseDetail,\n map: MapRef,\n captureComplete: boolean,\n): ActiveSortie {\n return {\n ...map,\n id: `${captureComplete ? 'sortie' : 'partial'}:${map.mapId}:${detail.time}`,\n startedAt: detail.time,\n captureComplete,\n route: [],\n timeline: [],\n observationIds: [],\n }\n}\n\nfunction appendObservation(\n state: TrackerState,\n sortie: ActiveSortie,\n observation: Observation,\n): boolean {\n if (state.observations.some((item) => item.id === observation.id)) return false\n state.observations.push(observation)\n sortie.observationIds.push(observation.id)\n for (const condition of state.guideConditions) {\n if (observationMatches(condition, observation)) condition.observedCount += 1\n }\n return true\n}\n\nfunction currentNode(sortie: ActiveSortie) {\n return sortie.route.at(-1)\n}\n\nfunction addNodeVisit(\n state: TrackerState,\n sortie: ActiveSortie,\n detail: GameResponseDetail,\n nodeResolver: NodeResolver,\n): void {\n const nodeNo = extractNodeNo(detail.body)\n if (nodeNo == null) return\n const id = `${eventKey(detail)}:node`\n if (sortie.route.some((visit) => visit.id === id)) return\n const nodeLabel = safeNodeLabel(nodeResolver, sortie, nodeNo)\n const visit = {\n id,\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n nodeNo,\n nodeLabel,\n arrivedAt: detail.time,\n sourcePath: detail.path,\n }\n sortie.route.push(visit)\n sortie.timeline.push({\n id: `${id}:timeline`,\n kind: 'node',\n occurredAt: detail.time,\n sourcePath: detail.path,\n nodeVisitId: id,\n nodeNo,\n nodeLabel,\n })\n const observation: NodeArrivalObservation = {\n id: `${id}:arrival`,\n kind: 'node-arrival',\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n nodeNo,\n nodeLabel,\n observedAt: detail.time,\n sourcePath: detail.path,\n sortieId: sortie.id,\n }\n appendObservation(state, sortie, observation)\n}\n\nfunction addBattle(\n state: TrackerState,\n sortie: ActiveSortie,\n detail: GameResponseDetail,\n): void {\n const battle: BattleReference = {\n timestamp: detail.time,\n path: detail.path,\n kind: detail.path.endsWith('/ld_airbattle') ? 'fleet-air-raid' : 'battle',\n }\n sortie.lastBattle = battle\n const node = currentNode(sortie)\n if (node != null) {\n node.battle = battle\n ;(node.battleEvents ??= []).push(battle)\n }\n const airState = extractBattleAirState(detail.body)\n if (battle.kind === 'fleet-air-raid') {\n sortie.timeline.push({\n id: `${eventKey(detail)}:fleet-air-raid`,\n kind: 'fleet-air-raid',\n occurredAt: detail.time,\n sourcePath: detail.path,\n nodeVisitId: node?.id,\n nodeNo: node?.nodeNo,\n nodeLabel: node?.nodeLabel,\n airState,\n })\n }\n if (node == null || airState == null) return\n node.airState = airState\n const observation: NodeAirObservation = {\n id: `${eventKey(detail)}:node-air`,\n kind: 'node-air-state',\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n nodeNo: node.nodeNo,\n nodeLabel: node.nodeLabel,\n airState,\n observedAt: detail.time,\n sourcePath: detail.path,\n sortieId: sortie.id,\n battle,\n }\n appendObservation(state, sortie, observation)\n}\n\nfunction addBattleResult(\n state: TrackerState,\n sortie: ActiveSortie,\n detail: GameResponseDetail,\n): void {\n const rank = extractVictoryRank(detail.body)\n const node = currentNode(sortie)\n if (rank == null || node == null) return\n node.rank = rank\n const observation: VictoryObservation = {\n id: `${eventKey(detail)}:victory`,\n kind: 'victory-rank',\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n nodeNo: node.nodeNo,\n nodeLabel: node.nodeLabel,\n rank,\n observedAt: detail.time,\n sourcePath: detail.path,\n sortieId: sortie.id,\n battle: sortie.lastBattle,\n }\n appendObservation(state, sortie, observation)\n}\n\nfunction addDefenseBattles(\n state: TrackerState,\n sortie: ActiveSortie,\n detail: GameResponseDetail,\n): { signal: boolean } {\n const entries = extractDestructionBattles(detail.body)\n let signal = false\n entries.forEach((entry, index) => {\n signal ||= hasDefenseSignal(entry)\n const airState = extractDefenseAirState(entry)\n const node = currentNode(sortie)\n sortie.timeline.push({\n id: `${eventKey(detail)}:defense:${index}:timeline`,\n kind: 'base-air-defense',\n occurredAt: detail.time,\n sourcePath: detail.path,\n nodeVisitId: node?.id,\n nodeNo: node?.nodeNo,\n nodeLabel: node?.nodeLabel,\n airState,\n })\n if (node != null) {\n ;(node.baseDefenseEvents ??= []).push({\n timestamp: detail.time,\n path: detail.path,\n airState,\n })\n }\n if (airState == null) return\n const observation: BaseAirDefenseObservation = {\n id: `${eventKey(detail)}:defense:${index}`,\n kind: 'base-air-defense',\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n airState,\n observedAt: detail.time,\n sourcePath: detail.path,\n sortieId: sortie.id,\n battle: { timestamp: detail.time, path: detail.path, kind: 'base-air-defense' },\n }\n appendObservation(state, sortie, observation)\n })\n return { signal }\n}\n\nfunction reviewFromSortie(\n state: TrackerState,\n sortie: ActiveSortie,\n source: TriggerSource,\n detectedAt: number,\n sealed: boolean,\n): PendingReview {\n return {\n id: `review:${sortie.id}`,\n sortieId: sortie.id,\n startedAt: sortie.startedAt,\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n detectedAt,\n triggerSources: [source],\n sealed,\n routeComplete: sortie.captureComplete,\n route: cloneState({\n schemaVersion: 1,\n observations: [],\n guideConditions: [],\n reviews: [],\n freeCompletions: [],\n processedEventKeys: [],\n updatedAt: 0,\n activeSortie: sortie,\n }).activeSortie!.route,\n timeline: JSON.parse(JSON.stringify(sortie.timeline)) as PendingReview['timeline'],\n observationIds: [...sortie.observationIds],\n candidates: buildReviewCandidates(state, sortie.mapId, sortie.observationIds),\n status: 'pending',\n }\n}\n\nfunction orphanReview(state: TrackerState, source: TriggerSource, detectedAt: number): PendingReview {\n const sortieId = `orphan:${detectedAt}`\n return {\n id: `review:${sortieId}`,\n sortieId,\n startedAt: detectedAt,\n mapArea: 0,\n mapNo: 0,\n mapId: 0,\n detectedAt,\n triggerSources: [source],\n sealed: source === 'port',\n routeComplete: false,\n route: [],\n timeline: [],\n observationIds: [],\n candidates: buildReviewCandidates(state, 0, []),\n status: 'pending',\n }\n}\n\nfunction triggerReview(\n state: TrackerState,\n sortie: ActiveSortie | undefined,\n source: TriggerSource,\n detectedAt: number,\n sealed: boolean,\n): boolean {\n if (sortie == null && source === 'port') {\n const recentOrphan = [...state.reviews]\n .reverse()\n .find(\n (item) =>\n item.status === 'pending' &&\n item.sortieId.startsWith('orphan:') &&\n !item.sealed &&\n item.triggerSources.includes('battleresult') &&\n !item.triggerSources.includes('port') &&\n detectedAt - item.detectedAt <= 10 * 60 * 1000,\n )\n if (recentOrphan != null) {\n if (!recentOrphan.triggerSources.includes(source)) recentOrphan.triggerSources.push(source)\n recentOrphan.sealed ||= sealed || source === 'port'\n return false\n }\n }\n const candidate =\n sortie == null\n ? orphanReview(state, source, detectedAt)\n : reviewFromSortie(state, sortie, source, detectedAt, sealed)\n const existing = state.reviews.find((item) => item.id === candidate.id)\n if (existing == null) {\n state.reviews.push(candidate)\n return true\n }\n if (!existing.triggerSources.includes(source)) existing.triggerSources.push(source)\n existing.sealed ||= sealed\n if (sortie != null) {\n existing.routeComplete = sortie.captureComplete\n existing.route = candidate.route\n existing.timeline = candidate.timeline\n existing.observationIds = candidate.observationIds\n if (existing.status === 'pending') existing.candidates = candidate.candidates\n }\n return false\n}\n\nfunction syncReviewForActiveSortie(state: TrackerState, sealed = false): void {\n const sortie = state.activeSortie\n if (sortie == null) return\n const review = state.reviews.find((item) => item.sortieId === sortie.id)\n if (review == null) return\n review.sealed ||= sealed\n review.routeComplete = sortie.captureComplete\n review.route = JSON.parse(JSON.stringify(sortie.route)) as PendingReview['route']\n review.timeline = JSON.parse(JSON.stringify(sortie.timeline)) as PendingReview['timeline']\n review.observationIds = [...sortie.observationIds]\n if (review.status === 'pending') {\n review.candidates = buildReviewCandidates(state, sortie.mapId, sortie.observationIds)\n }\n}\n\nfunction handleMapEvent(\n state: TrackerState,\n detail: GameResponseDetail,\n nodeResolver: NodeResolver,\n): boolean {\n const isStart = detail.path === '/kcsapi/api_req_map/start'\n if (isStart) {\n const map = extractMapRef(detail)\n state.activeSortie = createSortie(detail, map, true)\n } else if (state.activeSortie == null) {\n const map = extractMapRef(detail)\n state.activeSortie = createSortie(detail, map, false)\n } else {\n const map = extractMapRef(detail, state.activeSortie)\n Object.assign(state.activeSortie, map)\n }\n\n const sortie = state.activeSortie\n if (detail.path !== '/kcsapi/api_req_map/air_raid') {\n addNodeVisit(state, sortie, detail, nodeResolver)\n }\n const defense = addDefenseBattles(state, sortie, detail)\n let shouldFocus = false\n if (defense.signal) {\n shouldFocus = triggerReview(state, sortie, 'air-defense', detail.time, false)\n }\n syncReviewForActiveSortie(state)\n return shouldFocus\n}\n\nexport function applyGameResponse(\n previous: TrackerState,\n detail: GameResponseDetail,\n nodeResolver: NodeResolver,\n): TransitionResult {\n if (!isRelevantPath(detail.path)) {\n return { state: previous, changed: false, shouldFocus: false }\n }\n const key = eventKey(detail)\n if (previous.processedEventKeys.includes(key)) {\n return { state: previous, changed: false, shouldFocus: false }\n }\n\n const state = cloneState(previous)\n state.processedEventKeys.push(key)\n if (state.processedEventKeys.length > MAX_PROCESSED_EVENT_KEYS) {\n state.processedEventKeys.splice(0, state.processedEventKeys.length - MAX_PROCESSED_EVENT_KEYS)\n }\n let shouldFocus = false\n\n if (MAP_PATHS.has(detail.path)) {\n shouldFocus = handleMapEvent(state, detail, nodeResolver)\n } else if (isBattleResultPath(detail.path)) {\n if (state.activeSortie != null) addBattleResult(state, state.activeSortie, detail)\n if (hasBattleResultSignal(detail)) {\n shouldFocus = triggerReview(\n state,\n state.activeSortie,\n 'battleresult',\n detail.time,\n false,\n )\n }\n syncReviewForActiveSortie(state)\n } else if (isBattlePath(detail.path)) {\n if (state.activeSortie != null) {\n addBattle(state, state.activeSortie, detail)\n syncReviewForActiveSortie(state)\n }\n } else if (detail.path === '/kcsapi/api_port/port') {\n if (hasPortSignal(detail)) {\n shouldFocus = triggerReview(state, state.activeSortie, 'port', detail.time, true)\n }\n syncReviewForActiveSortie(state, true)\n state.activeSortie = undefined\n }\n\n state.updatedAt = detail.time\n return { state, changed: true, shouldFocus }\n}\n\nfunction refreshPendingCandidates(state: TrackerState): void {\n for (const review of state.reviews) {\n if (review.status !== 'pending') continue\n review.candidates = buildReviewCandidates(state, review.mapId, review.observationIds)\n }\n}\n\nexport function importGuideConditions(\n previous: TrackerState,\n text: string,\n map: MapRef,\n now: number,\n): ImportGuideResult {\n const parsed = parseGuideText(text)\n if (parsed.length === 0) return { state: previous, importedCount: 0 }\n const state = cloneState(previous)\n const unique = new Map<string, (typeof parsed)[number]>()\n for (const item of parsed) unique.set(conditionSignature(map, item), item)\n\n for (const [signature, item] of unique) {\n const id = `guide:${signature}`\n const existing = state.guideConditions.find((condition) => condition.id === id)\n if (existing != null) {\n existing.rawText = item.rawText\n existing.requiredCount = item.requiredCount\n existing.observedCount = state.observations.filter((observation) =>\n observationMatches(existing, observation),\n ).length\n continue\n }\n const condition: GuideCondition = {\n ...map,\n id,\n kind: item.kind,\n rawText: item.rawText,\n nodeLabel: item.nodeLabel,\n requiredRank: item.requiredRank,\n requiredAirState: item.requiredAirState,\n requiredCount: item.requiredCount,\n observedCount: 0,\n confirmed: false,\n createdAt: now,\n }\n condition.observedCount = state.observations.filter((observation) =>\n observationMatches(condition, observation),\n ).length\n state.guideConditions.push(condition)\n }\n refreshPendingCandidates(state)\n state.updatedAt = now\n return { state, importedCount: unique.size }\n}\n\nexport function confirmReview(\n previous: TrackerState,\n reviewId: string,\n candidateIds: string[],\n now: number,\n): TrackerState {\n if (candidateIds.length === 0) return previous\n const state = cloneState(previous)\n const review = state.reviews.find((item) => item.id === reviewId)\n if (review == null || review.status !== 'pending') return previous\n const selected = review.candidates.filter((candidate) => candidateIds.includes(candidate.id))\n if (selected.length === 0) return previous\n\n const mapHasGuide = state.guideConditions.some((condition) => condition.mapId === review.mapId)\n for (const candidate of selected) {\n if (candidate.mode === 'guide' && candidate.conditionId != null) {\n const condition = state.guideConditions.find((item) => item.id === candidate.conditionId)\n if (condition != null && !condition.confirmed) {\n condition.confirmed = true\n condition.confirmedAt = now\n condition.confirmedByReviewId = review.id\n }\n continue\n }\n if (mapHasGuide || candidate.observationId == null) continue\n const observation = state.observations.find((item) => item.id === candidate.observationId)\n if (observation == null) continue\n const completion: FreeCompletion = {\n mapArea: review.mapArea,\n mapNo: review.mapNo,\n mapId: review.mapId,\n id: `free-completion:${review.id}:${candidate.observationId}`,\n kind: candidate.kind,\n label: candidate.label,\n reviewId: review.id,\n observationId: candidate.observationId,\n confirmedAt: now,\n }\n if (!state.freeCompletions.some((item) => item.id === completion.id)) {\n state.freeCompletions.push(completion)\n }\n }\n review.status = 'confirmed'\n review.selectedCandidateIds = selected.map((candidate) => candidate.id)\n review.resolvedAt = now\n refreshPendingCandidates(state)\n state.updatedAt = now\n return state\n}\n\nexport function ignoreReview(previous: TrackerState, reviewId: string, now: number): TrackerState {\n const state = cloneState(previous)\n const review = state.reviews.find((item) => item.id === reviewId)\n if (review == null || review.status !== 'pending') return previous\n review.status = 'ignored'\n review.resolvedAt = now\n state.updatedAt = now\n return state\n}\n","import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'fs'\nimport { join } from 'path'\n\nimport { createInitialState, SCHEMA_VERSION, type TrackerState } from '../core'\n\nexport interface TrackerStorage {\n load(memberId: string): TrackerState\n save(memberId: string, state: TrackerState): void\n}\n\nexport function normalizeMemberId(value: unknown): string | undefined {\n if (value == null) return undefined\n const normalized = typeof value === 'number' ? String(value) : typeof value === 'string' ? value : ''\n return /^\\d+$/.test(normalized) ? normalized : undefined\n}\n\nfunction hydrateState(value: unknown): TrackerState {\n if (typeof value !== 'object' || value == null) return createInitialState()\n const raw = value as Partial<TrackerState>\n if (raw.schemaVersion !== SCHEMA_VERSION) return createInitialState()\n const reviews = Array.isArray(raw.reviews)\n ? raw.reviews.map((review) => ({\n ...review,\n startedAt:\n typeof review.startedAt === 'number'\n ? review.startedAt\n : review.route?.[0]?.arrivedAt ?? review.detectedAt,\n timeline: Array.isArray(review.timeline) ? review.timeline : [],\n }))\n : []\n const activeSortie =\n raw.activeSortie != null\n ? {\n ...raw.activeSortie,\n timeline: Array.isArray(raw.activeSortie.timeline) ? raw.activeSortie.timeline : [],\n }\n : undefined\n return {\n ...createInitialState(typeof raw.updatedAt === 'number' ? raw.updatedAt : 0),\n ...raw,\n schemaVersion: SCHEMA_VERSION,\n observations: Array.isArray(raw.observations) ? raw.observations : [],\n guideConditions: Array.isArray(raw.guideConditions) ? raw.guideConditions : [],\n reviews,\n freeCompletions: Array.isArray(raw.freeCompletions) ? raw.freeCompletions : [],\n processedEventKeys: Array.isArray(raw.processedEventKeys) ? raw.processedEventKeys : [],\n activeSortie,\n }\n}\n\nexport class FileTrackerStorage implements TrackerStorage {\n constructor(private readonly appDataPath: string) {}\n\n statePath(memberId: string): string {\n const safe = normalizeMemberId(memberId)\n if (safe == null) throw new Error('Invalid memberId')\n return join(this.appDataPath, 'gimmick-tracker', safe, 'state-v1.json')\n }\n\n load(memberId: string): TrackerState {\n const path = this.statePath(memberId)\n if (!existsSync(path)) return createInitialState()\n try {\n return hydrateState(JSON.parse(readFileSync(path, 'utf8')) as unknown)\n } catch {\n return createInitialState()\n }\n }\n\n save(memberId: string, state: TrackerState): void {\n const path = this.statePath(memberId)\n const directory = join(path, '..')\n mkdirSync(directory, { recursive: true })\n const temporary = `${path}.${process.pid}.${Date.now()}.tmp`\n try {\n writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\\n`, 'utf8')\n renameSync(temporary, path)\n } catch (error) {\n try {\n if (existsSync(temporary)) unlinkSync(temporary)\n } catch {\n // Preserve the original write error.\n }\n throw error\n }\n }\n}\n\nexport class MemoryTrackerStorage implements TrackerStorage {\n readonly states = new Map<string, TrackerState>()\n\n load(memberId: string): TrackerState {\n const state = this.states.get(memberId)\n return state == null ? createInitialState() : JSON.parse(JSON.stringify(state)) as TrackerState\n }\n\n save(memberId: string, state: TrackerState): void {\n this.states.set(memberId, JSON.parse(JSON.stringify(state)) as TrackerState)\n }\n}\n","import {\n applyGameResponse,\n asRecord,\n confirmReview,\n createInitialState,\n extractPortMemberId,\n ignoreReview,\n importGuideConditions,\n type GameResponseDetail,\n type MapRef,\n type NodeResolver,\n type TrackerState,\n} from './core'\nimport type { FocusMethod } from './host'\nimport { normalizeMemberId, type TrackerStorage } from './storage'\n\nexport interface EventTargetLike {\n addEventListener(type: string, listener: (event: unknown) => void): void\n removeEventListener(type: string, listener: (event: unknown) => void): void\n}\n\nexport interface TrackerControllerDependencies {\n storage: TrackerStorage\n focus: { focusPlugin(id: string): FocusMethod | string }\n pluginId: string\n nodeResolver: NodeResolver\n getMemberId?: () => unknown\n now?: () => number\n onError?: (error: unknown) => void\n}\n\nfunction parseEvent(event: unknown): GameResponseDetail | undefined {\n if (typeof event !== 'object' || event == null) return undefined\n const detail = (event as { detail?: unknown }).detail\n if (typeof detail !== 'object' || detail == null) return undefined\n const value = detail as Record<string, unknown>\n if (typeof value.path !== 'string' || typeof value.time !== 'number') return undefined\n return {\n method: typeof value.method === 'string' ? value.method : undefined,\n path: value.path,\n body: asRecord(value.body) ?? {},\n postBody: asRecord(value.postBody),\n time: value.time,\n }\n}\n\nfunction mergeById<T extends { id: string }>(stored: T[], transient: T[]): T[] {\n const merged = new Map(stored.map((item) => [item.id, item]))\n for (const item of transient) merged.set(item.id, item)\n return [...merged.values()]\n}\n\nexport class TrackerController {\n private state: TrackerState = createInitialState()\n private readonly subscribers = new Set<() => void>()\n private eventTarget?: EventTargetLike\n private memberId?: string\n private started = false\n\n private readonly handleResponse = (event: unknown): void => {\n const detail = parseEvent(event)\n if (detail == null) return\n try {\n const preferred = this.readPreferredMemberId()\n const portMember = normalizeMemberId(extractPortMemberId(detail))\n this.switchMember(portMember ?? preferred)\n const result = applyGameResponse(this.state, detail, this.dependencies.nodeResolver)\n if (!result.changed) return\n this.state = result.state\n this.persist()\n this.emit()\n if (result.shouldFocus) {\n try {\n this.dependencies.focus.focusPlugin(this.dependencies.pluginId)\n } catch (error) {\n this.report(error)\n }\n }\n } catch (error) {\n this.report(error)\n }\n }\n\n constructor(private readonly dependencies: TrackerControllerDependencies) {}\n\n start(target: EventTargetLike): void {\n if (this.started) return\n this.started = true\n this.eventTarget = target\n this.switchMember(this.readPreferredMemberId())\n target.addEventListener('game.response', this.handleResponse)\n this.emit()\n }\n\n stop(): void {\n if (!this.started) return\n this.eventTarget?.removeEventListener('game.response', this.handleResponse)\n this.eventTarget = undefined\n this.started = false\n }\n\n getSnapshot = (): TrackerState => this.state\n\n subscribe = (subscriber: () => void): (() => void) => {\n this.subscribers.add(subscriber)\n return () => this.subscribers.delete(subscriber)\n }\n\n currentMap(): MapRef | undefined {\n if (this.state.activeSortie != null && this.state.activeSortie.mapId > 0) {\n return {\n mapArea: this.state.activeSortie.mapArea,\n mapNo: this.state.activeSortie.mapNo,\n mapId: this.state.activeSortie.mapId,\n }\n }\n const review = [...this.state.reviews].reverse().find((item) => item.mapId > 0)\n if (review != null) {\n return { mapArea: review.mapArea, mapNo: review.mapNo, mapId: review.mapId }\n }\n const observation = this.state.observations.at(-1)\n return observation == null\n ? undefined\n : {\n mapArea: observation.mapArea,\n mapNo: observation.mapNo,\n mapId: observation.mapId,\n }\n }\n\n importGuide(text: string, map = this.currentMap()): number {\n if (map == null) return 0\n const result = importGuideConditions(this.state, text, map, this.now())\n if (result.state === this.state) return result.importedCount\n this.state = result.state\n this.persist()\n this.emit()\n return result.importedCount\n }\n\n confirm(reviewId: string, candidateIds: string[]): void {\n const next = confirmReview(this.state, reviewId, candidateIds, this.now())\n if (next === this.state) return\n this.state = next\n this.persist()\n this.emit()\n }\n\n ignore(reviewId: string): void {\n const next = ignoreReview(this.state, reviewId, this.now())\n if (next === this.state) return\n this.state = next\n this.persist()\n this.emit()\n }\n\n private now(): number {\n return this.dependencies.now?.() ?? Date.now()\n }\n\n private readPreferredMemberId(): string | undefined {\n try {\n return normalizeMemberId(this.dependencies.getMemberId?.())\n } catch (error) {\n this.report(error)\n return undefined\n }\n }\n\n private switchMember(next: string | undefined): void {\n if (next == null || next === this.memberId) return\n if (this.memberId != null) this.persist()\n const transient = this.memberId == null ? this.state : undefined\n this.memberId = next\n try {\n const stored = this.dependencies.storage.load(next)\n if (transient == null || (transient.updatedAt === 0 && transient.activeSortie == null)) {\n this.state = stored\n return\n }\n this.state = {\n ...stored,\n activeSortie: transient.activeSortie ?? stored.activeSortie,\n observations: mergeById(stored.observations, transient.observations),\n guideConditions: mergeById(stored.guideConditions, transient.guideConditions),\n reviews: mergeById(stored.reviews, transient.reviews),\n freeCompletions: mergeById(stored.freeCompletions, transient.freeCompletions),\n processedEventKeys: [...new Set([\n ...stored.processedEventKeys,\n ...transient.processedEventKeys,\n ])].slice(-2048),\n updatedAt: Math.max(stored.updatedAt, transient.updatedAt),\n }\n } catch (error) {\n this.state = transient ?? createInitialState()\n this.report(error)\n }\n }\n\n private persist(): void {\n if (this.memberId == null) return\n try {\n this.dependencies.storage.save(this.memberId, this.state)\n } catch (error) {\n this.report(error)\n }\n }\n\n private emit(): void {\n for (const subscriber of this.subscribers) subscriber()\n }\n\n private report(error: unknown): void {\n this.dependencies.onError?.(error)\n }\n}\n","export interface IpcLike {\n access(scope: string): { ipcFocusPlugin?: (id: string) => void } | undefined\n}\n\nexport interface FocusAdapterDependencies {\n primaryIpc?: IpcLike\n legacyIpc?: IpcLike\n dispatch?: (action: unknown) => unknown\n getStore?: (path: string) => unknown\n success?: (message: string) => unknown\n}\n\nexport type FocusMethod = 'ipc' | 'legacy-ipc' | 'dispatch' | 'notice' | 'none'\n\nfunction tryIpc(ipc: IpcLike | undefined, id: string): boolean {\n try {\n const focus = ipc?.access('MainWindow')?.ipcFocusPlugin\n if (typeof focus !== 'function') return false\n focus(id)\n return true\n } catch {\n return false\n }\n}\n\nexport function createFocusAdapter(dependencies: FocusAdapterDependencies) {\n return {\n focusPlugin(id: string): FocusMethod {\n if (tryIpc(dependencies.primaryIpc, id)) return 'ipc'\n if (tryIpc(dependencies.legacyIpc, id)) return 'legacy-ipc'\n try {\n if (typeof dependencies.dispatch === 'function') {\n const doubleTabbed = Boolean(dependencies.getStore?.('config.poi.tabarea.double'))\n const tabInfo = doubleTabbed\n ? { activePluginName: id }\n : { activeMainTab: id, activePluginName: id }\n dependencies.dispatch({\n type: '@@TabSwitch',\n payload: { tabInfo, autoSwitch: false },\n })\n return 'dispatch'\n }\n } catch {\n // Continue to the non-throwing notification fallback.\n }\n try {\n if (typeof dependencies.success === 'function') {\n dependencies.success('检测到机关变化,请打开机关条件追踪器确认。')\n return 'notice'\n }\n } catch {\n // Host notifications must never break response handling.\n }\n return 'none'\n },\n }\n}\n","import type { NodeResolver } from '../core'\n\nexport type StoreGetter = (path?: string) => unknown\n\nexport function createFcdNodeResolver(getStore: StoreGetter): NodeResolver {\n return ({ mapArea, mapNo, apiNo }) => {\n const fallback = `#${apiNo}`\n try {\n const fcdMap = getStore('fcd.map')\n if (typeof fcdMap !== 'object' || fcdMap == null) return fallback\n const map = (fcdMap as Record<string, unknown>)[`${mapArea}-${mapNo}`]\n if (typeof map !== 'object' || map == null) return fallback\n const route = (map as Record<string, unknown>).route\n if (typeof route !== 'object' || route == null) return fallback\n const edge = (route as Record<string, unknown>)[String(apiNo)]\n if (!Array.isArray(edge) || typeof edge[1] !== 'string' || edge[1].trim() === '') {\n return fallback\n }\n return edge[1]\n } catch {\n return fallback\n }\n }\n}\n","{\n \"机关条件追踪器\": \"机关条件追踪器\",\n \"记录出击路线并在机关触发后请用户确认完成条件\": \"记录出击路线并在机关触发后请用户确认完成条件\",\n \"title\": \"机关条件追踪器\",\n \"subtitle\": \"平时安静记录;检测到机关变化后才请你确认。数据只保存在本机。\",\n \"activeMap\": \"当前海域\",\n \"pendingReviews\": \"待确认触发\",\n \"pending\": \"待确认\",\n \"noPending\": \"暂无待确认触发。普通出击不会自动切换到本页。\",\n \"unknownMap\": \"未知海域\",\n \"route\": \"本次路线\",\n \"incompleteRoute\": \"未捕获到完整出击:插件可能在出击中途加载,或仅收到了回港信号。\",\n \"emptyRoute\": \"没有可显示的节点路线。\",\n \"candidates\": \"这次完成了哪一项?\",\n \"chooseCandidate\": \"请选择本次完成条件(● 表示本次有直接证据)\",\n \"noCandidates\": \"没有足够事实生成候选;可忽略本次,或先导入攻略条件后再观察下一次出击。\",\n \"matchedThisSortie\": \"本次有证据\",\n \"confirm\": \"确认完成\",\n \"ignore\": \"忽略本次\",\n \"ignored\": \"已忽略\",\n \"guide\": \"导入攻略条件\",\n \"guidePlaceholder\": \"例如:D点S胜2次;C2点到达2次 A2点空优1次,守家空优2次\",\n \"import\": \"导入到当前海域\",\n \"importNeedMap\": \"尚未识别当前海域,请先捕获一次地图出击。\",\n \"importSuccess\": \"已识别 {count} 条条件。\",\n \"noMatch\": \"未识别到受支持的条件格式。\",\n \"completed\": \"按海域的上次触发与完成清单\",\n \"lastTriggeredSortie\": \"上一次触发出击\",\n \"triggerHistory\": \"每次机关触发出击\",\n \"noCompleted\": \"尚未导入攻略条件,也没有自由完成记录。\",\n \"observed\": \"观测\",\n \"confirmed\": \"已确认\",\n \"unconfirmed\": \"未确认\",\n \"freeRecord\": \"无攻略时的自由记录\"\n}\n","import {\n Button,\n Callout,\n Card,\n Elevation,\n HTMLSelect,\n HTMLTable,\n Tag,\n TextArea,\n} from '@blueprintjs/core'\nimport React, { useMemo, useState, useSyncExternalStore } from 'react'\n\nimport zhCN from '../../i18n/zh-CN.json'\nimport { airStateLabel, guideConditionLabel, mapLabel, type PendingReview } from '../core'\nimport type { TrackerController } from '../controller'\n\nconst styles: Record<string, React.CSSProperties> = {\n root: { padding: 16, height: '100%', overflow: 'auto', boxSizing: 'border-box' },\n header: { display: 'flex', justifyContent: 'space-between', gap: 16, alignItems: 'center' },\n section: { marginTop: 16 },\n review: { marginTop: 12 },\n reviewGrid: { display: 'grid', gridTemplateColumns: 'minmax(260px, 1.2fr) minmax(280px, .8fr)', gap: 18 },\n route: { display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 8 },\n historyRow: { display: 'grid', gridTemplateColumns: 'minmax(280px, 1fr) auto', gap: 16, padding: '10px 0', borderBottom: '1px solid rgba(128,128,128,.18)' },\n candidate: { padding: '6px 0', borderBottom: '1px solid rgba(128,128,128,.18)' },\n actions: { display: 'flex', gap: 8, marginTop: 12 },\n guideRow: { display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 12, alignItems: 'center' },\n muted: { color: '#738091' },\n}\n\ntype TextKey = keyof typeof zhCN\nconst t = (key: TextKey): string => zhCN[key]\n\nfunction routeBadge(review: PendingReview, index: number): React.ReactNode {\n const visit = review.route[index]\n const details = [visit.rank == null ? undefined : `${visit.rank}胜`]\n if (visit.airState != null) details.push(airStateLabel(visit.airState))\n if (visit.battleEvents?.some((event) => event.kind === 'fleet-air-raid')) details.push('舰队空袭')\n for (const defense of visit.baseDefenseEvents ?? []) {\n details.push(`基地防空${defense.airState == null ? '' : ` · ${airStateLabel(defense.airState)}`}`)\n }\n return (\n <Tag\n key={visit.id}\n minimal={index !== review.route.length - 1}\n title={new Date(visit.arrivedAt).toLocaleString()}\n >\n {visit.nodeLabel}\n {details.filter(Boolean).length > 0 ? ` · ${details.filter(Boolean).join(' · ')}` : ''}\n </Tag>\n )\n}\n\nfunction formatSortieTime(review: PendingReview): string {\n const startedAt = review.startedAt ?? review.route[0]?.arrivedAt ?? review.detectedAt\n return `${new Date(startedAt).toLocaleString()} ~ ${new Date(review.detectedAt).toLocaleString()}`\n}\n\nfunction timelineBadge(review: PendingReview, event: PendingReview['timeline'][number]): React.ReactNode {\n if (event.kind === 'node') {\n const index = review.route.findIndex((visit) => visit.id === event.nodeVisitId)\n if (index >= 0) return routeBadge(review, index)\n }\n const label = event.kind === 'fleet-air-raid' ? '舰队空袭' : '基地防空'\n const parts = [event.nodeLabel == null ? undefined : `${event.nodeLabel}点`, label]\n if (event.airState != null) parts.push(airStateLabel(event.airState))\n return (\n <Tag key={event.id} intent={event.kind === 'base-air-defense' ? 'warning' : 'primary'} title={new Date(event.occurredAt).toLocaleString()}>\n {parts.filter(Boolean).join(' · ')}\n </Tag>\n )\n}\n\nfunction sortieTimeline(review: PendingReview): React.ReactNode {\n const timeline = review.timeline ?? []\n if (timeline.length > 0) {\n return <div style={styles.route}>{timeline.map((event) => timelineBadge(review, event))}</div>\n }\n if (review.route.length > 0) {\n return <div style={styles.route}>{review.route.map((_, index) => routeBadge(review, index))}</div>\n }\n return <span style={styles.muted}>{t('emptyRoute')}</span>\n}\n\nfunction PendingCard({\n review,\n selected,\n setSelected,\n controller,\n}: {\n review: PendingReview\n selected: string\n setSelected: (id: string) => void\n controller: TrackerController\n}) {\n const selectedCandidate = review.candidates.find((candidate) => candidate.id === selected)\n return (\n <Card style={styles.review} elevation={Elevation.ONE}>\n <div style={styles.header}>\n <div>\n <strong>{review.mapId > 0 ? mapLabel(review) : t('unknownMap')}</strong>{' '}\n <Tag intent=\"warning\">{t('pending')}</Tag>\n <div style={styles.muted}>{formatSortieTime(review)}</div>\n </div>\n <div style={styles.route}>\n {review.triggerSources.map((source) => (\n <Tag key={source} minimal>\n {source}\n </Tag>\n ))}\n </div>\n </div>\n <div style={styles.reviewGrid}>\n <div>\n <h4>{t('route')}</h4>\n {!review.routeComplete && <Callout intent=\"warning\">{t('incompleteRoute')}</Callout>}\n {sortieTimeline(review)}\n </div>\n <div>\n <h4>{t('candidates')}</h4>\n {review.candidates.length === 0 ? (\n <Callout>{t('noCandidates')}</Callout>\n ) : (\n <>\n <HTMLSelect\n fill\n value={selected}\n onChange={(event) => setSelected(event.currentTarget.value)}\n >\n <option value=\"\">{t('chooseCandidate')}</option>\n {review.candidates.map((candidate) => (\n <option key={candidate.id} value={candidate.id}>\n {candidate.matchedInSortie ? '● ' : ''}{candidate.label}\n {candidate.observedCount == null\n ? ''\n : ` · ${t('observed')} ${candidate.observedCount}/${candidate.requiredCount}`}\n </option>\n ))}\n </HTMLSelect>\n {selectedCandidate?.matchedInSortie && (\n <Callout style={{ marginTop: 8 }} intent=\"success\">{t('matchedThisSortie')}</Callout>\n )}\n </>\n )}\n <div style={styles.actions}>\n <Button\n intent=\"primary\"\n disabled={selected === ''}\n onClick={() => controller.confirm(review.id, [selected])}\n >\n {t('confirm')}\n </Button>\n <Button onClick={() => controller.ignore(review.id)}>{t('ignore')}</Button>\n </div>\n </div>\n </div>\n </Card>\n )\n}\n\nexport function createTrackerView(controller: TrackerController): React.ComponentType {\n return function GimmickTrackerView() {\n const state = useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot)\n const [guideText, setGuideText] = useState('')\n const [message, setMessage] = useState('')\n const [selected, setSelected] = useState<Record<string, string>>({})\n const pending = state.reviews.filter((review) => review.status === 'pending')\n const currentMap = controller.currentMap()\n const mapIds = useMemo(\n () =>\n [...new Set([\n ...state.guideConditions.map((item) => item.mapId),\n ...state.freeCompletions.map((item) => item.mapId),\n ...state.reviews.filter((item) => item.mapId > 0).map((item) => item.mapId),\n ])].sort((left, right) => left - right),\n [state.guideConditions, state.freeCompletions, state.reviews],\n )\n\n const importGuide = () => {\n if (currentMap == null) {\n setMessage(t('importNeedMap'))\n return\n }\n const count = controller.importGuide(guideText, currentMap)\n setMessage(count > 0 ? t('importSuccess').replace('{count}', String(count)) : t('noMatch'))\n }\n\n return (\n <main style={styles.root}>\n <header style={styles.header}>\n <div>\n <h2 style={{ margin: 0 }}>{t('title')}</h2>\n <p style={{ ...styles.muted, marginBottom: 0 }}>{t('subtitle')}</p>\n </div>\n {currentMap != null && <Tag large>{t('activeMap')}: {mapLabel(currentMap)}</Tag>}\n </header>\n\n <section style={styles.section}>\n <h3>{t('pendingReviews')}</h3>\n {pending.length === 0 ? (\n <Callout>{t('noPending')}</Callout>\n ) : (\n pending.map((review) => (\n <PendingCard\n key={review.id}\n review={review}\n controller={controller}\n selected={selected[review.id] ?? ''}\n setSelected={(id) => setSelected((value) => ({ ...value, [review.id]: id }))}\n />\n ))\n )}\n </section>\n\n <section style={styles.section}>\n <h3>{t('guide')}</h3>\n <TextArea\n fill\n rows={4}\n value={guideText}\n placeholder={t('guidePlaceholder')}\n onChange={(event) => setGuideText(event.currentTarget.value)}\n />\n <div style={styles.actions}>\n <Button intent=\"primary\" onClick={importGuide}>{t('import')}</Button>\n {message !== '' && <span style={styles.muted}>{message}</span>}\n </div>\n </section>\n\n <section style={styles.section}>\n <h3>{t('completed')}</h3>\n {mapIds.length === 0 ? (\n <Callout>{t('noCompleted')}</Callout>\n ) : (\n mapIds.map((mapId) => {\n const guides = state.guideConditions.filter((item) => item.mapId === mapId)\n const free = state.freeCompletions.filter((item) => item.mapId === mapId)\n const reviews = state.reviews.filter((item) => item.mapId === mapId)\n const lastReview = reviews.at(-1)\n const map = guides[0] ?? free[0] ?? lastReview\n return (\n <Card key={mapId} style={styles.review} elevation={Elevation.ZERO}>\n <h4>{map == null ? `#${mapId}` : mapLabel(map)}</h4>\n {reviews.length > 0 && (\n <div style={{ marginBottom: 16 }}>\n <strong>{t('triggerHistory')}</strong>\n {[...reviews].reverse().map((review) => {\n const selectedLabels = review.candidates\n .filter((candidate) => review.selectedCandidateIds?.includes(candidate.id))\n .map((candidate) => candidate.label)\n return (\n <div key={review.id} style={styles.historyRow}>\n <div>{sortieTimeline(review)}</div>\n <div style={{ textAlign: 'right' }}>\n <div style={styles.muted}>{formatSortieTime(review)}</div>\n <Tag\n intent={review.status === 'confirmed' ? 'success' : review.status === 'pending' ? 'warning' : 'none'}\n >\n {review.status === 'confirmed'\n ? t('confirmed')\n : review.status === 'pending'\n ? t('pending')\n : t('ignored')}\n </Tag>\n {selectedLabels.length > 0 && <div>{selectedLabels.join('、')}</div>}\n </div>\n </div>\n )\n })}\n </div>\n )}\n <HTMLTable compact striped style={{ width: '100%' }}>\n <tbody>\n {guides.map((condition) => (\n <tr key={condition.id}>\n <td>{guideConditionLabel(condition)}</td>\n <td>{t('observed')} {condition.observedCount}/{condition.requiredCount}</td>\n <td>\n <Tag intent={condition.confirmed ? 'success' : 'none'}>\n {condition.confirmed ? t('confirmed') : t('unconfirmed')}\n </Tag>\n </td>\n </tr>\n ))}\n {free.map((completion) => (\n <tr key={completion.id}>\n <td>{completion.label}</td>\n <td>{t('freeRecord')}</td>\n <td><Tag intent=\"success\">{t('confirmed')}</Tag></td>\n </tr>\n ))}\n </tbody>\n </HTMLTable>\n </Card>\n )\n })\n )}\n </section>\n </main>\n )\n }\n}\n","import { dispatch, getStore } from 'views/create-store'\nimport { APPDATA_PATH, ipc } from 'views/env'\n\nimport { PLUGIN_ID } from './src/core'\nimport { TrackerController } from './src/controller'\nimport { createFocusAdapter, createFcdNodeResolver } from './src/host'\nimport { FileTrackerStorage } from './src/storage'\nimport { createTrackerView } from './src/ui'\n\nconst appDataPath = window.APPDATA_PATH ?? APPDATA_PATH\nconst controller = new TrackerController({\n storage: new FileTrackerStorage(appDataPath),\n focus: createFocusAdapter({\n primaryIpc: ipc,\n legacyIpc: window.ipc,\n dispatch,\n getStore: (path) => getStore(path),\n success: window.success,\n }),\n pluginId: PLUGIN_ID,\n nodeResolver: createFcdNodeResolver((path) => getStore(path)),\n getMemberId: () => getStore('info.basic.api_member_id'),\n onError: (error) => console.error(`[${PLUGIN_ID}]`, error),\n})\n\nexport const reactClass = createTrackerView(controller)\nexport const windowMode = false\n\nexport function pluginDidLoad(): void {\n controller.start(window)\n}\n\nexport function pluginWillUnload(): void {\n controller.stop()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWA,MAAMA,oBAA8C;CAClD,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACL;AAED,MAAM,gBACJ;AAEF,SAAgB,mBAAmB,OAAuB;AACxD,QAAO,MAAM,MAAM,CAAC,aAAa;;AAGnC,SAAgB,eAAe,MAAsC;CACnE,MAAM,aAAa,KAAK,QAAQ,UAAU,UAAW,UAAU,MAAM,MAAM,IAAK;CAChF,MAAMC,SAAiC,EAAE;CACzC,IAAIC;AACJ,eAAc,YAAY;AAE1B,SAAQ,QAAQ,cAAc,KAAK,WAAW,MAAM,MAAM;EACxD,MAAM,UAAU,MAAM,GAAG,QAAQ,QAAQ,GAAG;AAC5C,MAAI,MAAM,MAAM,MAAM;AACpB,UAAO,KAAK;IACV,MAAM;IACN,WAAW,mBAAmB,MAAM,GAAG;IACvC,eAAe,OAAO,MAAM,GAAG;IAC/B;IACD,CAAC;AACF;;AAEF,MAAI,MAAM,MAAM,MAAM;AACpB,UAAO,KAAK;IACV,MAAM;IACN,WAAW,mBAAmB,MAAM,GAAG;IACvC,cAAc,MAAM,GAAG,aAAa;IACpC,eAAe,OAAO,MAAM,GAAG;IAC/B;IACD,CAAC;AACF;;AAEF,MAAI,MAAM,MAAM,MAAM;AACpB,UAAO,KAAK;IACV,MAAM;IACN,WAAW,mBAAmB,MAAM,GAAG;IACvC,kBAAkB,kBAAkB,MAAM;IAC1C,eAAe,OAAO,MAAM,GAAG;IAC/B;IACD,CAAC;AACF;;AAEF,SAAO,KAAK;GACV,MAAM;GACN,kBAAkB,kBAAkB,MAAM;GAC1C,eAAe,OAAO,MAAM,IAAI;GAChC;GACD,CAAC;;AAGJ,QAAO,OAAO,QAAQ,SAAS,OAAO,UAAU,KAAK,cAAc,IAAI,KAAK,gBAAgB,EAAE;;;;;AC3DhG,MAAMC,aAA0C;CAC9C,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,IAAI;CACL;AAED,MAAMC,YAAsC;CAC1C,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACJ;AAED,SAAgB,cAAc,OAAyB;AACrD,QAAO,UAAU;;AAGnB,SAAgB,UAAU,QAAqB,UAAgC;AAC7E,QAAO,WAAW,WAAW,WAAW;;AAG1C,SAAgB,cAAc,QAAkB,UAA6B;AAC3E,QAAO,UAAU;;AAGnB,SAAgB,mBACd,KACA,MAIQ;AACR,QAAO;EACL,IAAI;EACJ,KAAK;EACL,KAAK,aAAa,OAAO,KAAK,mBAAmB,KAAK,UAAU;EAChE,KAAK,gBAAgB;EACrB,KAAK,oBAAoB;EAC1B,CAAC,KAAK,IAAI;;AAGb,SAAgB,mBAAmB,WAA2B,aAAmC;AAC/F,KAAI,UAAU,UAAU,YAAY,SAAS,UAAU,SAAS,YAAY,KAAM,QAAO;AACzF,KACE,UAAU,aAAa,QACvB,mBAAmB,UAAU,UAAU,KAAK,mBAAmB,YAAY,aAAa,GAAG,CAE3F,QAAO;AAET,KAAI,UAAU,SAAS,eACrB,QACE,YAAY,SAAS,kBACrB,UAAU,gBAAgB,QAC1B,UAAU,YAAY,MAAM,UAAU,aAAa;AAGvD,KAAI,UAAU,SAAS,oBAAoB,UAAU,SAAS,mBAC5D,SACG,YAAY,SAAS,oBAAoB,YAAY,SAAS,uBAC/D,UAAU,oBAAoB,QAC9B,cAAc,YAAY,UAAU,UAAU,iBAAiB;AAGnE,QAAO,YAAY,SAAS;;AAG9B,SAAgB,oBAAoB,WAAmC;CACrE,MAAM,OAAO,UAAU,aAAa,OAAO,KAAK,GAAG,UAAU,UAAU;AACvE,SAAQ,UAAU,MAAlB;EACE,KAAK,eACH,QAAO,GAAG,KAAK,KAAK,UAAU,cAAc;EAC9C,KAAK,eACH,QAAO,GAAG,OAAO,UAAU,aAAa,IAAI,UAAU,cAAc;EACtE,KAAK,iBACH,QAAO,GAAG,OAAO,cAAc,UAAU,iBAAkB,CAAC,GAAG,UAAU,cAAc;EACzF,KAAK,mBACH,QAAO,KAAK,cAAc,UAAU,iBAAkB,CAAC,GAAG,UAAU,cAAc;;;AAIxF,SAAgB,iBAAiB,aAAkC;AACjE,SAAQ,YAAY,MAApB;EACE,KAAK,eACH,QAAO,GAAG,YAAY,UAAU;EAClC,KAAK,eACH,QAAO,GAAG,YAAY,UAAU,GAAG,YAAY,KAAK;EACtD,KAAK,iBACH,QAAO,GAAG,YAAY,UAAU,GAAG,cAAc,YAAY,SAAS;EACxE,KAAK,mBACH,QAAO,KAAK,cAAc,YAAY,SAAS;;;AAIrD,SAAgB,sBACd,OACA,OACA,gBACmB;CACnB,MAAM,WAAW,MAAM,aAAa,QAAQ,SAAS,eAAe,SAAS,KAAK,GAAG,CAAC;CACtF,MAAM,SAAS,MAAM,gBAAgB,QAClC,cAAc,UAAU,UAAU,SAAS,CAAC,UAAU,UACxD;AACD,KAAI,OAAO,SAAS,EAClB,QAAO,OACJ,KAAK,eAAe;EACnB,IAAI,SAAS,UAAU;EACvB,MAAM;EACN,MAAM,UAAU;EAChB,OAAO,oBAAoB,UAAU;EACrC,aAAa,UAAU;EACvB,iBAAiB,SAAS,MAAM,SAAS,mBAAmB,WAAW,KAAK,CAAC;EAC7E,eAAe,UAAU;EACzB,eAAe,UAAU;EAC1B,EAAE,CACF,MAAM,MAAM,UAAU,OAAO,MAAM,gBAAgB,GAAG,OAAO,KAAK,gBAAgB,CAAC;AAGxF,QAAO,SAAS,KAAK,iBAAiB;EACpC,IAAI,QAAQ,YAAY;EACxB,MAAM;EACN,MAAM,YAAY;EAClB,OAAO,iBAAiB,YAAY;EACpC,eAAe,YAAY;EAC3B,iBAAiB;EAClB,EAAE;;;;;AC1IL,MAAa,YAAY,IAAI,IAAI;CAC/B;CACA;CACA;CACD,CAAC;AAEF,MAAM,kBAAkB,IAAI,IAAI;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,SAAS,SAAS,OAAkD;AAClE,QAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM;;AAG7E,SAAgB,SAAS,OAAqD;AAC5E,KAAI,SAAS,MAAM,CAAE,QAAO;AAC5B,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,KAAI;EACF,MAAMC,SAAkB,KAAK,MAAM,MAAM;AACzC,SAAO,SAAS,OAAO,GAAG,SAAS;SAC7B;AACN;;;AAIJ,SAAgB,SAAS,OAAoC;AAC3D,KAAI,OAAO,UAAU,YAAY,OAAO,SAAS,MAAM,CAAE,QAAO;AAChE,KAAI,OAAO,UAAU,YAAY,MAAM,MAAM,KAAK,IAAI;EACpD,MAAM,SAAS,OAAO,MAAM;AAC5B,SAAO,OAAO,SAAS,OAAO,GAAG,SAAS;;;AAK9C,SAAgB,eAAe,QAAuB;AACpD,QACEC,WAAS,2BACT,UAAU,IAAIA,OAAK,IACnB,aAAaA,OAAK,IAClB,mBAAmBA,OAAK;;AAI5B,SAAgB,mBAAmB,QAAuB;AACxD,QAAOA,OAAK,WAAW,WAAW,IAAIA,OAAK,SAAS,gBAAgB;;AAGtE,SAAgB,aAAa,QAAuB;AAClD,KAAI,CAAC,gEAAgE,KAAKA,OAAK,CAAE,QAAO;AACxF,QAAO,gBAAgB,IAAIA,OAAK,MAAMA,OAAK,YAAY,IAAI,GAAG,EAAE,CAAC;;AAGnE,SAAgB,SAAS,QAAoC;AAC3D,QAAO,GAAG,OAAO,KAAK,GAAG,OAAO;;AAGlC,SAAgB,cACd,QACA,UACQ;CACR,MAAM,UACJ,SAAS,OAAO,KAAK,eAAe,IACpC,SAAS,OAAO,UAAU,eAAe,IACzC,UAAU,WACV;CACF,MAAM,QACJ,SAAS,OAAO,KAAK,eAAe,IACpC,SAAS,OAAO,UAAU,eAAe,IACzC,UAAU,SACV;AACF,QAAO;EAAE;EAAS;EAAO,OAAO,UAAU,KAAK;EAAO;;AAGxD,SAAgB,cAAc,MAAmD;AAC/E,QAAO,SAAS,KAAK,OAAO;;AAG9B,SAAgB,sBAAsB,MAAqD;CAGzF,MAAM,QAAQ,SADC,SADD,SAAS,KAAK,UAAU,EACP,WAAW,EACX,eAAe;AAC9C,QAAO,SAAS,QAAQ,SAAS,KAAK,SAAS,IAAK,QAAqB;;AAG3E,SAAgB,mBAAmB,MAAwD;CACzF,MAAM,QAAQ,OAAO,KAAK,iBAAiB,WAAW,KAAK,aAAa,aAAa,GAAG;AACxF,QAAO;EAAC;EAAM;EAAK;EAAK;EAAK;EAAK;EAAK;EAAI,CAAC,SAAS,MAAM,GACtD,QACD;;AAGN,SAAgB,0BAA0B,MAA0D;CAClG,MAAM,MAAM,KAAK;AAEjB,SADe,MAAM,QAAQ,IAAI,GAAG,MAAM,OAAO,OAAO,EAAE,GAAG,CAAC,IAAI,EACpD,IAAI,SAAS,CAAC,QAAQ,SAA0C,QAAQ,KAAK;;AAG7F,SAAgB,uBAAuB,OAAsD;CAG3F,MAAM,QAAQ,SADC,SADA,SAAS,MAAM,oBAAoB,EAClB,WAAW,EACZ,eAAe;AAC9C,QAAO,SAAS,QAAQ,SAAS,KAAK,SAAS,IAAK,QAAqB;;AAG3E,SAAgB,cAAc,QAAqC;CACjE,MAAM,cAAc,SAAS,OAAO,KAAK,iBAAiB;AAC1D,QAAO,OAAO,SAAS,2BAA2B,SAAS,aAAa,YAAY,KAAK;;AAG3F,SAAgB,sBAAsB,QAAqC;AACzE,QAAO,mBAAmB,OAAO,KAAK,IAAI,SAAS,OAAO,KAAK,OAAO,KAAK;;AAG7E,SAAgB,iBAAiB,OAAyC;AACxE,QAAO,SAAS,MAAM,OAAO,KAAK;;AAGpC,SAAgB,oBAAoB,QAAqC;AACvE,KAAI,OAAO,SAAS,wBAAyB,QAAO;AACpD,QAAO,SAAS,OAAO,KAAK,UAAU,EAAE;;;;;ACrI1C,MAAa,YAAY;AACzB,MAAa,iBAAiB;AA2L9B,SAAgB,mBAAmB,MAAM,GAAiB;AACxD,QAAO;EACL,eAAe;EACf,cAAc,EAAE;EAChB,iBAAiB,EAAE;EACnB,SAAS,EAAE;EACX,iBAAiB,EAAE;EACnB,oBAAoB,EAAE;EACtB,WAAW;EACZ;;AAGH,SAAgB,WAAW,OAAmC;AAC5D,QAAO,KAAK,MAAM,KAAK,UAAU,MAAM,CAAC;;AAG1C,SAAgB,SAAS,KAA0D;AACjF,QAAO,IAAI,UAAU,KAAK,IAAI,QAAQ,IAAI,GAAG,IAAI,QAAQ,GAAG,IAAI,UAAU,OAAO,IAAI;;;;;ACzJvF,MAAM,2BAA2B;AAEjC,SAAS,cAAc,UAAwB,KAAa,OAAuB;AACjF,KAAI;EACF,MAAM,QAAQ,SAAS;GAAE,SAAS,IAAI;GAAS,OAAO,IAAI;GAAO;GAAO,CAAC,CAAC,MAAM;AAChF,SAAO,UAAU,KAAK,IAAI,UAAU;SAC9B;AACN,SAAO,IAAI;;;AAIf,SAAS,aACP,QACA,KACA,iBACc;AACd,QAAO;EACL,GAAG;EACH,IAAI,GAAG,kBAAkB,WAAW,UAAU,GAAG,IAAI,MAAM,GAAG,OAAO;EACrE,WAAW,OAAO;EAClB;EACA,OAAO,EAAE;EACT,UAAU,EAAE;EACZ,gBAAgB,EAAE;EACnB;;AAGH,SAAS,kBACP,OACA,QACA,aACS;AACT,KAAI,MAAM,aAAa,MAAM,SAAS,KAAK,OAAO,YAAY,GAAG,CAAE,QAAO;AAC1E,OAAM,aAAa,KAAK,YAAY;AACpC,QAAO,eAAe,KAAK,YAAY,GAAG;AAC1C,MAAK,MAAM,aAAa,MAAM,gBAC5B,KAAI,mBAAmB,WAAW,YAAY,CAAE,WAAU,iBAAiB;AAE7E,QAAO;;AAGT,SAAS,YAAY,QAAsB;AACzC,QAAO,OAAO,MAAM,GAAG,GAAG;;AAG5B,SAAS,aACP,OACA,QACA,QACA,cACM;CACN,MAAM,SAAS,cAAc,OAAO,KAAK;AACzC,KAAI,UAAU,KAAM;CACpB,MAAM,KAAK,GAAG,SAAS,OAAO,CAAC;AAC/B,KAAI,OAAO,MAAM,MAAM,YAAUC,QAAM,OAAO,GAAG,CAAE;CACnD,MAAM,YAAY,cAAc,cAAc,QAAQ,OAAO;CAC7D,MAAM,QAAQ;EACZ;EACA,SAAS,OAAO;EAChB,OAAO,OAAO;EACd,OAAO,OAAO;EACd;EACA;EACA,WAAW,OAAO;EAClB,YAAY,OAAO;EACpB;AACD,QAAO,MAAM,KAAK,MAAM;AACxB,QAAO,SAAS,KAAK;EACnB,IAAI,GAAG,GAAG;EACV,MAAM;EACN,YAAY,OAAO;EACnB,YAAY,OAAO;EACnB,aAAa;EACb;EACA;EACD,CAAC;AAaF,mBAAkB,OAAO,QAZmB;EAC1C,IAAI,GAAG,GAAG;EACV,MAAM;EACN,SAAS,OAAO;EAChB,OAAO,OAAO;EACd,OAAO,OAAO;EACd;EACA;EACA,YAAY,OAAO;EACnB,YAAY,OAAO;EACnB,UAAU,OAAO;EAClB,CAC4C;;AAG/C,SAAS,UACP,OACA,QACA,QACM;CACN,MAAMC,SAA0B;EAC9B,WAAW,OAAO;EAClB,MAAM,OAAO;EACb,MAAM,OAAO,KAAK,SAAS,gBAAgB,GAAG,mBAAmB;EAClE;AACD,QAAO,aAAa;CACpB,MAAM,OAAO,YAAY,OAAO;AAChC,KAAI,QAAQ,MAAM;AAChB,OAAK,SAAS;AACb,GAAC,KAAK,iBAAiB,EAAE,EAAE,KAAK,OAAO;;CAE1C,MAAM,WAAW,sBAAsB,OAAO,KAAK;AACnD,KAAI,OAAO,SAAS,iBAClB,QAAO,SAAS,KAAK;EACnB,IAAI,GAAG,SAAS,OAAO,CAAC;EACxB,MAAM;EACN,YAAY,OAAO;EACnB,YAAY,OAAO;EACnB,aAAa,MAAM;EACnB,QAAQ,MAAM;EACd,WAAW,MAAM;EACjB;EACD,CAAC;AAEJ,KAAI,QAAQ,QAAQ,YAAY,KAAM;AACtC,MAAK,WAAW;AAehB,mBAAkB,OAAO,QAde;EACtC,IAAI,GAAG,SAAS,OAAO,CAAC;EACxB,MAAM;EACN,SAAS,OAAO;EAChB,OAAO,OAAO;EACd,OAAO,OAAO;EACd,QAAQ,KAAK;EACb,WAAW,KAAK;EAChB;EACA,YAAY,OAAO;EACnB,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB;EACD,CAC4C;;AAG/C,SAAS,gBACP,OACA,QACA,QACM;CACN,MAAM,OAAO,mBAAmB,OAAO,KAAK;CAC5C,MAAM,OAAO,YAAY,OAAO;AAChC,KAAI,QAAQ,QAAQ,QAAQ,KAAM;AAClC,MAAK,OAAO;AAeZ,mBAAkB,OAAO,QAde;EACtC,IAAI,GAAG,SAAS,OAAO,CAAC;EACxB,MAAM;EACN,SAAS,OAAO;EAChB,OAAO,OAAO;EACd,OAAO,OAAO;EACd,QAAQ,KAAK;EACb,WAAW,KAAK;EAChB;EACA,YAAY,OAAO;EACnB,YAAY,OAAO;EACnB,UAAU,OAAO;EACjB,QAAQ,OAAO;EAChB,CAC4C;;AAG/C,SAAS,kBACP,OACA,QACA,QACqB;CACrB,MAAM,UAAU,0BAA0B,OAAO,KAAK;CACtD,IAAI,SAAS;AACb,SAAQ,SAAS,OAAO,UAAU;AAChC,aAAW,iBAAiB,MAAM;EAClC,MAAM,WAAW,uBAAuB,MAAM;EAC9C,MAAM,OAAO,YAAY,OAAO;AAChC,SAAO,SAAS,KAAK;GACnB,IAAI,GAAG,SAAS,OAAO,CAAC,WAAW,MAAM;GACzC,MAAM;GACN,YAAY,OAAO;GACnB,YAAY,OAAO;GACnB,aAAa,MAAM;GACnB,QAAQ,MAAM;GACd,WAAW,MAAM;GACjB;GACD,CAAC;AACF,MAAI,QAAQ,KACT,EAAC,KAAK,sBAAsB,EAAE,EAAE,KAAK;GACpC,WAAW,OAAO;GAClB,MAAM,OAAO;GACb;GACD,CAAC;AAEJ,MAAI,YAAY,KAAM;AAatB,oBAAkB,OAAO,QAZsB;GAC7C,IAAI,GAAG,SAAS,OAAO,CAAC,WAAW;GACnC,MAAM;GACN,SAAS,OAAO;GAChB,OAAO,OAAO;GACd,OAAO,OAAO;GACd;GACA,YAAY,OAAO;GACnB,YAAY,OAAO;GACnB,UAAU,OAAO;GACjB,QAAQ;IAAE,WAAW,OAAO;IAAM,MAAM,OAAO;IAAM,MAAM;IAAoB;GAChF,CAC4C;GAC7C;AACF,QAAO,EAAE,QAAQ;;AAGnB,SAAS,iBACP,OACA,QACA,QACA,YACA,QACe;AACf,QAAO;EACL,IAAI,UAAU,OAAO;EACrB,UAAU,OAAO;EACjB,WAAW,OAAO;EAClB,SAAS,OAAO;EAChB,OAAO,OAAO;EACd,OAAO,OAAO;EACd;EACA,gBAAgB,CAAC,OAAO;EACxB;EACA,eAAe,OAAO;EACtB,OAAO,WAAW;GAChB,eAAe;GACf,cAAc,EAAE;GAChB,iBAAiB,EAAE;GACnB,SAAS,EAAE;GACX,iBAAiB,EAAE;GACnB,oBAAoB,EAAE;GACtB,WAAW;GACX,cAAc;GACf,CAAC,CAAC,aAAc;EACjB,UAAU,KAAK,MAAM,KAAK,UAAU,OAAO,SAAS,CAAC;EACrD,gBAAgB,CAAC,GAAG,OAAO,eAAe;EAC1C,YAAY,sBAAsB,OAAO,OAAO,OAAO,OAAO,eAAe;EAC7E,QAAQ;EACT;;AAGH,SAAS,aAAa,OAAqB,QAAuB,YAAmC;CACnG,MAAM,WAAW,UAAU;AAC3B,QAAO;EACL,IAAI,UAAU;EACd;EACA,WAAW;EACX,SAAS;EACT,OAAO;EACP,OAAO;EACP;EACA,gBAAgB,CAAC,OAAO;EACxB,QAAQ,WAAW;EACnB,eAAe;EACf,OAAO,EAAE;EACT,UAAU,EAAE;EACZ,gBAAgB,EAAE;EAClB,YAAY,sBAAsB,OAAO,GAAG,EAAE,CAAC;EAC/C,QAAQ;EACT;;AAGH,SAAS,cACP,OACA,QACA,QACA,YACA,QACS;AACT,KAAI,UAAU,QAAQ,WAAW,QAAQ;EACvC,MAAM,eAAe,CAAC,GAAG,MAAM,QAAQ,CACpC,SAAS,CACT,MACE,SACC,KAAK,WAAW,aAChB,KAAK,SAAS,WAAW,UAAU,IACnC,CAAC,KAAK,UACN,KAAK,eAAe,SAAS,eAAe,IAC5C,CAAC,KAAK,eAAe,SAAS,OAAO,IACrC,aAAa,KAAK,cAAc,MAAU,IAC7C;AACH,MAAI,gBAAgB,MAAM;AACxB,OAAI,CAAC,aAAa,eAAe,SAAS,OAAO,CAAE,cAAa,eAAe,KAAK,OAAO;AAC3F,gBAAa,WAAW,UAAU,WAAW;AAC7C,UAAO;;;CAGX,MAAM,YACJ,UAAU,OACN,aAAa,OAAO,QAAQ,WAAW,GACvC,iBAAiB,OAAO,QAAQ,QAAQ,YAAY,OAAO;CACjE,MAAM,WAAW,MAAM,QAAQ,MAAM,SAAS,KAAK,OAAO,UAAU,GAAG;AACvE,KAAI,YAAY,MAAM;AACpB,QAAM,QAAQ,KAAK,UAAU;AAC7B,SAAO;;AAET,KAAI,CAAC,SAAS,eAAe,SAAS,OAAO,CAAE,UAAS,eAAe,KAAK,OAAO;AACnF,UAAS,WAAW;AACpB,KAAI,UAAU,MAAM;AAClB,WAAS,gBAAgB,OAAO;AAChC,WAAS,QAAQ,UAAU;AAC3B,WAAS,WAAW,UAAU;AAC9B,WAAS,iBAAiB,UAAU;AACpC,MAAI,SAAS,WAAW,UAAW,UAAS,aAAa,UAAU;;AAErE,QAAO;;AAGT,SAAS,0BAA0B,OAAqB,SAAS,OAAa;CAC5E,MAAM,SAAS,MAAM;AACrB,KAAI,UAAU,KAAM;CACpB,MAAM,SAAS,MAAM,QAAQ,MAAM,SAAS,KAAK,aAAa,OAAO,GAAG;AACxE,KAAI,UAAU,KAAM;AACpB,QAAO,WAAW;AAClB,QAAO,gBAAgB,OAAO;AAC9B,QAAO,QAAQ,KAAK,MAAM,KAAK,UAAU,OAAO,MAAM,CAAC;AACvD,QAAO,WAAW,KAAK,MAAM,KAAK,UAAU,OAAO,SAAS,CAAC;AAC7D,QAAO,iBAAiB,CAAC,GAAG,OAAO,eAAe;AAClD,KAAI,OAAO,WAAW,UACpB,QAAO,aAAa,sBAAsB,OAAO,OAAO,OAAO,OAAO,eAAe;;AAIzF,SAAS,eACP,OACA,QACA,cACS;AAET,KADgB,OAAO,SAAS,4BAG9B,OAAM,eAAe,aAAa,QADtB,cAAc,OAAO,EACc,KAAK;UAC3C,MAAM,gBAAgB,KAE/B,OAAM,eAAe,aAAa,QADtB,cAAc,OAAO,EACc,MAAM;MAChD;EACL,MAAM,MAAM,cAAc,QAAQ,MAAM,aAAa;AACrD,SAAO,OAAO,MAAM,cAAc,IAAI;;CAGxC,MAAM,SAAS,MAAM;AACrB,KAAI,OAAO,SAAS,+BAClB,cAAa,OAAO,QAAQ,QAAQ,aAAa;CAEnD,MAAM,UAAU,kBAAkB,OAAO,QAAQ,OAAO;CACxD,IAAI,cAAc;AAClB,KAAI,QAAQ,OACV,eAAc,cAAc,OAAO,QAAQ,eAAe,OAAO,MAAM,MAAM;AAE/E,2BAA0B,MAAM;AAChC,QAAO;;AAGT,SAAgB,kBACd,UACA,QACA,cACkB;AAClB,KAAI,CAAC,eAAe,OAAO,KAAK,CAC9B,QAAO;EAAE,OAAO;EAAU,SAAS;EAAO,aAAa;EAAO;CAEhE,MAAM,MAAM,SAAS,OAAO;AAC5B,KAAI,SAAS,mBAAmB,SAAS,IAAI,CAC3C,QAAO;EAAE,OAAO;EAAU,SAAS;EAAO,aAAa;EAAO;CAGhE,MAAM,QAAQ,WAAW,SAAS;AAClC,OAAM,mBAAmB,KAAK,IAAI;AAClC,KAAI,MAAM,mBAAmB,SAAS,yBACpC,OAAM,mBAAmB,OAAO,GAAG,MAAM,mBAAmB,SAAS,yBAAyB;CAEhG,IAAI,cAAc;AAElB,KAAI,UAAU,IAAI,OAAO,KAAK,CAC5B,eAAc,eAAe,OAAO,QAAQ,aAAa;UAChD,mBAAmB,OAAO,KAAK,EAAE;AAC1C,MAAI,MAAM,gBAAgB,KAAM,iBAAgB,OAAO,MAAM,cAAc,OAAO;AAClF,MAAI,sBAAsB,OAAO,CAC/B,eAAc,cACZ,OACA,MAAM,cACN,gBACA,OAAO,MACP,MACD;AAEH,4BAA0B,MAAM;YACvB,aAAa,OAAO,KAAK,EAClC;MAAI,MAAM,gBAAgB,MAAM;AAC9B,aAAU,OAAO,MAAM,cAAc,OAAO;AAC5C,6BAA0B,MAAM;;YAEzB,OAAO,SAAS,yBAAyB;AAClD,MAAI,cAAc,OAAO,CACvB,eAAc,cAAc,OAAO,MAAM,cAAc,QAAQ,OAAO,MAAM,KAAK;AAEnF,4BAA0B,OAAO,KAAK;AACtC,QAAM,eAAe;;AAGvB,OAAM,YAAY,OAAO;AACzB,QAAO;EAAE;EAAO,SAAS;EAAM;EAAa;;AAG9C,SAAS,yBAAyB,OAA2B;AAC3D,MAAK,MAAM,UAAU,MAAM,SAAS;AAClC,MAAI,OAAO,WAAW,UAAW;AACjC,SAAO,aAAa,sBAAsB,OAAO,OAAO,OAAO,OAAO,eAAe;;;AAIzF,SAAgB,sBACd,UACA,MACA,KACA,KACmB;CACnB,MAAM,SAAS,eAAe,KAAK;AACnC,KAAI,OAAO,WAAW,EAAG,QAAO;EAAE,OAAO;EAAU,eAAe;EAAG;CACrE,MAAM,QAAQ,WAAW,SAAS;CAClC,MAAM,yBAAS,IAAI,KAAsC;AACzD,MAAK,MAAM,QAAQ,OAAQ,QAAO,IAAI,mBAAmB,KAAK,KAAK,EAAE,KAAK;AAE1E,MAAK,MAAM,CAAC,WAAW,SAAS,QAAQ;EACtC,MAAM,KAAK,SAAS;EACpB,MAAM,WAAW,MAAM,gBAAgB,MAAM,gBAAcC,YAAU,OAAO,GAAG;AAC/E,MAAI,YAAY,MAAM;AACpB,YAAS,UAAU,KAAK;AACxB,YAAS,gBAAgB,KAAK;AAC9B,YAAS,gBAAgB,MAAM,aAAa,QAAQ,gBAClD,mBAAmB,UAAU,YAAY,CAC1C,CAAC;AACF;;EAEF,MAAMC,YAA4B;GAChC,GAAG;GACH;GACA,MAAM,KAAK;GACX,SAAS,KAAK;GACd,WAAW,KAAK;GAChB,cAAc,KAAK;GACnB,kBAAkB,KAAK;GACvB,eAAe,KAAK;GACpB,eAAe;GACf,WAAW;GACX,WAAW;GACZ;AACD,YAAU,gBAAgB,MAAM,aAAa,QAAQ,gBACnD,mBAAmB,WAAW,YAAY,CAC3C,CAAC;AACF,QAAM,gBAAgB,KAAK,UAAU;;AAEvC,0BAAyB,MAAM;AAC/B,OAAM,YAAY;AAClB,QAAO;EAAE;EAAO,eAAe,OAAO;EAAM;;AAG9C,SAAgB,cACd,UACA,UACA,cACA,KACc;AACd,KAAI,aAAa,WAAW,EAAG,QAAO;CACtC,MAAM,QAAQ,WAAW,SAAS;CAClC,MAAM,SAAS,MAAM,QAAQ,MAAM,SAAS,KAAK,OAAO,SAAS;AACjE,KAAI,UAAU,QAAQ,OAAO,WAAW,UAAW,QAAO;CAC1D,MAAM,WAAW,OAAO,WAAW,QAAQ,cAAc,aAAa,SAAS,UAAU,GAAG,CAAC;AAC7F,KAAI,SAAS,WAAW,EAAG,QAAO;CAElC,MAAM,cAAc,MAAM,gBAAgB,MAAM,cAAc,UAAU,UAAU,OAAO,MAAM;AAC/F,MAAK,MAAM,aAAa,UAAU;AAChC,MAAI,UAAU,SAAS,WAAW,UAAU,eAAe,MAAM;GAC/D,MAAM,YAAY,MAAM,gBAAgB,MAAM,SAAS,KAAK,OAAO,UAAU,YAAY;AACzF,OAAI,aAAa,QAAQ,CAAC,UAAU,WAAW;AAC7C,cAAU,YAAY;AACtB,cAAU,cAAc;AACxB,cAAU,sBAAsB,OAAO;;AAEzC;;AAEF,MAAI,eAAe,UAAU,iBAAiB,KAAM;AAEpD,MADoB,MAAM,aAAa,MAAM,SAAS,KAAK,OAAO,UAAU,cAAc,IACvE,KAAM;EACzB,MAAMC,aAA6B;GACjC,SAAS,OAAO;GAChB,OAAO,OAAO;GACd,OAAO,OAAO;GACd,IAAI,mBAAmB,OAAO,GAAG,GAAG,UAAU;GAC9C,MAAM,UAAU;GAChB,OAAO,UAAU;GACjB,UAAU,OAAO;GACjB,eAAe,UAAU;GACzB,aAAa;GACd;AACD,MAAI,CAAC,MAAM,gBAAgB,MAAM,SAAS,KAAK,OAAO,WAAW,GAAG,CAClE,OAAM,gBAAgB,KAAK,WAAW;;AAG1C,QAAO,SAAS;AAChB,QAAO,uBAAuB,SAAS,KAAK,cAAc,UAAU,GAAG;AACvE,QAAO,aAAa;AACpB,0BAAyB,MAAM;AAC/B,OAAM,YAAY;AAClB,QAAO;;AAGT,SAAgB,aAAa,UAAwB,UAAkB,KAA2B;CAChG,MAAM,QAAQ,WAAW,SAAS;CAClC,MAAM,SAAS,MAAM,QAAQ,MAAM,SAAS,KAAK,OAAO,SAAS;AACjE,KAAI,UAAU,QAAQ,OAAO,WAAW,UAAW,QAAO;AAC1D,QAAO,SAAS;AAChB,QAAO,aAAa;AACpB,OAAM,YAAY;AAClB,QAAO;;;;;ACjjBT,SAAgB,kBAAkB,OAAoC;AACpE,KAAI,SAAS,KAAM,QAAO;CAC1B,MAAM,aAAa,OAAO,UAAU,WAAW,OAAO,MAAM,GAAG,OAAO,UAAU,WAAW,QAAQ;AACnG,QAAO,QAAQ,KAAK,WAAW,GAAG,aAAa;;AAGjD,SAAS,aAAa,OAA8B;AAClD,KAAI,OAAO,UAAU,YAAY,SAAS,KAAM,QAAO,oBAAoB;CAC3E,MAAM,MAAM;AACZ,KAAI,IAAI,kBAAkB,eAAgB,QAAO,oBAAoB;CACrE,MAAM,UAAU,MAAM,QAAQ,IAAI,QAAQ,GACtC,IAAI,QAAQ,KAAK,YAAY;EAC3B,GAAG;EACH,WACE,OAAO,OAAO,cAAc,WACxB,OAAO,YACP,OAAO,QAAQ,IAAI,aAAa,OAAO;EAC7C,UAAU,MAAM,QAAQ,OAAO,SAAS,GAAG,OAAO,WAAW,EAAE;EAChE,EAAE,GACH,EAAE;CACN,MAAM,eACJ,IAAI,gBAAgB,OAChB;EACE,GAAG,IAAI;EACP,UAAU,MAAM,QAAQ,IAAI,aAAa,SAAS,GAAG,IAAI,aAAa,WAAW,EAAE;EACpF,GACD;AACN,QAAO;EACL,GAAG,mBAAmB,OAAO,IAAI,cAAc,WAAW,IAAI,YAAY,EAAE;EAC5E,GAAG;EACH,eAAe;EACf,cAAc,MAAM,QAAQ,IAAI,aAAa,GAAG,IAAI,eAAe,EAAE;EACrE,iBAAiB,MAAM,QAAQ,IAAI,gBAAgB,GAAG,IAAI,kBAAkB,EAAE;EAC9E;EACA,iBAAiB,MAAM,QAAQ,IAAI,gBAAgB,GAAG,IAAI,kBAAkB,EAAE;EAC9E,oBAAoB,MAAM,QAAQ,IAAI,mBAAmB,GAAG,IAAI,qBAAqB,EAAE;EACvF;EACD;;AAGH,IAAa,qBAAb,MAA0D;CACxD,YAAY,AAAiBC,aAAqB;EAArB;;CAE7B,UAAU,UAA0B;EAClC,MAAM,OAAO,kBAAkB,SAAS;AACxC,MAAI,QAAQ,KAAM,OAAM,IAAI,MAAM,mBAAmB;AACrD,wBAAY,KAAK,aAAa,mBAAmB,MAAM,gBAAgB;;CAGzE,KAAK,UAAgC;EACnC,MAAMC,SAAO,KAAK,UAAU,SAAS;AACrC,MAAI,oBAAYA,OAAK,CAAE,QAAO,oBAAoB;AAClD,MAAI;AACF,UAAO,aAAa,KAAK,2BAAmBA,QAAM,OAAO,CAAC,CAAY;UAChE;AACN,UAAO,oBAAoB;;;CAI/B,KAAK,UAAkB,OAA2B;EAChD,MAAMA,SAAO,KAAK,UAAU,SAAS;AAErC,mCADuBA,QAAM,KAAK,EACb,EAAE,WAAW,MAAM,CAAC;EACzC,MAAM,YAAY,GAAGA,OAAK,GAAG,QAAQ,IAAI,GAAG,KAAK,KAAK,CAAC;AACvD,MAAI;AACF,yBAAc,WAAW,GAAG,KAAK,UAAU,OAAO,MAAM,EAAE,CAAC,KAAK,OAAO;AACvE,sBAAW,WAAWA,OAAK;WACpB,OAAO;AACd,OAAI;AACF,2BAAe,UAAU,CAAE,oBAAW,UAAU;WAC1C;AAGR,SAAM;;;;;;;ACpDZ,SAAS,WAAW,OAAgD;AAClE,KAAI,OAAO,UAAU,YAAY,SAAS,KAAM,QAAO;CACvD,MAAM,SAAU,MAA+B;AAC/C,KAAI,OAAO,WAAW,YAAY,UAAU,KAAM,QAAO;CACzD,MAAM,QAAQ;AACd,KAAI,OAAO,MAAM,SAAS,YAAY,OAAO,MAAM,SAAS,SAAU,QAAO;AAC7E,QAAO;EACL,QAAQ,OAAO,MAAM,WAAW,WAAW,MAAM,SAAS;EAC1D,MAAM,MAAM;EACZ,MAAM,SAAS,MAAM,KAAK,IAAI,EAAE;EAChC,UAAU,SAAS,MAAM,SAAS;EAClC,MAAM,MAAM;EACb;;AAGH,SAAS,UAAoC,QAAa,WAAqB;CAC7E,MAAM,SAAS,IAAI,IAAI,OAAO,KAAK,SAAS,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC;AAC7D,MAAK,MAAM,QAAQ,UAAW,QAAO,IAAI,KAAK,IAAI,KAAK;AACvD,QAAO,CAAC,GAAG,OAAO,QAAQ,CAAC;;AAG7B,IAAa,oBAAb,MAA+B;CAC7B,AAAQ,QAAsB,oBAAoB;CAClD,AAAiB,8BAAc,IAAI,KAAiB;CACpD,AAAQ;CACR,AAAQ;CACR,AAAQ,UAAU;CAElB,AAAiB,kBAAkB,UAAyB;EAC1D,MAAM,SAAS,WAAW,MAAM;AAChC,MAAI,UAAU,KAAM;AACpB,MAAI;GACF,MAAM,YAAY,KAAK,uBAAuB;GAC9C,MAAM,aAAa,kBAAkB,oBAAoB,OAAO,CAAC;AACjE,QAAK,aAAa,cAAc,UAAU;GAC1C,MAAM,SAAS,kBAAkB,KAAK,OAAO,QAAQ,KAAK,aAAa,aAAa;AACpF,OAAI,CAAC,OAAO,QAAS;AACrB,QAAK,QAAQ,OAAO;AACpB,QAAK,SAAS;AACd,QAAK,MAAM;AACX,OAAI,OAAO,YACT,KAAI;AACF,SAAK,aAAa,MAAM,YAAY,KAAK,aAAa,SAAS;YACxD,OAAO;AACd,SAAK,OAAO,MAAM;;WAGf,OAAO;AACd,QAAK,OAAO,MAAM;;;CAItB,YAAY,AAAiBC,cAA6C;EAA7C;;CAE7B,MAAM,QAA+B;AACnC,MAAI,KAAK,QAAS;AAClB,OAAK,UAAU;AACf,OAAK,cAAc;AACnB,OAAK,aAAa,KAAK,uBAAuB,CAAC;AAC/C,SAAO,iBAAiB,iBAAiB,KAAK,eAAe;AAC7D,OAAK,MAAM;;CAGb,OAAa;AACX,MAAI,CAAC,KAAK,QAAS;AACnB,OAAK,aAAa,oBAAoB,iBAAiB,KAAK,eAAe;AAC3E,OAAK,cAAc;AACnB,OAAK,UAAU;;CAGjB,oBAAkC,KAAK;CAEvC,aAAa,eAAyC;AACpD,OAAK,YAAY,IAAI,WAAW;AAChC,eAAa,KAAK,YAAY,OAAO,WAAW;;CAGlD,aAAiC;AAC/B,MAAI,KAAK,MAAM,gBAAgB,QAAQ,KAAK,MAAM,aAAa,QAAQ,EACrE,QAAO;GACL,SAAS,KAAK,MAAM,aAAa;GACjC,OAAO,KAAK,MAAM,aAAa;GAC/B,OAAO,KAAK,MAAM,aAAa;GAChC;EAEH,MAAM,SAAS,CAAC,GAAG,KAAK,MAAM,QAAQ,CAAC,SAAS,CAAC,MAAM,SAAS,KAAK,QAAQ,EAAE;AAC/E,MAAI,UAAU,KACZ,QAAO;GAAE,SAAS,OAAO;GAAS,OAAO,OAAO;GAAO,OAAO,OAAO;GAAO;EAE9E,MAAM,cAAc,KAAK,MAAM,aAAa,GAAG,GAAG;AAClD,SAAO,eAAe,OAClB,SACA;GACE,SAAS,YAAY;GACrB,OAAO,YAAY;GACnB,OAAO,YAAY;GACpB;;CAGP,YAAY,MAAc,MAAM,KAAK,YAAY,EAAU;AACzD,MAAI,OAAO,KAAM,QAAO;EACxB,MAAM,SAAS,sBAAsB,KAAK,OAAO,MAAM,KAAK,KAAK,KAAK,CAAC;AACvE,MAAI,OAAO,UAAU,KAAK,MAAO,QAAO,OAAO;AAC/C,OAAK,QAAQ,OAAO;AACpB,OAAK,SAAS;AACd,OAAK,MAAM;AACX,SAAO,OAAO;;CAGhB,QAAQ,UAAkB,cAA8B;EACtD,MAAM,OAAO,cAAc,KAAK,OAAO,UAAU,cAAc,KAAK,KAAK,CAAC;AAC1E,MAAI,SAAS,KAAK,MAAO;AACzB,OAAK,QAAQ;AACb,OAAK,SAAS;AACd,OAAK,MAAM;;CAGb,OAAO,UAAwB;EAC7B,MAAM,OAAO,aAAa,KAAK,OAAO,UAAU,KAAK,KAAK,CAAC;AAC3D,MAAI,SAAS,KAAK,MAAO;AACzB,OAAK,QAAQ;AACb,OAAK,SAAS;AACd,OAAK,MAAM;;CAGb,AAAQ,MAAc;AACpB,SAAO,KAAK,aAAa,OAAO,IAAI,KAAK,KAAK;;CAGhD,AAAQ,wBAA4C;AAClD,MAAI;AACF,UAAO,kBAAkB,KAAK,aAAa,eAAe,CAAC;WACpD,OAAO;AACd,QAAK,OAAO,MAAM;AAClB;;;CAIJ,AAAQ,aAAa,MAAgC;AACnD,MAAI,QAAQ,QAAQ,SAAS,KAAK,SAAU;AAC5C,MAAI,KAAK,YAAY,KAAM,MAAK,SAAS;EACzC,MAAM,YAAY,KAAK,YAAY,OAAO,KAAK,QAAQ;AACvD,OAAK,WAAW;AAChB,MAAI;GACF,MAAM,SAAS,KAAK,aAAa,QAAQ,KAAK,KAAK;AACnD,OAAI,aAAa,QAAS,UAAU,cAAc,KAAK,UAAU,gBAAgB,MAAO;AACtF,SAAK,QAAQ;AACb;;AAEF,QAAK,QAAQ;IACX,GAAG;IACH,cAAc,UAAU,gBAAgB,OAAO;IAC/C,cAAc,UAAU,OAAO,cAAc,UAAU,aAAa;IACpE,iBAAiB,UAAU,OAAO,iBAAiB,UAAU,gBAAgB;IAC7E,SAAS,UAAU,OAAO,SAAS,UAAU,QAAQ;IACrD,iBAAiB,UAAU,OAAO,iBAAiB,UAAU,gBAAgB;IAC7E,oBAAoB,CAAC,GAAG,IAAI,IAAI,CAC9B,GAAG,OAAO,oBACV,GAAG,UAAU,mBACd,CAAC,CAAC,CAAC,MAAM,MAAM;IAChB,WAAW,KAAK,IAAI,OAAO,WAAW,UAAU,UAAU;IAC3D;WACM,OAAO;AACd,QAAK,QAAQ,aAAa,oBAAoB;AAC9C,QAAK,OAAO,MAAM;;;CAItB,AAAQ,UAAgB;AACtB,MAAI,KAAK,YAAY,KAAM;AAC3B,MAAI;AACF,QAAK,aAAa,QAAQ,KAAK,KAAK,UAAU,KAAK,MAAM;WAClD,OAAO;AACd,QAAK,OAAO,MAAM;;;CAItB,AAAQ,OAAa;AACnB,OAAK,MAAM,cAAc,KAAK,YAAa,aAAY;;CAGzD,AAAQ,OAAO,OAAsB;AACnC,OAAK,aAAa,UAAU,MAAM;;;;;;ACvMtC,SAAS,OAAO,OAA0B,IAAqB;AAC7D,KAAI;EACF,MAAM,QAAQC,OAAK,OAAO,aAAa,EAAE;AACzC,MAAI,OAAO,UAAU,WAAY,QAAO;AACxC,QAAM,GAAG;AACT,SAAO;SACD;AACN,SAAO;;;AAIX,SAAgB,mBAAmB,cAAwC;AACzE,QAAO,EACL,YAAY,IAAyB;AACnC,MAAI,OAAO,aAAa,YAAY,GAAG,CAAE,QAAO;AAChD,MAAI,OAAO,aAAa,WAAW,GAAG,CAAE,QAAO;AAC/C,MAAI;AACF,OAAI,OAAO,aAAa,aAAa,YAAY;IAE/C,MAAM,UADe,QAAQ,aAAa,WAAW,4BAA4B,CAAC,GAE9E,EAAE,kBAAkB,IAAI,GACxB;KAAE,eAAe;KAAI,kBAAkB;KAAI;AAC/C,iBAAa,SAAS;KACpB,MAAM;KACN,SAAS;MAAE;MAAS,YAAY;MAAO;KACxC,CAAC;AACF,WAAO;;UAEH;AAGR,MAAI;AACF,OAAI,OAAO,aAAa,YAAY,YAAY;AAC9C,iBAAa,QAAQ,wBAAwB;AAC7C,WAAO;;UAEH;AAGR,SAAO;IAEV;;;;;ACnDH,SAAgB,sBAAsB,YAAqC;AACzE,SAAQ,EAAE,SAAS,OAAO,YAAY;EACpC,MAAM,WAAW,IAAI;AACrB,MAAI;GACF,MAAM,SAASC,WAAS,UAAU;AAClC,OAAI,OAAO,WAAW,YAAY,UAAU,KAAM,QAAO;GACzD,MAAM,MAAO,OAAmC,GAAG,QAAQ,GAAG;AAC9D,OAAI,OAAO,QAAQ,YAAY,OAAO,KAAM,QAAO;GACnD,MAAMC,UAAS,IAAgC;AAC/C,OAAI,OAAOA,YAAU,YAAYA,WAAS,KAAM,QAAO;GACvD,MAAM,OAAQA,QAAkC,OAAO,MAAM;AAC7D,OAAI,CAAC,MAAM,QAAQ,KAAK,IAAI,OAAO,KAAK,OAAO,YAAY,KAAK,GAAG,MAAM,KAAK,GAC5E,QAAO;AAET,UAAO,KAAK;UACN;AACN,UAAO;;;;;;;oBCpBb;UACa;yBACe;QACjB;WACG;YACC;iBACK;UACP;YACE;aACC;QACL;kBACU;aACL;aACA;kBACK;eACH;oBACK;UACV;SACD;UACC;QACF;mBACW;CACpB,UAAU;gBACO;gBACA;UACN;YACE;sBACU;iBACL;cACH;WACH;YACC;cACE;aACD;CACf;;;;AClBD,MAAMC,SAA8C;CAClD,MAAM;EAAE,SAAS;EAAI,QAAQ;EAAQ,UAAU;EAAQ,WAAW;EAAc;CAChF,QAAQ;EAAE,SAAS;EAAQ,gBAAgB;EAAiB,KAAK;EAAI,YAAY;EAAU;CAC3F,SAAS,EAAE,WAAW,IAAI;CAC1B,QAAQ,EAAE,WAAW,IAAI;CACzB,YAAY;EAAE,SAAS;EAAQ,qBAAqB;EAA4C,KAAK;EAAI;CACzG,OAAO;EAAE,SAAS;EAAQ,UAAU;EAAQ,KAAK;EAAG,WAAW;EAAG;CAClE,YAAY;EAAE,SAAS;EAAQ,qBAAqB;EAA2B,KAAK;EAAI,SAAS;EAAU,cAAc;EAAmC;CAC5J,WAAW;EAAE,SAAS;EAAS,cAAc;EAAmC;CAChF,SAAS;EAAE,SAAS;EAAQ,KAAK;EAAG,WAAW;EAAI;CACnD,UAAU;EAAE,SAAS;EAAQ,qBAAqB;EAAiB,KAAK;EAAI,YAAY;EAAU;CAClG,OAAO,EAAE,OAAO,WAAW;CAC5B;AAGD,MAAM,KAAK,QAAyBC,cAAK;AAEzC,SAAS,WAAW,QAAuB,OAAgC;CACzE,MAAM,QAAQ,OAAO,MAAM;CAC3B,MAAM,UAAU,CAAC,MAAM,QAAQ,OAAO,SAAY,GAAG,MAAM,KAAK,GAAG;AACnE,KAAI,MAAM,YAAY,KAAM,SAAQ,KAAK,cAAc,MAAM,SAAS,CAAC;AACvE,KAAI,MAAM,cAAc,MAAM,UAAU,MAAM,SAAS,iBAAiB,CAAE,SAAQ,KAAK,OAAO;AAC9F,MAAK,MAAM,WAAW,MAAM,qBAAqB,EAAE,CACjD,SAAQ,KAAK,OAAO,QAAQ,YAAY,OAAO,KAAK,MAAM,cAAc,QAAQ,SAAS,KAAK;AAEhG,QACE,4CAACC;EAEC,SAAS,UAAU,OAAO,MAAM,SAAS;EACzC,OAAO,IAAI,KAAK,MAAM,UAAU,CAAC,gBAAgB;aAEhD,MAAM,WACN,QAAQ,OAAO,QAAQ,CAAC,SAAS,IAAI,MAAM,QAAQ,OAAO,QAAQ,CAAC,KAAK,MAAM,KAAK;IAL/E,MAAM,GAMP;;AAIV,SAAS,iBAAiB,QAA+B;CACvD,MAAM,YAAY,OAAO,aAAa,OAAO,MAAM,IAAI,aAAa,OAAO;AAC3E,QAAO,GAAG,IAAI,KAAK,UAAU,CAAC,gBAAgB,CAAC,KAAK,IAAI,KAAK,OAAO,WAAW,CAAC,gBAAgB;;AAGlG,SAAS,cAAc,QAAuB,OAA2D;AACvG,KAAI,MAAM,SAAS,QAAQ;EACzB,MAAM,QAAQ,OAAO,MAAM,WAAW,UAAU,MAAM,OAAO,MAAM,YAAY;AAC/E,MAAI,SAAS,EAAG,QAAO,WAAW,QAAQ,MAAM;;CAElD,MAAM,QAAQ,MAAM,SAAS,mBAAmB,SAAS;CACzD,MAAM,QAAQ,CAAC,MAAM,aAAa,OAAO,SAAY,GAAG,MAAM,UAAU,IAAI,MAAM;AAClF,KAAI,MAAM,YAAY,KAAM,OAAM,KAAK,cAAc,MAAM,SAAS,CAAC;AACrE,QACE,2CAACA;EAAmB,QAAQ,MAAM,SAAS,qBAAqB,YAAY;EAAW,OAAO,IAAI,KAAK,MAAM,WAAW,CAAC,gBAAgB;YACtI,MAAM,OAAO,QAAQ,CAAC,KAAK,MAAM;IAD1B,MAAM,GAEV;;AAIV,SAAS,eAAe,QAAwC;CAC9D,MAAM,WAAW,OAAO,YAAY,EAAE;AACtC,KAAI,SAAS,SAAS,EACpB,QAAO,2CAAC;EAAI,OAAO,OAAO;YAAQ,SAAS,KAAK,UAAU,cAAc,QAAQ,MAAM,CAAC;GAAO;AAEhG,KAAI,OAAO,MAAM,SAAS,EACxB,QAAO,2CAAC;EAAI,OAAO,OAAO;YAAQ,OAAO,MAAM,KAAK,GAAG,UAAU,WAAW,QAAQ,MAAM,CAAC;GAAO;AAEpG,QAAO,2CAAC;EAAK,OAAO,OAAO;YAAQ,EAAE,aAAa;GAAQ;;AAG5D,SAAS,YAAY,EACnB,QACA,UACA,aACA,4BAMC;CACD,MAAM,oBAAoB,OAAO,WAAW,MAAM,cAAc,UAAU,OAAO,SAAS;AAC1F,QACE,4CAACC;EAAK,OAAO,OAAO;EAAQ,WAAWC,6BAAU;aAC/C,4CAAC;GAAI,OAAO,OAAO;cACjB,4CAAC;IACC,2CAAC,sBAAQ,OAAO,QAAQ,IAAI,SAAS,OAAO,GAAG,EAAE,aAAa,GAAU;IAAC;IACzE,2CAACF;KAAI,QAAO;eAAW,EAAE,UAAU;MAAO;IAC1C,2CAAC;KAAI,OAAO,OAAO;eAAQ,iBAAiB,OAAO;MAAO;OACtD,EACN,2CAAC;IAAI,OAAO,OAAO;cAChB,OAAO,eAAe,KAAK,WAC1B,2CAACA;KAAiB;eACf;OADO,OAEJ,CACN;KACE;IACF,EACN,4CAAC;GAAI,OAAO,OAAO;cACjB,4CAAC;IACC,2CAAC,kBAAI,EAAE,QAAQ,GAAM;IACpB,CAAC,OAAO,iBAAiB,2CAACG;KAAQ,QAAO;eAAW,EAAE,kBAAkB;MAAW;IACnF,eAAe,OAAO;OACnB,EACN,4CAAC;IACC,2CAAC,kBAAI,EAAE,aAAa,GAAM;IACzB,OAAO,WAAW,WAAW,IAC5B,2CAACA,wCAAS,EAAE,eAAe,GAAW,GAEtC,qFACE,4CAACC;KACC;KACA,OAAO;KACP,WAAW,UAAU,YAAY,MAAM,cAAc,MAAM;gBAE3D,2CAAC;MAAO,OAAM;gBAAI,EAAE,kBAAkB;OAAU,EAC/C,OAAO,WAAW,KAAK,cACtB,4CAAC;MAA0B,OAAO,UAAU;;OACzC,UAAU,kBAAkB,OAAO;OAAI,UAAU;OACjD,UAAU,iBAAiB,OACxB,KACA,MAAM,EAAE,WAAW,CAAC,GAAG,UAAU,cAAc,GAAG,UAAU;;QAJrD,UAAU,GAKd,CACT;MACS,EACZ,mBAAmB,mBAClB,2CAACD;KAAQ,OAAO,EAAE,WAAW,GAAG;KAAE,QAAO;eAAW,EAAE,oBAAoB;MAAW,IAEtF;IAEL,4CAAC;KAAI,OAAO,OAAO;gBACjB,2CAACE;MACC,QAAO;MACP,UAAU,aAAa;MACvB,eAAeC,aAAW,QAAQ,OAAO,IAAI,CAAC,SAAS,CAAC;gBAEvD,EAAE,UAAU;OACN,EACT,2CAACD;MAAO,eAAeC,aAAW,OAAO,OAAO,GAAG;gBAAG,EAAE,SAAS;OAAU;MACvE;OACF;IACF;GACD;;AAIX,SAAgB,kBAAkB,cAAoD;AACpF,QAAO,SAAS,qBAAqB;EACnC,MAAM,wCAA6BA,aAAW,WAAWA,aAAW,aAAaA,aAAW,YAAY;EACxG,MAAM,CAAC,WAAW,oCAAyB,GAAG;EAC9C,MAAM,CAAC,SAAS,kCAAuB,GAAG;EAC1C,MAAM,CAAC,UAAU,mCAAgD,EAAE,CAAC;EACpE,MAAMC,YAAU,MAAM,QAAQ,QAAQ,WAAW,OAAO,WAAW,UAAU;EAC7E,MAAM,aAAaD,aAAW,YAAY;EAC1C,MAAM,kCAEF,CAAC,GAAG,IAAI,IAAI;GACV,GAAG,MAAM,gBAAgB,KAAK,SAAS,KAAK,MAAM;GAClD,GAAG,MAAM,gBAAgB,KAAK,SAAS,KAAK,MAAM;GAClD,GAAG,MAAM,QAAQ,QAAQ,SAAS,KAAK,QAAQ,EAAE,CAAC,KAAK,SAAS,KAAK,MAAM;GAC5E,CAAC,CAAC,CAAC,MAAM,MAAM,UAAU,OAAO,MAAM,EACzC;GAAC,MAAM;GAAiB,MAAM;GAAiB,MAAM;GAAQ,CAC9D;EAED,MAAM,oBAAoB;AACxB,OAAI,cAAc,MAAM;AACtB,eAAW,EAAE,gBAAgB,CAAC;AAC9B;;GAEF,MAAM,QAAQA,aAAW,YAAY,WAAW,WAAW;AAC3D,cAAW,QAAQ,IAAI,EAAE,gBAAgB,CAAC,QAAQ,WAAW,OAAO,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC;;AAG7F,SACE,4CAAC;GAAK,OAAO,OAAO;;IAClB,4CAAC;KAAO,OAAO,OAAO;gBACpB,4CAAC,oBACC,2CAAC;MAAG,OAAO,EAAE,QAAQ,GAAG;gBAAG,EAAE,QAAQ;OAAM,EAC3C,2CAAC;MAAE,OAAO;OAAE,GAAG,OAAO;OAAO,cAAc;OAAG;gBAAG,EAAE,WAAW;OAAK,IAC/D,EACL,cAAc,QAAQ,4CAACN;MAAI;;OAAO,EAAE,YAAY;OAAC;OAAG,SAAS,WAAW;;OAAO;MACzE;IAET,4CAAC;KAAQ,OAAO,OAAO;gBACrB,2CAAC,kBAAI,EAAE,iBAAiB,GAAM,EAC7BO,UAAQ,WAAW,IAClB,2CAACJ,wCAAS,EAAE,YAAY,GAAW,GAEnCI,UAAQ,KAAK,WACX,2CAAC;MAES;MACR,YAAYD;MACZ,UAAU,SAAS,OAAO,OAAO;MACjC,cAAc,OAAO,aAAa,WAAW;OAAE,GAAG;QAAQ,OAAO,KAAK;OAAI,EAAE;QAJvE,OAAO,GAKZ,CACF;MAEI;IAEV,4CAAC;KAAQ,OAAO,OAAO;;MACrB,2CAAC,kBAAI,EAAE,QAAQ,GAAM;MACrB,2CAACE;OACC;OACA,MAAM;OACN,OAAO;OACP,aAAa,EAAE,mBAAmB;OAClC,WAAW,UAAU,aAAa,MAAM,cAAc,MAAM;QAC5D;MACF,4CAAC;OAAI,OAAO,OAAO;kBACjB,2CAACH;QAAO,QAAO;QAAU,SAAS;kBAAc,EAAE,SAAS;SAAU,EACpE,YAAY,MAAM,2CAAC;QAAK,OAAO,OAAO;kBAAQ;SAAe;QAC1D;;MACE;IAEV,4CAAC;KAAQ,OAAO,OAAO;gBACrB,2CAAC,kBAAI,EAAE,YAAY,GAAM,EACxB,OAAO,WAAW,IACjB,2CAACF,wCAAS,EAAE,cAAc,GAAW,GAErC,OAAO,KAAK,UAAU;MACpB,MAAM,SAAS,MAAM,gBAAgB,QAAQ,SAAS,KAAK,UAAU,MAAM;MAC3E,MAAM,OAAO,MAAM,gBAAgB,QAAQ,SAAS,KAAK,UAAU,MAAM;MACzE,MAAM,UAAU,MAAM,QAAQ,QAAQ,SAAS,KAAK,UAAU,MAAM;MACpE,MAAM,aAAa,QAAQ,GAAG,GAAG;MACjC,MAAM,MAAM,OAAO,MAAM,KAAK,MAAM;AACpC,aACE,4CAACF;OAAiB,OAAO,OAAO;OAAQ,WAAWC,6BAAU;;QAC3D,2CAAC,kBAAI,OAAO,OAAO,IAAI,UAAU,SAAS,IAAI,GAAM;QACnD,QAAQ,SAAS,KAChB,4CAAC;SAAI,OAAO,EAAE,cAAc,IAAI;oBAC9B,2CAAC,sBAAQ,EAAE,iBAAiB,GAAU,EACrC,CAAC,GAAG,QAAQ,CAAC,SAAS,CAAC,KAAK,WAAW;UACtC,MAAM,iBAAiB,OAAO,WAC3B,QAAQ,cAAc,OAAO,sBAAsB,SAAS,UAAU,GAAG,CAAC,CAC1E,KAAK,cAAc,UAAU,MAAM;AACtC,iBACE,4CAAC;WAAoB,OAAO,OAAO;sBACjC,2CAAC,mBAAK,eAAe,OAAO,GAAO,EACnC,4CAAC;YAAI,OAAO,EAAE,WAAW,SAAS;;aAChC,2CAAC;cAAI,OAAO,OAAO;wBAAQ,iBAAiB,OAAO;eAAO;aAC1D,2CAACF;cACC,QAAQ,OAAO,WAAW,cAAc,YAAY,OAAO,WAAW,YAAY,YAAY;wBAE7F,OAAO,WAAW,cACf,EAAE,YAAY,GACd,OAAO,WAAW,YAChB,EAAE,UAAU,GACZ,EAAE,UAAU;eACd;aACL,eAAe,SAAS,KAAK,2CAAC,mBAAK,eAAe,KAAK,IAAI,GAAO;;aAC/D;aAdE,OAAO,GAeX;WAER;UACE;QAER,2CAACS;SAAU;SAAQ;SAAQ,OAAO,EAAE,OAAO,QAAQ;mBACjD,4CAAC,sBACE,OAAO,KAAK,cACX,4CAAC;UACC,2CAAC,kBAAI,oBAAoB,UAAU,GAAM;UACzC,4CAAC;WAAI,EAAE,WAAW;WAAC;WAAE,UAAU;WAAc;WAAE,UAAU;cAAmB;UAC5E,2CAAC,kBACC,2CAACT;WAAI,QAAQ,UAAU,YAAY,YAAY;qBAC5C,UAAU,YAAY,EAAE,YAAY,GAAG,EAAE,cAAc;YACpD,GACH;cAPE,UAAU,GAQd,CACL,EACD,KAAK,KAAK,eACT,4CAAC;UACC,2CAAC,kBAAI,WAAW,QAAW;UAC3B,2CAAC,kBAAI,EAAE,aAAa,GAAM;UAC1B,2CAAC,kBAAG,2CAACA;WAAI,QAAO;qBAAW,EAAE,YAAY;YAAO,GAAK;cAH9C,WAAW,GAIf,CACL,IACI;UACE;;SAnDH,MAoDJ;OAET;MAEI;;IACL;;;;;;AChSb,MAAM,aAAa,IAAI,kBAAkB;CACvC,SAAS,IAAI,mBAFK,OAAO,gBAAgBU,uBAEG;CAC5C,OAAO,mBAAmB;EACxB,YAAYC;EACZ,WAAW,OAAO;EAClB;EACA,WAAW,4CAAkBC,OAAK;EAClC,SAAS,OAAO;EACjB,CAAC;CACF,UAAU;CACV,cAAc,uBAAuB,4CAAkBA,OAAK,CAAC;CAC7D,oDAA4B,2BAA2B;CACvD,UAAU,UAAU,QAAQ,MAAM,IAAI,UAAU,IAAI,MAAM;CAC3D,CAAC;AAEF,MAAa,aAAa,kBAAkB,WAAW;AACvD,MAAa,aAAa;AAE1B,SAAgB,gBAAsB;AACpC,YAAW,MAAM,OAAO;;AAG1B,SAAgB,mBAAyB;AACvC,YAAW,MAAM"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["AIR_TEXT_TO_STATE: Record<string, AirState>","parsed: ParsedGuideCondition[]","match: RegExpExecArray | null","RANK_SCORE: Record<VictoryRank, number>","AIR_LABEL: Record<AirState, string>","parsed: unknown","path","visit","battle: BattleReference","condition","condition: GuideCondition","completion: FreeCompletion","appDataPath: string","path","dependencies: TrackerControllerDependencies","ipc","route","styles: Record<string, React.CSSProperties>","zhCN","Tag","Card","Elevation","Callout","HTMLSelect","Button","controller","pending","TextArea","HTMLTable","APPDATA_PATH","path","ipc"],"sources":["src/core/guide-parser.ts","src/core/conditions.ts","src/core/events.ts","src/core/types.ts","src/core/state-machine.ts","src/storage/index.ts","src/controller.ts","src/host/focus.ts","src/host/node-resolver.ts","i18n/zh-CN.json","src/ui/index.tsx","index-src.ts"],"sourcesContent":["import type { AirState, ConditionKind, VictoryRank } from './types'\n\nexport interface ParsedGuideCondition {\n kind: ConditionKind\n rawText: string\n nodeLabel?: string\n requiredRank?: VictoryRank\n requiredAirState?: AirState\n requiredCount: number\n}\n\nconst AIR_TEXT_TO_STATE: Record<string, AirState> = {\n 空确: 1,\n 空優: 2,\n 空优: 2,\n 空均: 3,\n 空劣: 4,\n}\n\nconst TOKEN_PATTERN =\n /([A-Za-z]\\d*)\\s*点\\s*(到达|到達)\\s*(\\d+)\\s*次|([A-Za-z]\\d*)\\s*点\\s*(SS|S|A|B|C|D|E)\\s*胜\\s*(\\d+)\\s*次|([A-Za-z]\\d*)\\s*点\\s*(空确|空優|空优|空均|空劣)\\s*(\\d+)\\s*次|(守家|基地防空)\\s*(空确|空優|空优|空均|空劣)\\s*(\\d+)\\s*次/gi\n\nexport function normalizeNodeLabel(value: string): string {\n return value.trim().toUpperCase()\n}\n\nexport function parseGuideText(text: string): ParsedGuideCondition[] {\n const normalized = text.replace(/[,;]/g, (value) => (value === ',' ? ',' : ';'))\n const parsed: ParsedGuideCondition[] = []\n let match: RegExpExecArray | null\n TOKEN_PATTERN.lastIndex = 0\n\n while ((match = TOKEN_PATTERN.exec(normalized)) !== null) {\n const rawText = match[0].replace(/\\s+/g, '')\n if (match[1] != null) {\n parsed.push({\n kind: 'node-arrival',\n nodeLabel: normalizeNodeLabel(match[1]),\n requiredCount: Number(match[3]),\n rawText,\n })\n continue\n }\n if (match[4] != null) {\n parsed.push({\n kind: 'victory-rank',\n nodeLabel: normalizeNodeLabel(match[4]),\n requiredRank: match[5].toUpperCase() as VictoryRank,\n requiredCount: Number(match[6]),\n rawText,\n })\n continue\n }\n if (match[7] != null) {\n parsed.push({\n kind: 'node-air-state',\n nodeLabel: normalizeNodeLabel(match[7]),\n requiredAirState: AIR_TEXT_TO_STATE[match[8]],\n requiredCount: Number(match[9]),\n rawText,\n })\n continue\n }\n parsed.push({\n kind: 'base-air-defense',\n requiredAirState: AIR_TEXT_TO_STATE[match[11]],\n requiredCount: Number(match[12]),\n rawText,\n })\n }\n\n return parsed.filter((item) => Number.isInteger(item.requiredCount) && item.requiredCount > 0)\n}\n","import type { ParsedGuideCondition } from './guide-parser'\nimport { normalizeNodeLabel } from './guide-parser'\nimport type {\n AirState,\n GuideCondition,\n MapRef,\n Observation,\n ReviewCandidate,\n TrackerState,\n VictoryRank,\n} from './types'\n\nconst RANK_SCORE: Record<VictoryRank, number> = {\n E: 0,\n D: 1,\n C: 2,\n B: 3,\n A: 4,\n S: 5,\n SS: 6,\n}\n\nconst AIR_LABEL: Record<AirState, string> = {\n 1: '空确',\n 2: '空优',\n 3: '空均',\n 4: '空劣',\n}\n\nexport function airStateLabel(state: AirState): string {\n return AIR_LABEL[state]\n}\n\nexport function meetsRank(actual: VictoryRank, required: VictoryRank): boolean {\n return RANK_SCORE[actual] >= RANK_SCORE[required]\n}\n\nexport function meetsAirState(actual: AirState, required: AirState): boolean {\n return actual <= required\n}\n\nexport function conditionSignature(\n map: Pick<MapRef, 'mapId'>,\n item: Pick<\n ParsedGuideCondition,\n 'kind' | 'nodeLabel' | 'requiredRank' | 'requiredAirState'\n >,\n): string {\n return [\n map.mapId,\n item.kind,\n item.nodeLabel == null ? '' : normalizeNodeLabel(item.nodeLabel),\n item.requiredRank ?? '',\n item.requiredAirState ?? '',\n ].join(':')\n}\n\nexport function observationMatches(condition: GuideCondition, observation: Observation): boolean {\n if (condition.mapId !== observation.mapId || condition.kind !== observation.kind) return false\n if (\n condition.nodeLabel != null &&\n normalizeNodeLabel(condition.nodeLabel) !== normalizeNodeLabel(observation.nodeLabel ?? '')\n ) {\n return false\n }\n if (condition.kind === 'victory-rank') {\n return (\n observation.kind === 'victory-rank' &&\n condition.requiredRank != null &&\n meetsRank(observation.rank, condition.requiredRank)\n )\n }\n if (condition.kind === 'node-air-state' || condition.kind === 'base-air-defense') {\n return (\n (observation.kind === 'node-air-state' || observation.kind === 'base-air-defense') &&\n condition.requiredAirState != null &&\n meetsAirState(observation.airState, condition.requiredAirState)\n )\n }\n return observation.kind === 'node-arrival'\n}\n\nexport function guideConditionLabel(condition: GuideCondition): string {\n const node = condition.nodeLabel == null ? '' : `${condition.nodeLabel}点`\n switch (condition.kind) {\n case 'node-arrival':\n return `${node}到达 ${condition.requiredCount}次`\n case 'victory-rank':\n return `${node}${condition.requiredRank}胜 ${condition.requiredCount}次`\n case 'node-air-state':\n return `${node}${airStateLabel(condition.requiredAirState!)} ${condition.requiredCount}次`\n case 'base-air-defense':\n return `守家${airStateLabel(condition.requiredAirState!)} ${condition.requiredCount}次`\n }\n}\n\nexport function observationLabel(observation: Observation): string {\n switch (observation.kind) {\n case 'node-arrival':\n return `${observation.nodeLabel}点到达`\n case 'victory-rank':\n return `${observation.nodeLabel}点${observation.rank}胜`\n case 'node-air-state':\n return `${observation.nodeLabel}点${airStateLabel(observation.airState)}`\n case 'base-air-defense':\n return `守家${airStateLabel(observation.airState)}`\n }\n}\n\nexport function buildReviewCandidates(\n state: TrackerState,\n mapId: number,\n observationIds: string[],\n): ReviewCandidate[] {\n const evidence = state.observations.filter((item) => observationIds.includes(item.id))\n const guides = state.guideConditions.filter(\n (condition) => condition.mapId === mapId && !condition.confirmed,\n )\n if (guides.length > 0) {\n return guides\n .map((condition) => ({\n id: `guide:${condition.id}`,\n mode: 'guide' as const,\n kind: condition.kind,\n label: guideConditionLabel(condition),\n conditionId: condition.id,\n matchedInSortie: evidence.some((item) => observationMatches(condition, item)),\n observedCount: condition.observedCount,\n requiredCount: condition.requiredCount,\n }))\n .sort((left, right) => Number(right.matchedInSortie) - Number(left.matchedInSortie))\n }\n\n return evidence.map((observation) => ({\n id: `free:${observation.id}`,\n mode: 'free' as const,\n kind: observation.kind,\n label: observationLabel(observation),\n observationId: observation.id,\n matchedInSortie: true,\n }))\n}\n","import type { AirState, GameResponseDetail, MapRef, VictoryRank } from './types'\n\nexport const MAP_PATHS = new Set([\n '/kcsapi/api_req_map/start',\n '/kcsapi/api_req_map/next',\n '/kcsapi/api_req_map/air_raid',\n])\n\nconst BATTLE_SUFFIXES = new Set([\n 'battle',\n 'airbattle',\n 'ld_airbattle',\n 'night_to_day',\n 'sp_midnight',\n 'midnight',\n 'midnight_battle',\n 'battle_water',\n 'each_battle',\n 'each_battle_water',\n 'ec_battle',\n 'ec_midnight_battle',\n 'ec_night_to_day',\n])\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nexport function asRecord(value: unknown): Record<string, unknown> | undefined {\n if (isRecord(value)) return value\n if (typeof value !== 'string') return undefined\n try {\n const parsed: unknown = JSON.parse(value)\n return isRecord(parsed) ? parsed : undefined\n } catch {\n return undefined\n }\n}\n\nexport function asNumber(value: unknown): number | undefined {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim() !== '') {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : undefined\n }\n return undefined\n}\n\nexport function isRelevantPath(path: string): boolean {\n return (\n path === '/kcsapi/api_port/port' ||\n MAP_PATHS.has(path) ||\n isBattlePath(path) ||\n isBattleResultPath(path)\n )\n}\n\nexport function isBattleResultPath(path: string): boolean {\n return path.startsWith('/kcsapi/') && path.endsWith('/battleresult')\n}\n\nexport function isBattlePath(path: string): boolean {\n if (!/^\\/kcsapi\\/api_req_(sortie|combined_battle|battle_midnight)\\//.test(path)) return false\n return BATTLE_SUFFIXES.has(path.slice(path.lastIndexOf('/') + 1))\n}\n\nexport function eventKey(detail: GameResponseDetail): string {\n return `${detail.path}|${detail.time}`\n}\n\nexport function extractMapRef(\n detail: GameResponseDetail,\n fallback?: Pick<MapRef, 'mapArea' | 'mapNo'>,\n): MapRef {\n const mapArea =\n asNumber(detail.body.api_maparea_id) ??\n asNumber(detail.postBody?.api_maparea_id) ??\n fallback?.mapArea ??\n 0\n const mapNo =\n asNumber(detail.body.api_mapinfo_no) ??\n asNumber(detail.postBody?.api_mapinfo_no) ??\n fallback?.mapNo ??\n 0\n return { mapArea, mapNo, mapId: mapArea * 10 + mapNo }\n}\n\nexport function extractNodeNo(body: Record<string, unknown>): number | undefined {\n return asNumber(body.api_no)\n}\n\nexport function extractBattleAirState(body: Record<string, unknown>): AirState | undefined {\n const kouku = asRecord(body.api_kouku)\n const stage1 = asRecord(kouku?.api_stage1)\n const value = asNumber(stage1?.api_disp_seiku)\n return value != null && value >= 1 && value <= 4 ? (value as AirState) : undefined\n}\n\nexport function extractVictoryRank(body: Record<string, unknown>): VictoryRank | undefined {\n const value = typeof body.api_win_rank === 'string' ? body.api_win_rank.toUpperCase() : ''\n return ['SS', 'S', 'A', 'B', 'C', 'D', 'E'].includes(value)\n ? (value as VictoryRank)\n : undefined\n}\n\nexport function extractDestructionBattles(body: Record<string, unknown>): Record<string, unknown>[] {\n const raw = body.api_destruction_battle\n const values = Array.isArray(raw) ? raw : raw == null ? [] : [raw]\n return values.map(asRecord).filter((item): item is Record<string, unknown> => item != null)\n}\n\nexport function extractDefenseAirState(entry: Record<string, unknown>): AirState | undefined {\n const attack = asRecord(entry.api_air_base_attack)\n const stage1 = asRecord(attack?.api_stage1)\n const value = asNumber(stage1?.api_disp_seiku)\n return value != null && value >= 1 && value <= 4 ? (value as AirState) : undefined\n}\n\nexport function hasPortSignal(detail: GameResponseDetail): boolean {\n const eventObject = asRecord(detail.body.api_event_object)\n return detail.path === '/kcsapi/api_port/port' && asNumber(eventObject?.api_m_flag2) === 1\n}\n\nexport function hasBattleResultSignal(detail: GameResponseDetail): boolean {\n return isBattleResultPath(detail.path) && asNumber(detail.body.api_m2) === 1\n}\n\nexport function hasDefenseSignal(entry: Record<string, unknown>): boolean {\n return asNumber(entry.api_m2) === 1\n}\n\nexport function extractPortMemberId(detail: GameResponseDetail): unknown {\n if (detail.path !== '/kcsapi/api_port/port') return undefined\n return asRecord(detail.body.api_basic)?.api_member_id\n}\n","export const PLUGIN_ID = 'poi-plugin-gimmick-tracker'\nexport const SCHEMA_VERSION = 1 as const\n\nexport type AirState = 1 | 2 | 3 | 4\nexport type VictoryRank = 'SS' | 'S' | 'A' | 'B' | 'C' | 'D' | 'E'\nexport type ConditionKind =\n | 'node-arrival'\n | 'victory-rank'\n | 'node-air-state'\n | 'base-air-defense'\n\nexport interface MapRef {\n mapArea: number\n mapNo: number\n mapId: number\n}\n\nexport interface BattleReference {\n timestamp: number\n path: string\n kind?: 'battle' | 'fleet-air-raid' | 'base-air-defense'\n}\n\nexport interface BaseDefenseEvent {\n timestamp: number\n path: string\n airState?: AirState\n}\n\nexport type TimelineEventKind = 'node' | 'fleet-air-raid' | 'base-air-defense'\n\nexport interface SortieTimelineEvent {\n id: string\n kind: TimelineEventKind\n occurredAt: number\n sourcePath: string\n nodeVisitId?: string\n nodeNo?: number\n nodeLabel?: string\n airState?: AirState\n}\n\nexport interface NodeVisit extends MapRef {\n id: string\n nodeNo: number\n nodeLabel: string\n arrivedAt: number\n sourcePath: string\n battle?: BattleReference\n battleEvents?: BattleReference[]\n baseDefenseEvents?: BaseDefenseEvent[]\n rank?: VictoryRank\n airState?: AirState\n}\n\ninterface ObservationBase extends MapRef {\n id: string\n kind: ConditionKind\n observedAt: number\n sourcePath: string\n sortieId: string\n nodeNo?: number\n nodeLabel?: string\n battle?: BattleReference\n}\n\nexport interface NodeArrivalObservation extends ObservationBase {\n kind: 'node-arrival'\n nodeNo: number\n nodeLabel: string\n}\n\nexport interface VictoryObservation extends ObservationBase {\n kind: 'victory-rank'\n nodeNo: number\n nodeLabel: string\n rank: VictoryRank\n}\n\nexport interface NodeAirObservation extends ObservationBase {\n kind: 'node-air-state'\n nodeNo: number\n nodeLabel: string\n airState: AirState\n}\n\nexport interface BaseAirDefenseObservation extends ObservationBase {\n kind: 'base-air-defense'\n airState: AirState\n}\n\nexport type Observation =\n | NodeArrivalObservation\n | VictoryObservation\n | NodeAirObservation\n | BaseAirDefenseObservation\n\nexport interface GuideCondition extends MapRef {\n id: string\n kind: ConditionKind\n rawText: string\n nodeLabel?: string\n requiredRank?: VictoryRank\n requiredAirState?: AirState\n requiredCount: number\n observedCount: number\n confirmed: boolean\n confirmedAt?: number\n confirmedByReviewId?: string\n createdAt: number\n}\n\nexport interface ReviewCandidate {\n id: string\n mode: 'guide' | 'free'\n kind: ConditionKind\n label: string\n conditionId?: string\n observationId?: string\n matchedInSortie: boolean\n observedCount?: number\n requiredCount?: number\n}\n\nexport type TriggerSource = 'port' | 'battleresult' | 'air-defense'\nexport type ReviewStatus = 'pending' | 'confirmed' | 'ignored'\n\nexport interface PendingReview extends MapRef {\n id: string\n sortieId: string\n startedAt: number\n detectedAt: number\n triggerSources: TriggerSource[]\n sealed: boolean\n routeComplete: boolean\n route: NodeVisit[]\n timeline: SortieTimelineEvent[]\n observationIds: string[]\n candidates: ReviewCandidate[]\n status: ReviewStatus\n selectedCandidateIds?: string[]\n resolvedAt?: number\n}\n\nexport interface FreeCompletion extends MapRef {\n id: string\n kind: ConditionKind\n label: string\n reviewId: string\n observationId: string\n confirmedAt: number\n}\n\nexport interface ActiveSortie extends MapRef {\n id: string\n startedAt: number\n captureComplete: boolean\n route: NodeVisit[]\n timeline: SortieTimelineEvent[]\n observationIds: string[]\n lastBattle?: BattleReference\n}\n\nexport interface TrackerState {\n schemaVersion: typeof SCHEMA_VERSION\n activeSortie?: ActiveSortie\n observations: Observation[]\n guideConditions: GuideCondition[]\n reviews: PendingReview[]\n freeCompletions: FreeCompletion[]\n processedEventKeys: string[]\n updatedAt: number\n}\n\nexport interface GameResponseDetail {\n method?: string\n path: string\n body: Record<string, unknown>\n postBody?: Record<string, unknown>\n time: number\n}\n\nexport type NodeResolver = (input: {\n mapArea: number\n mapNo: number\n apiNo: number\n}) => string\n\nexport function createInitialState(now = 0): TrackerState {\n return {\n schemaVersion: SCHEMA_VERSION,\n observations: [],\n guideConditions: [],\n reviews: [],\n freeCompletions: [],\n processedEventKeys: [],\n updatedAt: now,\n }\n}\n\nexport function cloneState(state: TrackerState): TrackerState {\n return JSON.parse(JSON.stringify(state)) as TrackerState\n}\n\nexport function mapLabel(map: Pick<MapRef, 'mapArea' | 'mapNo' | 'mapId'>): string {\n return map.mapArea > 0 && map.mapNo > 0 ? `${map.mapArea}-${map.mapNo}` : `地图 #${map.mapId}`\n}\n","import {\n buildReviewCandidates,\n conditionSignature,\n guideConditionLabel,\n observationLabel,\n observationMatches,\n} from './conditions'\nimport {\n eventKey,\n extractBattleAirState,\n extractDefenseAirState,\n extractDestructionBattles,\n extractMapRef,\n extractNodeNo,\n extractVictoryRank,\n hasBattleResultSignal,\n hasDefenseSignal,\n hasPortSignal,\n isBattlePath,\n isBattleResultPath,\n isRelevantPath,\n MAP_PATHS,\n} from './events'\nimport { parseGuideText } from './guide-parser'\nimport type {\n ActiveSortie,\n BaseAirDefenseObservation,\n BattleReference,\n FreeCompletion,\n GameResponseDetail,\n GuideCondition,\n MapRef,\n NodeAirObservation,\n NodeArrivalObservation,\n NodeResolver,\n Observation,\n PendingReview,\n TrackerState,\n TriggerSource,\n VictoryObservation,\n} from './types'\nimport { cloneState } from './types'\n\nexport interface TransitionResult {\n state: TrackerState\n changed: boolean\n shouldFocus: boolean\n}\n\nexport interface ImportGuideResult {\n state: TrackerState\n importedCount: number\n}\n\nconst MAX_PROCESSED_EVENT_KEYS = 2048\n\nfunction safeNodeLabel(resolver: NodeResolver, map: MapRef, apiNo: number): string {\n try {\n const value = resolver({ mapArea: map.mapArea, mapNo: map.mapNo, apiNo }).trim()\n return value === '' ? `#${apiNo}` : value\n } catch {\n return `#${apiNo}`\n }\n}\n\nfunction createSortie(\n detail: GameResponseDetail,\n map: MapRef,\n captureComplete: boolean,\n): ActiveSortie {\n return {\n ...map,\n id: `${captureComplete ? 'sortie' : 'partial'}:${map.mapId}:${detail.time}`,\n startedAt: detail.time,\n captureComplete,\n route: [],\n timeline: [],\n observationIds: [],\n }\n}\n\nfunction appendObservation(\n state: TrackerState,\n sortie: ActiveSortie,\n observation: Observation,\n): boolean {\n if (state.observations.some((item) => item.id === observation.id)) return false\n state.observations.push(observation)\n sortie.observationIds.push(observation.id)\n for (const condition of state.guideConditions) {\n if (observationMatches(condition, observation)) condition.observedCount += 1\n }\n return true\n}\n\nfunction currentNode(sortie: ActiveSortie) {\n return sortie.route.at(-1)\n}\n\nfunction addNodeVisit(\n state: TrackerState,\n sortie: ActiveSortie,\n detail: GameResponseDetail,\n nodeResolver: NodeResolver,\n): void {\n const nodeNo = extractNodeNo(detail.body)\n if (nodeNo == null) return\n const id = `${eventKey(detail)}:node`\n if (sortie.route.some((visit) => visit.id === id)) return\n const nodeLabel = safeNodeLabel(nodeResolver, sortie, nodeNo)\n const visit = {\n id,\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n nodeNo,\n nodeLabel,\n arrivedAt: detail.time,\n sourcePath: detail.path,\n }\n sortie.route.push(visit)\n sortie.timeline.push({\n id: `${id}:timeline`,\n kind: 'node',\n occurredAt: detail.time,\n sourcePath: detail.path,\n nodeVisitId: id,\n nodeNo,\n nodeLabel,\n })\n const observation: NodeArrivalObservation = {\n id: `${id}:arrival`,\n kind: 'node-arrival',\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n nodeNo,\n nodeLabel,\n observedAt: detail.time,\n sourcePath: detail.path,\n sortieId: sortie.id,\n }\n appendObservation(state, sortie, observation)\n}\n\nfunction addBattle(\n state: TrackerState,\n sortie: ActiveSortie,\n detail: GameResponseDetail,\n): void {\n const battle: BattleReference = {\n timestamp: detail.time,\n path: detail.path,\n kind: detail.path.endsWith('/ld_airbattle') ? 'fleet-air-raid' : 'battle',\n }\n sortie.lastBattle = battle\n const node = currentNode(sortie)\n if (node != null) {\n node.battle = battle\n ;(node.battleEvents ??= []).push(battle)\n }\n const airState = extractBattleAirState(detail.body)\n if (battle.kind === 'fleet-air-raid') {\n sortie.timeline.push({\n id: `${eventKey(detail)}:fleet-air-raid`,\n kind: 'fleet-air-raid',\n occurredAt: detail.time,\n sourcePath: detail.path,\n nodeVisitId: node?.id,\n nodeNo: node?.nodeNo,\n nodeLabel: node?.nodeLabel,\n airState,\n })\n }\n if (node == null || airState == null) return\n node.airState = airState\n const observation: NodeAirObservation = {\n id: `${eventKey(detail)}:node-air`,\n kind: 'node-air-state',\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n nodeNo: node.nodeNo,\n nodeLabel: node.nodeLabel,\n airState,\n observedAt: detail.time,\n sourcePath: detail.path,\n sortieId: sortie.id,\n battle,\n }\n appendObservation(state, sortie, observation)\n}\n\nfunction addBattleResult(\n state: TrackerState,\n sortie: ActiveSortie,\n detail: GameResponseDetail,\n): void {\n const rank = extractVictoryRank(detail.body)\n const node = currentNode(sortie)\n if (rank == null || node == null) return\n node.rank = rank\n const observation: VictoryObservation = {\n id: `${eventKey(detail)}:victory`,\n kind: 'victory-rank',\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n nodeNo: node.nodeNo,\n nodeLabel: node.nodeLabel,\n rank,\n observedAt: detail.time,\n sourcePath: detail.path,\n sortieId: sortie.id,\n battle: sortie.lastBattle,\n }\n appendObservation(state, sortie, observation)\n}\n\nfunction addDefenseBattles(\n state: TrackerState,\n sortie: ActiveSortie,\n detail: GameResponseDetail,\n): { signal: boolean } {\n const entries = extractDestructionBattles(detail.body)\n let signal = false\n entries.forEach((entry, index) => {\n signal ||= hasDefenseSignal(entry)\n const airState = extractDefenseAirState(entry)\n const node = currentNode(sortie)\n sortie.timeline.push({\n id: `${eventKey(detail)}:defense:${index}:timeline`,\n kind: 'base-air-defense',\n occurredAt: detail.time,\n sourcePath: detail.path,\n nodeVisitId: node?.id,\n nodeNo: node?.nodeNo,\n nodeLabel: node?.nodeLabel,\n airState,\n })\n if (node != null) {\n ;(node.baseDefenseEvents ??= []).push({\n timestamp: detail.time,\n path: detail.path,\n airState,\n })\n }\n if (airState == null) return\n const observation: BaseAirDefenseObservation = {\n id: `${eventKey(detail)}:defense:${index}`,\n kind: 'base-air-defense',\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n airState,\n observedAt: detail.time,\n sourcePath: detail.path,\n sortieId: sortie.id,\n battle: { timestamp: detail.time, path: detail.path, kind: 'base-air-defense' },\n }\n appendObservation(state, sortie, observation)\n })\n return { signal }\n}\n\nfunction reviewFromSortie(\n state: TrackerState,\n sortie: ActiveSortie,\n source: TriggerSource,\n detectedAt: number,\n sealed: boolean,\n): PendingReview {\n return {\n id: `review:${sortie.id}`,\n sortieId: sortie.id,\n startedAt: sortie.startedAt,\n mapArea: sortie.mapArea,\n mapNo: sortie.mapNo,\n mapId: sortie.mapId,\n detectedAt,\n triggerSources: [source],\n sealed,\n routeComplete: sortie.captureComplete,\n route: cloneState({\n schemaVersion: 1,\n observations: [],\n guideConditions: [],\n reviews: [],\n freeCompletions: [],\n processedEventKeys: [],\n updatedAt: 0,\n activeSortie: sortie,\n }).activeSortie!.route,\n timeline: JSON.parse(JSON.stringify(sortie.timeline)) as PendingReview['timeline'],\n observationIds: [...sortie.observationIds],\n candidates: buildReviewCandidates(state, sortie.mapId, sortie.observationIds),\n status: 'pending',\n }\n}\n\nfunction orphanReview(state: TrackerState, source: TriggerSource, detectedAt: number): PendingReview {\n const sortieId = `orphan:${detectedAt}`\n return {\n id: `review:${sortieId}`,\n sortieId,\n startedAt: detectedAt,\n mapArea: 0,\n mapNo: 0,\n mapId: 0,\n detectedAt,\n triggerSources: [source],\n sealed: source === 'port',\n routeComplete: false,\n route: [],\n timeline: [],\n observationIds: [],\n candidates: buildReviewCandidates(state, 0, []),\n status: 'pending',\n }\n}\n\nfunction triggerReview(\n state: TrackerState,\n sortie: ActiveSortie | undefined,\n source: TriggerSource,\n detectedAt: number,\n sealed: boolean,\n): boolean {\n if (sortie == null && source === 'port') {\n const recentOrphan = [...state.reviews]\n .reverse()\n .find(\n (item) =>\n item.status === 'pending' &&\n item.sortieId.startsWith('orphan:') &&\n !item.sealed &&\n item.triggerSources.includes('battleresult') &&\n !item.triggerSources.includes('port') &&\n detectedAt - item.detectedAt <= 10 * 60 * 1000,\n )\n if (recentOrphan != null) {\n if (!recentOrphan.triggerSources.includes(source)) recentOrphan.triggerSources.push(source)\n recentOrphan.sealed ||= sealed || source === 'port'\n return false\n }\n }\n const candidate =\n sortie == null\n ? orphanReview(state, source, detectedAt)\n : reviewFromSortie(state, sortie, source, detectedAt, sealed)\n const existing = state.reviews.find((item) => item.id === candidate.id)\n if (existing == null) {\n state.reviews.push(candidate)\n return true\n }\n if (!existing.triggerSources.includes(source)) existing.triggerSources.push(source)\n existing.sealed ||= sealed\n if (sortie != null) {\n existing.routeComplete = sortie.captureComplete\n existing.route = candidate.route\n existing.timeline = candidate.timeline\n existing.observationIds = candidate.observationIds\n if (existing.status === 'pending') existing.candidates = candidate.candidates\n }\n return false\n}\n\nfunction syncReviewForActiveSortie(state: TrackerState, sealed = false): void {\n const sortie = state.activeSortie\n if (sortie == null) return\n const review = state.reviews.find((item) => item.sortieId === sortie.id)\n if (review == null) return\n review.sealed ||= sealed\n review.routeComplete = sortie.captureComplete\n review.route = JSON.parse(JSON.stringify(sortie.route)) as PendingReview['route']\n review.timeline = JSON.parse(JSON.stringify(sortie.timeline)) as PendingReview['timeline']\n review.observationIds = [...sortie.observationIds]\n if (review.status === 'pending') {\n review.candidates = buildReviewCandidates(state, sortie.mapId, sortie.observationIds)\n }\n}\n\nfunction handleMapEvent(\n state: TrackerState,\n detail: GameResponseDetail,\n nodeResolver: NodeResolver,\n): boolean {\n const isStart = detail.path === '/kcsapi/api_req_map/start'\n if (isStart) {\n const map = extractMapRef(detail)\n state.activeSortie = createSortie(detail, map, true)\n } else if (state.activeSortie == null) {\n const map = extractMapRef(detail)\n state.activeSortie = createSortie(detail, map, false)\n } else {\n const map = extractMapRef(detail, state.activeSortie)\n Object.assign(state.activeSortie, map)\n }\n\n const sortie = state.activeSortie\n if (detail.path !== '/kcsapi/api_req_map/air_raid') {\n addNodeVisit(state, sortie, detail, nodeResolver)\n }\n const defense = addDefenseBattles(state, sortie, detail)\n let shouldFocus = false\n if (defense.signal) {\n shouldFocus = triggerReview(state, sortie, 'air-defense', detail.time, false)\n }\n syncReviewForActiveSortie(state)\n return shouldFocus\n}\n\nexport function applyGameResponse(\n previous: TrackerState,\n detail: GameResponseDetail,\n nodeResolver: NodeResolver,\n): TransitionResult {\n if (!isRelevantPath(detail.path)) {\n return { state: previous, changed: false, shouldFocus: false }\n }\n const key = eventKey(detail)\n if (previous.processedEventKeys.includes(key)) {\n return { state: previous, changed: false, shouldFocus: false }\n }\n\n const state = cloneState(previous)\n state.processedEventKeys.push(key)\n if (state.processedEventKeys.length > MAX_PROCESSED_EVENT_KEYS) {\n state.processedEventKeys.splice(0, state.processedEventKeys.length - MAX_PROCESSED_EVENT_KEYS)\n }\n let shouldFocus = false\n\n if (MAP_PATHS.has(detail.path)) {\n shouldFocus = handleMapEvent(state, detail, nodeResolver)\n } else if (isBattleResultPath(detail.path)) {\n if (state.activeSortie != null) addBattleResult(state, state.activeSortie, detail)\n if (hasBattleResultSignal(detail)) {\n shouldFocus = triggerReview(\n state,\n state.activeSortie,\n 'battleresult',\n detail.time,\n false,\n )\n }\n syncReviewForActiveSortie(state)\n } else if (isBattlePath(detail.path)) {\n if (state.activeSortie != null) {\n addBattle(state, state.activeSortie, detail)\n syncReviewForActiveSortie(state)\n }\n } else if (detail.path === '/kcsapi/api_port/port') {\n if (hasPortSignal(detail)) {\n shouldFocus = triggerReview(state, state.activeSortie, 'port', detail.time, true)\n }\n syncReviewForActiveSortie(state, true)\n state.activeSortie = undefined\n }\n\n state.updatedAt = detail.time\n return { state, changed: true, shouldFocus }\n}\n\nfunction refreshPendingCandidates(state: TrackerState): void {\n for (const review of state.reviews) {\n if (review.status !== 'pending') continue\n review.candidates = buildReviewCandidates(state, review.mapId, review.observationIds)\n }\n}\n\nfunction isFallbackLabel(label: string | undefined, nodeNo: number | undefined): boolean {\n return nodeNo != null && label === `#${nodeNo}`\n}\n\nexport function relabelResolvedNodes(\n previous: TrackerState,\n nodeResolver: NodeResolver,\n now: number,\n): TrackerState {\n const state = cloneState(previous)\n let changed = false\n\n const resolve = (item: {\n mapArea: number\n mapNo: number\n mapId: number\n nodeNo?: number\n nodeLabel?: string\n }): void => {\n if (!isFallbackLabel(item.nodeLabel, item.nodeNo)) return\n const label = safeNodeLabel(nodeResolver, item, item.nodeNo!)\n if (label === item.nodeLabel || label === `#${item.nodeNo}`) return\n item.nodeLabel = label\n changed = true\n }\n\n for (const observation of state.observations) resolve(observation)\n for (const visit of state.activeSortie?.route ?? []) resolve(visit)\n for (const event of state.activeSortie?.timeline ?? []) {\n if (event.nodeNo == null || event.nodeLabel == null) continue\n resolve({\n mapArea: state.activeSortie!.mapArea,\n mapNo: state.activeSortie!.mapNo,\n mapId: state.activeSortie!.mapId,\n nodeNo: event.nodeNo,\n get nodeLabel() { return event.nodeLabel },\n set nodeLabel(value: string | undefined) { event.nodeLabel = value },\n })\n }\n for (const review of state.reviews) {\n for (const visit of review.route) resolve(visit)\n for (const event of review.timeline) {\n if (event.nodeNo == null || event.nodeLabel == null) continue\n resolve({\n mapArea: review.mapArea,\n mapNo: review.mapNo,\n mapId: review.mapId,\n nodeNo: event.nodeNo,\n get nodeLabel() { return event.nodeLabel },\n set nodeLabel(value: string | undefined) { event.nodeLabel = value },\n })\n }\n }\n if (!changed) return previous\n\n for (const condition of state.guideConditions) {\n condition.observedCount = state.observations.filter((observation) =>\n observationMatches(condition, observation),\n ).length\n }\n for (const completion of state.freeCompletions) {\n const observation = state.observations.find((item) => item.id === completion.observationId)\n if (observation != null) completion.label = observationLabel(observation)\n }\n for (const review of state.reviews) {\n if (review.status === 'pending') {\n review.candidates = buildReviewCandidates(state, review.mapId, review.observationIds)\n continue\n }\n for (const candidate of review.candidates) {\n const condition = state.guideConditions.find((item) => item.id === candidate.conditionId)\n const observation = state.observations.find((item) => item.id === candidate.observationId)\n if (condition != null) {\n candidate.label = guideConditionLabel(condition)\n candidate.observedCount = condition.observedCount\n } else if (observation != null) {\n candidate.label = observationLabel(observation)\n }\n }\n }\n state.updatedAt = Math.max(state.updatedAt, now)\n return state\n}\n\nexport function importGuideConditions(\n previous: TrackerState,\n text: string,\n map: MapRef,\n now: number,\n): ImportGuideResult {\n const parsed = parseGuideText(text)\n if (parsed.length === 0) return { state: previous, importedCount: 0 }\n const state = cloneState(previous)\n const unique = new Map<string, (typeof parsed)[number]>()\n for (const item of parsed) unique.set(conditionSignature(map, item), item)\n\n for (const [signature, item] of unique) {\n const id = `guide:${signature}`\n const existing = state.guideConditions.find((condition) => condition.id === id)\n if (existing != null) {\n existing.rawText = item.rawText\n existing.requiredCount = item.requiredCount\n existing.observedCount = state.observations.filter((observation) =>\n observationMatches(existing, observation),\n ).length\n continue\n }\n const condition: GuideCondition = {\n ...map,\n id,\n kind: item.kind,\n rawText: item.rawText,\n nodeLabel: item.nodeLabel,\n requiredRank: item.requiredRank,\n requiredAirState: item.requiredAirState,\n requiredCount: item.requiredCount,\n observedCount: 0,\n confirmed: false,\n createdAt: now,\n }\n condition.observedCount = state.observations.filter((observation) =>\n observationMatches(condition, observation),\n ).length\n state.guideConditions.push(condition)\n }\n refreshPendingCandidates(state)\n state.updatedAt = now\n return { state, importedCount: unique.size }\n}\n\nexport function confirmReview(\n previous: TrackerState,\n reviewId: string,\n candidateIds: string[],\n now: number,\n): TrackerState {\n if (candidateIds.length === 0) return previous\n const state = cloneState(previous)\n const review = state.reviews.find((item) => item.id === reviewId)\n if (review == null || review.status !== 'pending') return previous\n const selected = review.candidates.filter((candidate) => candidateIds.includes(candidate.id))\n if (selected.length === 0) return previous\n\n const mapHasGuide = state.guideConditions.some((condition) => condition.mapId === review.mapId)\n for (const candidate of selected) {\n if (candidate.mode === 'guide' && candidate.conditionId != null) {\n const condition = state.guideConditions.find((item) => item.id === candidate.conditionId)\n if (condition != null && !condition.confirmed) {\n condition.confirmed = true\n condition.confirmedAt = now\n condition.confirmedByReviewId = review.id\n }\n continue\n }\n if (mapHasGuide || candidate.observationId == null) continue\n const observation = state.observations.find((item) => item.id === candidate.observationId)\n if (observation == null) continue\n const completion: FreeCompletion = {\n mapArea: review.mapArea,\n mapNo: review.mapNo,\n mapId: review.mapId,\n id: `free-completion:${review.id}:${candidate.observationId}`,\n kind: candidate.kind,\n label: candidate.label,\n reviewId: review.id,\n observationId: candidate.observationId,\n confirmedAt: now,\n }\n if (!state.freeCompletions.some((item) => item.id === completion.id)) {\n state.freeCompletions.push(completion)\n }\n }\n review.status = 'confirmed'\n review.selectedCandidateIds = selected.map((candidate) => candidate.id)\n review.resolvedAt = now\n refreshPendingCandidates(state)\n state.updatedAt = now\n return state\n}\n\nexport function ignoreReview(previous: TrackerState, reviewId: string, now: number): TrackerState {\n const state = cloneState(previous)\n const review = state.reviews.find((item) => item.id === reviewId)\n if (review == null || review.status !== 'pending') return previous\n review.status = 'ignored'\n review.resolvedAt = now\n state.updatedAt = now\n return state\n}\n","import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'fs'\nimport { join } from 'path'\n\nimport { createInitialState, SCHEMA_VERSION, type TrackerState } from '../core'\n\nexport interface TrackerStorage {\n load(memberId: string): TrackerState\n save(memberId: string, state: TrackerState): void\n}\n\nexport function normalizeMemberId(value: unknown): string | undefined {\n if (value == null) return undefined\n const normalized = typeof value === 'number' ? String(value) : typeof value === 'string' ? value : ''\n return /^\\d+$/.test(normalized) ? normalized : undefined\n}\n\nfunction hydrateState(value: unknown): TrackerState {\n if (typeof value !== 'object' || value == null) return createInitialState()\n const raw = value as Partial<TrackerState>\n if (raw.schemaVersion !== SCHEMA_VERSION) return createInitialState()\n const reviews = Array.isArray(raw.reviews)\n ? raw.reviews.map((review) => ({\n ...review,\n startedAt:\n typeof review.startedAt === 'number'\n ? review.startedAt\n : review.route?.[0]?.arrivedAt ?? review.detectedAt,\n timeline: Array.isArray(review.timeline) ? review.timeline : [],\n }))\n : []\n const activeSortie =\n raw.activeSortie != null\n ? {\n ...raw.activeSortie,\n timeline: Array.isArray(raw.activeSortie.timeline) ? raw.activeSortie.timeline : [],\n }\n : undefined\n return {\n ...createInitialState(typeof raw.updatedAt === 'number' ? raw.updatedAt : 0),\n ...raw,\n schemaVersion: SCHEMA_VERSION,\n observations: Array.isArray(raw.observations) ? raw.observations : [],\n guideConditions: Array.isArray(raw.guideConditions) ? raw.guideConditions : [],\n reviews,\n freeCompletions: Array.isArray(raw.freeCompletions) ? raw.freeCompletions : [],\n processedEventKeys: Array.isArray(raw.processedEventKeys) ? raw.processedEventKeys : [],\n activeSortie,\n }\n}\n\nexport class FileTrackerStorage implements TrackerStorage {\n constructor(private readonly appDataPath: string) {}\n\n statePath(memberId: string): string {\n const safe = normalizeMemberId(memberId)\n if (safe == null) throw new Error('Invalid memberId')\n return join(this.appDataPath, 'gimmick-tracker', safe, 'state-v1.json')\n }\n\n load(memberId: string): TrackerState {\n const path = this.statePath(memberId)\n if (!existsSync(path)) return createInitialState()\n try {\n return hydrateState(JSON.parse(readFileSync(path, 'utf8')) as unknown)\n } catch {\n return createInitialState()\n }\n }\n\n save(memberId: string, state: TrackerState): void {\n const path = this.statePath(memberId)\n const directory = join(path, '..')\n mkdirSync(directory, { recursive: true })\n const temporary = `${path}.${process.pid}.${Date.now()}.tmp`\n try {\n writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\\n`, 'utf8')\n renameSync(temporary, path)\n } catch (error) {\n try {\n if (existsSync(temporary)) unlinkSync(temporary)\n } catch {\n // Preserve the original write error.\n }\n throw error\n }\n }\n}\n\nexport class MemoryTrackerStorage implements TrackerStorage {\n readonly states = new Map<string, TrackerState>()\n\n load(memberId: string): TrackerState {\n const state = this.states.get(memberId)\n return state == null ? createInitialState() : JSON.parse(JSON.stringify(state)) as TrackerState\n }\n\n save(memberId: string, state: TrackerState): void {\n this.states.set(memberId, JSON.parse(JSON.stringify(state)) as TrackerState)\n }\n}\n","import {\n applyGameResponse,\n asRecord,\n confirmReview,\n createInitialState,\n extractPortMemberId,\n ignoreReview,\n importGuideConditions,\n relabelResolvedNodes,\n type GameResponseDetail,\n type MapRef,\n type NodeResolver,\n type TrackerState,\n} from './core'\nimport type { FocusMethod } from './host'\nimport { normalizeMemberId, type TrackerStorage } from './storage'\n\nexport interface EventTargetLike {\n addEventListener(type: string, listener: (event: unknown) => void): void\n removeEventListener(type: string, listener: (event: unknown) => void): void\n}\n\nexport interface TrackerControllerDependencies {\n storage: TrackerStorage\n focus: { focusPlugin(id: string): FocusMethod | string }\n pluginId: string\n nodeResolver: NodeResolver\n getMemberId?: () => unknown\n now?: () => number\n onError?: (error: unknown) => void\n}\n\nexport interface TrackerRuntimeStatus {\n started: boolean\n startedAt?: number\n receivedCount: number\n trackedCount: number\n lastResponseAt?: number\n lastResponsePath?: string\n lastTrackedAt?: number\n lastFocusAt?: number\n lastFocusMethod?: string\n lastFocusPath?: string\n}\n\nexport interface TrackerViewSnapshot {\n state: TrackerState\n runtime: TrackerRuntimeStatus\n}\n\nfunction parseEvent(event: unknown): GameResponseDetail | undefined {\n if (typeof event !== 'object' || event == null) return undefined\n const detail = (event as { detail?: unknown }).detail\n if (typeof detail !== 'object' || detail == null) return undefined\n const value = detail as Record<string, unknown>\n if (typeof value.path !== 'string' || typeof value.time !== 'number') return undefined\n return {\n method: typeof value.method === 'string' ? value.method : undefined,\n path: value.path,\n body: asRecord(value.body) ?? {},\n postBody: asRecord(value.postBody),\n time: value.time,\n }\n}\n\nfunction mergeById<T extends { id: string }>(stored: T[], transient: T[]): T[] {\n const merged = new Map(stored.map((item) => [item.id, item]))\n for (const item of transient) merged.set(item.id, item)\n return [...merged.values()]\n}\n\nexport class TrackerController {\n private state: TrackerState = createInitialState()\n private readonly subscribers = new Set<() => void>()\n private eventTarget?: EventTargetLike\n private memberId?: string\n private started = false\n private runtime: TrackerRuntimeStatus = { started: false, receivedCount: 0, trackedCount: 0 }\n private viewSnapshot: TrackerViewSnapshot = { state: this.state, runtime: this.runtime }\n\n private readonly handleResponse = (event: unknown): void => {\n const detail = parseEvent(event)\n if (detail == null) return\n this.runtime = {\n ...this.runtime,\n receivedCount: this.runtime.receivedCount + 1,\n lastResponseAt: detail.time,\n lastResponsePath: detail.path,\n }\n try {\n const preferred = this.readPreferredMemberId()\n const portMember = normalizeMemberId(extractPortMemberId(detail))\n this.switchMember(portMember ?? preferred)\n if (\n detail.path === '/kcsapi/api_port/port' ||\n detail.path === '/kcsapi/api_req_map/start'\n ) {\n const relabeled = relabelResolvedNodes(this.state, this.dependencies.nodeResolver, detail.time)\n if (relabeled !== this.state) {\n this.state = relabeled\n this.persist()\n }\n }\n const result = applyGameResponse(this.state, detail, this.dependencies.nodeResolver)\n if (!result.changed) {\n this.emit()\n return\n }\n this.state = result.state\n this.runtime = {\n ...this.runtime,\n trackedCount: this.runtime.trackedCount + 1,\n lastTrackedAt: detail.time,\n }\n this.persist()\n if (result.shouldFocus) {\n try {\n const method = this.dependencies.focus.focusPlugin(this.dependencies.pluginId)\n this.runtime = {\n ...this.runtime,\n lastFocusAt: detail.time,\n lastFocusMethod: method,\n lastFocusPath: detail.path,\n }\n } catch (error) {\n this.report(error)\n }\n }\n this.emit()\n } catch (error) {\n this.report(error)\n }\n }\n\n constructor(private readonly dependencies: TrackerControllerDependencies) {}\n\n start(target: EventTargetLike): void {\n if (this.started) return\n this.started = true\n this.eventTarget = target\n this.switchMember(this.readPreferredMemberId())\n this.runtime = {\n ...this.runtime,\n started: true,\n startedAt: this.now(),\n }\n target.addEventListener('game.response', this.handleResponse)\n this.emit()\n }\n\n stop(): void {\n if (!this.started) return\n this.eventTarget?.removeEventListener('game.response', this.handleResponse)\n this.eventTarget = undefined\n this.started = false\n this.runtime = { ...this.runtime, started: false }\n this.emit()\n }\n\n getSnapshot = (): TrackerViewSnapshot => this.viewSnapshot\n\n subscribe = (subscriber: () => void): (() => void) => {\n this.subscribers.add(subscriber)\n return () => this.subscribers.delete(subscriber)\n }\n\n currentMap(): MapRef | undefined {\n if (this.state.activeSortie != null && this.state.activeSortie.mapId > 0) {\n return {\n mapArea: this.state.activeSortie.mapArea,\n mapNo: this.state.activeSortie.mapNo,\n mapId: this.state.activeSortie.mapId,\n }\n }\n const review = [...this.state.reviews].reverse().find((item) => item.mapId > 0)\n if (review != null) {\n return { mapArea: review.mapArea, mapNo: review.mapNo, mapId: review.mapId }\n }\n const observation = this.state.observations.at(-1)\n return observation == null\n ? undefined\n : {\n mapArea: observation.mapArea,\n mapNo: observation.mapNo,\n mapId: observation.mapId,\n }\n }\n\n importGuide(text: string, map = this.currentMap()): number {\n if (map == null) return 0\n const result = importGuideConditions(this.state, text, map, this.now())\n if (result.state === this.state) return result.importedCount\n this.state = result.state\n this.persist()\n this.emit()\n return result.importedCount\n }\n\n confirm(reviewId: string, candidateIds: string[]): void {\n const next = confirmReview(this.state, reviewId, candidateIds, this.now())\n if (next === this.state) return\n this.state = next\n this.persist()\n this.emit()\n }\n\n ignore(reviewId: string): void {\n const next = ignoreReview(this.state, reviewId, this.now())\n if (next === this.state) return\n this.state = next\n this.persist()\n this.emit()\n }\n\n private now(): number {\n return this.dependencies.now?.() ?? Date.now()\n }\n\n private readPreferredMemberId(): string | undefined {\n try {\n return normalizeMemberId(this.dependencies.getMemberId?.())\n } catch (error) {\n this.report(error)\n return undefined\n }\n }\n\n private switchMember(next: string | undefined): void {\n if (next == null || next === this.memberId) return\n if (this.memberId != null) this.persist()\n const transient = this.memberId == null ? this.state : undefined\n this.memberId = next\n try {\n const stored = this.dependencies.storage.load(next)\n if (transient == null || (transient.updatedAt === 0 && transient.activeSortie == null)) {\n this.state = relabelResolvedNodes(stored, this.dependencies.nodeResolver, this.now())\n if (this.state !== stored) this.persist()\n return\n }\n this.state = {\n ...stored,\n activeSortie: transient.activeSortie ?? stored.activeSortie,\n observations: mergeById(stored.observations, transient.observations),\n guideConditions: mergeById(stored.guideConditions, transient.guideConditions),\n reviews: mergeById(stored.reviews, transient.reviews),\n freeCompletions: mergeById(stored.freeCompletions, transient.freeCompletions),\n processedEventKeys: [...new Set([\n ...stored.processedEventKeys,\n ...transient.processedEventKeys,\n ])].slice(-2048),\n updatedAt: Math.max(stored.updatedAt, transient.updatedAt),\n }\n this.state = relabelResolvedNodes(this.state, this.dependencies.nodeResolver, this.now())\n this.persist()\n } catch (error) {\n this.state = transient ?? createInitialState()\n this.report(error)\n }\n }\n\n private persist(): void {\n if (this.memberId == null) return\n try {\n this.dependencies.storage.save(this.memberId, this.state)\n } catch (error) {\n this.report(error)\n }\n }\n\n private emit(): void {\n this.viewSnapshot = { state: this.state, runtime: { ...this.runtime } }\n for (const subscriber of this.subscribers) subscriber()\n }\n\n private report(error: unknown): void {\n this.dependencies.onError?.(error)\n }\n}\n","export interface IpcLike {\n access(scope: string): { ipcFocusPlugin?: (id: string) => void } | undefined\n}\n\nexport interface FocusAdapterDependencies {\n primaryIpc?: IpcLike\n legacyIpc?: IpcLike\n dispatch?: (action: unknown) => unknown\n getStore?: (path: string) => unknown\n success?: (message: string) => unknown\n}\n\nexport type FocusMethod = 'ipc' | 'legacy-ipc' | 'dispatch' | 'notice' | 'none'\n\nfunction tryIpc(ipc: IpcLike | undefined, id: string): boolean {\n try {\n const focus = ipc?.access('MainWindow')?.ipcFocusPlugin\n if (typeof focus !== 'function') return false\n focus(id)\n return true\n } catch {\n return false\n }\n}\n\nexport function createFocusAdapter(dependencies: FocusAdapterDependencies) {\n return {\n focusPlugin(id: string): FocusMethod {\n if (tryIpc(dependencies.primaryIpc, id)) return 'ipc'\n if (tryIpc(dependencies.legacyIpc, id)) return 'legacy-ipc'\n try {\n if (typeof dependencies.dispatch === 'function') {\n const doubleTabbed = Boolean(dependencies.getStore?.('config.poi.tabarea.double'))\n const tabInfo = doubleTabbed\n ? { activePluginName: id }\n : { activeMainTab: id, activePluginName: id }\n dependencies.dispatch({\n type: '@@TabSwitch',\n payload: { tabInfo, autoSwitch: false },\n })\n return 'dispatch'\n }\n } catch {\n // Continue to the non-throwing notification fallback.\n }\n try {\n if (typeof dependencies.success === 'function') {\n dependencies.success('检测到解密或削甲变化,请打开“解密·削甲记录”确认。')\n return 'notice'\n }\n } catch {\n // Host notifications must never break response handling.\n }\n return 'none'\n },\n }\n}\n","import type { NodeResolver } from '../core'\n\nexport type StoreGetter = (path?: string) => unknown\n\nexport function createFcdNodeResolver(getStore: StoreGetter): NodeResolver {\n return ({ mapArea, mapNo, apiNo }) => {\n const fallback = `#${apiNo}`\n try {\n const fcdMap = getStore('fcd.map')\n if (typeof fcdMap !== 'object' || fcdMap == null) return fallback\n const map = (fcdMap as Record<string, unknown>)[`${mapArea}-${mapNo}`]\n if (typeof map !== 'object' || map == null) return fallback\n const route = (map as Record<string, unknown>).route\n if (typeof route !== 'object' || route == null) return fallback\n const edge = (route as Record<string, unknown>)[String(apiNo)]\n if (!Array.isArray(edge) || typeof edge[1] !== 'string' || edge[1].trim() === '') {\n return fallback\n }\n return edge[1]\n } catch {\n return fallback\n }\n }\n}\n","{\n \"解密·削甲记录\": \"解密·削甲记录\",\n \"记录出击路线,并在解密或削甲生效后提醒确认\": \"记录出击路线,并在解密或削甲生效后提醒确认\",\n \"title\": \"解密·削甲记录\",\n \"subtitle\": \"自动记下路线、战斗和空袭;检测到解密或削甲生效后,请你确认刚完成的条件。数据只保存在本机。\",\n \"running\": \"运行中,正在接收 Poi 战斗数据\",\n \"stopped\": \"监听已停止\",\n \"receivedThisRun\": \"本次加载收到响应\",\n \"trackedThisRun\": \"本次加载已处理\",\n \"recordedSorties\": \"累计出击\",\n \"observations\": \"累计记录\",\n \"lastResponse\": \"最近收到\",\n \"lastPageSwitch\": \"最近自动切页请求\",\n \"unresolvedNodes\": \"仍有 {count} 条旧记录暂时只能显示 #编号;FCD 点位数据就绪后会自动回填。\",\n \"activeMap\": \"当前海域\",\n \"pendingReviews\": \"待确认记录\",\n \"pending\": \"待确认\",\n \"noPending\": \"暂无待确认记录。普通出击只在后台记录,不会打断你。\",\n \"unknownMap\": \"未知海域\",\n \"route\": \"本次路线\",\n \"incompleteRoute\": \"未捕获到完整出击:插件可能在出击中途加载,或仅收到了回港信号。\",\n \"emptyRoute\": \"没有可显示的节点路线。\",\n \"candidates\": \"这次完成了哪一项?\",\n \"chooseCandidate\": \"请选择本次完成条件(● 表示本次有直接证据)\",\n \"noCandidates\": \"没有足够事实生成候选;可忽略本次,或先导入攻略条件后再观察下一次出击。\",\n \"matchedThisSortie\": \"本次有证据\",\n \"confirm\": \"确认完成\",\n \"ignore\": \"忽略本次\",\n \"ignored\": \"已忽略\",\n \"guide\": \"导入攻略条件\",\n \"guidePlaceholder\": \"例如:D点S胜2次;C2点到达2次 A2点空优1次,守家空优2次\",\n \"import\": \"导入到当前海域\",\n \"importNeedMap\": \"尚未识别当前海域,请先捕获一次地图出击。\",\n \"importSuccess\": \"已识别 {count} 条条件。\",\n \"noMatch\": \"未识别到受支持的条件格式。\",\n \"completed\": \"按海域查看解密·削甲进度\",\n \"lastTriggeredSortie\": \"上一次生效出击\",\n \"triggerHistory\": \"每次检测到生效的出击\",\n \"noCompleted\": \"尚未导入攻略条件,也没有自由完成记录。\",\n \"observed\": \"观测\",\n \"confirmed\": \"已确认\",\n \"unconfirmed\": \"未确认\",\n \"freeRecord\": \"无攻略时的自由记录\"\n}\n","import {\n Button,\n Callout,\n Card,\n Elevation,\n HTMLSelect,\n HTMLTable,\n Tag,\n TextArea,\n} from '@blueprintjs/core'\nimport React, { useMemo, useState, useSyncExternalStore } from 'react'\n\nimport zhCN from '../../i18n/zh-CN.json'\nimport { airStateLabel, guideConditionLabel, mapLabel, type PendingReview } from '../core'\nimport type { TrackerController } from '../controller'\n\nconst styles: Record<string, React.CSSProperties> = {\n root: { padding: 16, height: '100%', overflow: 'auto', boxSizing: 'border-box' },\n header: { display: 'flex', justifyContent: 'space-between', gap: 16, alignItems: 'center' },\n section: { marginTop: 16 },\n review: { marginTop: 12 },\n reviewGrid: { display: 'grid', gridTemplateColumns: 'minmax(260px, 1.2fr) minmax(280px, .8fr)', gap: 18 },\n route: { display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 8 },\n historyRow: { display: 'grid', gridTemplateColumns: 'minmax(280px, 1fr) auto', gap: 16, padding: '10px 0', borderBottom: '1px solid rgba(128,128,128,.18)' },\n candidate: { padding: '6px 0', borderBottom: '1px solid rgba(128,128,128,.18)' },\n actions: { display: 'flex', gap: 8, marginTop: 12 },\n guideRow: { display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 12, alignItems: 'center' },\n muted: { color: '#738091' },\n statusGrid: { display: 'flex', flexWrap: 'wrap', gap: 8, alignItems: 'center', marginTop: 10 },\n}\n\ntype TextKey = keyof typeof zhCN\nconst t = (key: TextKey): string => zhCN[key]\n\nfunction shortTime(value: number | undefined): string {\n return value == null ? '—' : new Date(value).toLocaleTimeString()\n}\n\nfunction focusMethodLabel(method: string | undefined): string {\n switch (method) {\n case 'ipc': return 'Poi IPC 已发送'\n case 'legacy-ipc': return '兼容 IPC 已发送'\n case 'dispatch': return '页签切换已派发'\n case 'notice': return '已发送提醒'\n case 'none': return '宿主未提供切页接口'\n default: return '尚未触发'\n }\n}\n\nfunction routeBadge(review: PendingReview, index: number): React.ReactNode {\n const visit = review.route[index]\n const details = [visit.rank == null ? undefined : `${visit.rank}胜`]\n if (visit.airState != null) details.push(airStateLabel(visit.airState))\n if (visit.battleEvents?.some((event) => event.kind === 'fleet-air-raid')) details.push('舰队空袭')\n for (const defense of visit.baseDefenseEvents ?? []) {\n details.push(`基地防空${defense.airState == null ? '' : ` · ${airStateLabel(defense.airState)}`}`)\n }\n return (\n <Tag\n key={visit.id}\n minimal={index !== review.route.length - 1}\n title={new Date(visit.arrivedAt).toLocaleString()}\n >\n {visit.nodeLabel}\n {details.filter(Boolean).length > 0 ? ` · ${details.filter(Boolean).join(' · ')}` : ''}\n </Tag>\n )\n}\n\nfunction formatSortieTime(review: PendingReview): string {\n const startedAt = review.startedAt ?? review.route[0]?.arrivedAt ?? review.detectedAt\n return `${new Date(startedAt).toLocaleString()} ~ ${new Date(review.detectedAt).toLocaleString()}`\n}\n\nfunction timelineBadge(review: PendingReview, event: PendingReview['timeline'][number]): React.ReactNode {\n if (event.kind === 'node') {\n const index = review.route.findIndex((visit) => visit.id === event.nodeVisitId)\n if (index >= 0) return routeBadge(review, index)\n }\n const label = event.kind === 'fleet-air-raid' ? '舰队空袭' : '基地防空'\n const parts = [event.nodeLabel == null ? undefined : `${event.nodeLabel}点`, label]\n if (event.airState != null) parts.push(airStateLabel(event.airState))\n return (\n <Tag key={event.id} intent={event.kind === 'base-air-defense' ? 'warning' : 'primary'} title={new Date(event.occurredAt).toLocaleString()}>\n {parts.filter(Boolean).join(' · ')}\n </Tag>\n )\n}\n\nfunction sortieTimeline(review: PendingReview): React.ReactNode {\n const timeline = review.timeline ?? []\n if (timeline.length > 0) {\n return <div style={styles.route}>{timeline.map((event) => timelineBadge(review, event))}</div>\n }\n if (review.route.length > 0) {\n return <div style={styles.route}>{review.route.map((_, index) => routeBadge(review, index))}</div>\n }\n return <span style={styles.muted}>{t('emptyRoute')}</span>\n}\n\nfunction PendingCard({\n review,\n selected,\n setSelected,\n controller,\n}: {\n review: PendingReview\n selected: string\n setSelected: (id: string) => void\n controller: TrackerController\n}) {\n const selectedCandidate = review.candidates.find((candidate) => candidate.id === selected)\n return (\n <Card style={styles.review} elevation={Elevation.ONE}>\n <div style={styles.header}>\n <div>\n <strong>{review.mapId > 0 ? mapLabel(review) : t('unknownMap')}</strong>{' '}\n <Tag intent=\"warning\">{t('pending')}</Tag>\n <div style={styles.muted}>{formatSortieTime(review)}</div>\n </div>\n <div style={styles.route}>\n {review.triggerSources.map((source) => (\n <Tag key={source} minimal>\n {source}\n </Tag>\n ))}\n </div>\n </div>\n <div style={styles.reviewGrid}>\n <div>\n <h4>{t('route')}</h4>\n {!review.routeComplete && <Callout intent=\"warning\">{t('incompleteRoute')}</Callout>}\n {sortieTimeline(review)}\n </div>\n <div>\n <h4>{t('candidates')}</h4>\n {review.candidates.length === 0 ? (\n <Callout>{t('noCandidates')}</Callout>\n ) : (\n <>\n <HTMLSelect\n fill\n value={selected}\n onChange={(event) => setSelected(event.currentTarget.value)}\n >\n <option value=\"\">{t('chooseCandidate')}</option>\n {review.candidates.map((candidate) => (\n <option key={candidate.id} value={candidate.id}>\n {candidate.matchedInSortie ? '● ' : ''}{candidate.label}\n {candidate.observedCount == null\n ? ''\n : ` · ${t('observed')} ${candidate.observedCount}/${candidate.requiredCount}`}\n </option>\n ))}\n </HTMLSelect>\n {selectedCandidate?.matchedInSortie && (\n <Callout style={{ marginTop: 8 }} intent=\"success\">{t('matchedThisSortie')}</Callout>\n )}\n </>\n )}\n <div style={styles.actions}>\n <Button\n intent=\"primary\"\n disabled={selected === ''}\n onClick={() => controller.confirm(review.id, [selected])}\n >\n {t('confirm')}\n </Button>\n <Button onClick={() => controller.ignore(review.id)}>{t('ignore')}</Button>\n </div>\n </div>\n </div>\n </Card>\n )\n}\n\nexport function createTrackerView(controller: TrackerController): React.ComponentType {\n return function GimmickTrackerView() {\n const snapshot = useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot)\n const { state, runtime } = snapshot\n const [guideText, setGuideText] = useState('')\n const [message, setMessage] = useState('')\n const [selected, setSelected] = useState<Record<string, string>>({})\n const pending = state.reviews.filter((review) => review.status === 'pending')\n const currentMap = controller.currentMap()\n const sortieCount = useMemo(\n () => new Set(state.observations.map((item) => item.sortieId)).size,\n [state.observations],\n )\n const unresolvedCount = useMemo(\n () => state.observations.filter((item) => item.nodeLabel?.startsWith('#')).length,\n [state.observations],\n )\n const mapIds = useMemo(\n () =>\n [...new Set([\n ...state.guideConditions.map((item) => item.mapId),\n ...state.freeCompletions.map((item) => item.mapId),\n ...state.reviews.filter((item) => item.mapId > 0).map((item) => item.mapId),\n ])].sort((left, right) => left - right),\n [state.guideConditions, state.freeCompletions, state.reviews],\n )\n\n const importGuide = () => {\n if (currentMap == null) {\n setMessage(t('importNeedMap'))\n return\n }\n const count = controller.importGuide(guideText, currentMap)\n setMessage(count > 0 ? t('importSuccess').replace('{count}', String(count)) : t('noMatch'))\n }\n\n return (\n <main style={styles.root}>\n <header style={styles.header}>\n <div>\n <h2 style={{ margin: 0 }}>{t('title')}</h2>\n <p style={{ ...styles.muted, marginBottom: 0 }}>{t('subtitle')}</p>\n </div>\n {currentMap != null && <Tag large>{t('activeMap')}: {mapLabel(currentMap)}</Tag>}\n </header>\n\n <Callout style={styles.section} intent={runtime.started ? 'success' : 'warning'}>\n <strong>{runtime.started ? t('running') : t('stopped')}</strong>\n <div style={styles.statusGrid}>\n <Tag minimal>{t('receivedThisRun')}: {runtime.receivedCount}</Tag>\n <Tag minimal>{t('trackedThisRun')}: {runtime.trackedCount}</Tag>\n <Tag minimal>{t('recordedSorties')}: {sortieCount}</Tag>\n <Tag minimal>{t('observations')}: {state.observations.length}</Tag>\n </div>\n <div style={{ ...styles.muted, marginTop: 8 }}>\n {t('lastResponse')}: {shortTime(runtime.lastResponseAt)}\n {runtime.lastResponsePath == null ? '' : ` · ${runtime.lastResponsePath}`}\n </div>\n <div style={styles.muted}>\n {t('lastPageSwitch')}: {shortTime(runtime.lastFocusAt)} · {focusMethodLabel(runtime.lastFocusMethod)}\n </div>\n </Callout>\n\n {unresolvedCount > 0 && (\n <Callout style={styles.section} intent=\"warning\">\n {t('unresolvedNodes').replace('{count}', String(unresolvedCount))}\n </Callout>\n )}\n\n <section style={styles.section}>\n <h3>{t('pendingReviews')}</h3>\n {pending.length === 0 ? (\n <Callout>{t('noPending')}</Callout>\n ) : (\n pending.map((review) => (\n <PendingCard\n key={review.id}\n review={review}\n controller={controller}\n selected={selected[review.id] ?? ''}\n setSelected={(id) => setSelected((value) => ({ ...value, [review.id]: id }))}\n />\n ))\n )}\n </section>\n\n <section style={styles.section}>\n <h3>{t('guide')}</h3>\n <TextArea\n fill\n rows={4}\n value={guideText}\n placeholder={t('guidePlaceholder')}\n onChange={(event) => setGuideText(event.currentTarget.value)}\n />\n <div style={styles.actions}>\n <Button intent=\"primary\" onClick={importGuide}>{t('import')}</Button>\n {message !== '' && <span style={styles.muted}>{message}</span>}\n </div>\n </section>\n\n <section style={styles.section}>\n <h3>{t('completed')}</h3>\n {mapIds.length === 0 ? (\n <Callout>{t('noCompleted')}</Callout>\n ) : (\n mapIds.map((mapId) => {\n const guides = state.guideConditions.filter((item) => item.mapId === mapId)\n const free = state.freeCompletions.filter((item) => item.mapId === mapId)\n const reviews = state.reviews.filter((item) => item.mapId === mapId)\n const lastReview = reviews.at(-1)\n const map = guides[0] ?? free[0] ?? lastReview\n return (\n <Card key={mapId} style={styles.review} elevation={Elevation.ZERO}>\n <h4>{map == null ? `#${mapId}` : mapLabel(map)}</h4>\n {reviews.length > 0 && (\n <div style={{ marginBottom: 16 }}>\n <strong>{t('triggerHistory')}</strong>\n {[...reviews].reverse().map((review) => {\n const selectedLabels = review.candidates\n .filter((candidate) => review.selectedCandidateIds?.includes(candidate.id))\n .map((candidate) => candidate.label)\n return (\n <div key={review.id} style={styles.historyRow}>\n <div>{sortieTimeline(review)}</div>\n <div style={{ textAlign: 'right' }}>\n <div style={styles.muted}>{formatSortieTime(review)}</div>\n <Tag\n intent={review.status === 'confirmed' ? 'success' : review.status === 'pending' ? 'warning' : 'none'}\n >\n {review.status === 'confirmed'\n ? t('confirmed')\n : review.status === 'pending'\n ? t('pending')\n : t('ignored')}\n </Tag>\n {selectedLabels.length > 0 && <div>{selectedLabels.join('、')}</div>}\n </div>\n </div>\n )\n })}\n </div>\n )}\n <HTMLTable compact striped style={{ width: '100%' }}>\n <tbody>\n {guides.map((condition) => (\n <tr key={condition.id}>\n <td>{guideConditionLabel(condition)}</td>\n <td>{t('observed')} {condition.observedCount}/{condition.requiredCount}</td>\n <td>\n <Tag intent={condition.confirmed ? 'success' : 'none'}>\n {condition.confirmed ? t('confirmed') : t('unconfirmed')}\n </Tag>\n </td>\n </tr>\n ))}\n {free.map((completion) => (\n <tr key={completion.id}>\n <td>{completion.label}</td>\n <td>{t('freeRecord')}</td>\n <td><Tag intent=\"success\">{t('confirmed')}</Tag></td>\n </tr>\n ))}\n </tbody>\n </HTMLTable>\n </Card>\n )\n })\n )}\n </section>\n </main>\n )\n }\n}\n","import { APPDATA_PATH, ipc } from 'views/env'\n\nimport { PLUGIN_ID } from './src/core'\nimport { TrackerController } from './src/controller'\nimport { createFocusAdapter, createFcdNodeResolver } from './src/host'\nimport { FileTrackerStorage } from './src/storage'\nimport { createTrackerView } from './src/ui'\n\nconst appDataPath = window.APPDATA_PATH ?? APPDATA_PATH\nconst readPoiStore = (path?: string): unknown => window.getStore?.(path)\nconst controller = new TrackerController({\n storage: new FileTrackerStorage(appDataPath),\n focus: createFocusAdapter({\n primaryIpc: ipc,\n legacyIpc: window.ipc,\n dispatch: window.dispatch,\n getStore: readPoiStore,\n success: window.success,\n }),\n pluginId: PLUGIN_ID,\n nodeResolver: createFcdNodeResolver(readPoiStore),\n getMemberId: () => readPoiStore('info.basic.api_member_id'),\n onError: (error) => console.error(`[${PLUGIN_ID}]`, error),\n})\n\nexport const reactClass = createTrackerView(controller)\nexport const windowMode = false\n\nexport function pluginDidLoad(): void {\n controller.start(window)\n}\n\nexport function pluginWillUnload(): void {\n controller.stop()\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poi-plugin-gimmick-tracker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Track and confirm KanColle event-map gimmick conditions in poi.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"author": "inoryirony",
|
|
39
39
|
"poiPlugin": {
|
|
40
40
|
"id": "poi-plugin-gimmick-tracker",
|
|
41
|
-
"title": "
|
|
42
|
-
"name": "
|
|
43
|
-
"description": "
|
|
41
|
+
"title": "解密·削甲记录",
|
|
42
|
+
"name": "解密·削甲记录",
|
|
43
|
+
"description": "记录出击路线,并在解密或削甲生效后提醒确认",
|
|
44
44
|
"icon": "fa/check-square-o",
|
|
45
45
|
"priority": 120,
|
|
46
46
|
"earliestCompatibleMain": "11.1.0",
|