poi-plugin-gimmick-tracker 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/index.js +153 -86
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -28,6 +28,8 @@ P2点空优1次
28
28
 
29
29
  导入时逐行报告识别结果,无法识别的行会明确指出,不会静默丢弃。条件可删除,重复导入按签名更新次数。
30
30
 
31
+ 解析器对攻略写法尽量鲁棒:中文数字(两次/一回)、省略「胜/点/次」(`P3点A2次`)、乘号计数(`S×2`)、词序变体(`G点2次S胜`、`到达E2点2次`)、`boss/王点/斩杀` 等装饰词、大小写混排、全角字符、繁体(點/勝/達)、一行挤多条。`击破` 按 A 胜理解;`P1boss一次` 这类无字母点的 boss 简写按「P1点击破 A胜」理解并在报告里提示。
32
+
31
33
  ## 数据与隐私
32
34
 
33
35
  - 状态按成员分文件保存在 `<poi AppData>/gimmick-tracker/<memberId>/state-v2.json`,临时文件加 rename 原子写。
package/index.js CHANGED
@@ -62,96 +62,160 @@ function matchesCondition(condition, observation) {
62
62
  }
63
63
 
64
64
  // src/parser.js
65
- var AIR_TEXT_TO_STATE = { \u7A7A\u786E: 1, \u7A7A\u512A: 2, \u7A7A\u4F18: 2, \u7A7A\u5747: 3, \u7A7A\u52A3: 4 };
65
+ var AIR_ALIAS = {
66
+ \u7A7A\u786E: 1,
67
+ \u7A7A\u78BA: 1,
68
+ \u5236\u7A7A\u786E\u4FDD: 1,
69
+ \u5236\u7A7A\u78BA\u4FDD: 1,
70
+ \u5236\u7A7A\u6743\u786E\u4FDD: 1,
71
+ \u5236\u7A7A\u6B0A\u78BA\u4FDD: 1,
72
+ \u7A7A\u512A: 2,
73
+ \u7A7A\u4F18: 2,
74
+ \u822A\u7A7A\u512A\u52E2: 2,
75
+ \u822A\u7A7A\u4F18\u52BF: 2,
76
+ \u7A7A\u5747: 3,
77
+ \u7A7A\u5747\u8861: 3,
78
+ \u7A7A\u52A3: 4
79
+ };
66
80
  var AIR_STATE_TO_TEXT = { 1: "\u7A7A\u786E", 2: "\u7A7A\u4F18", 3: "\u7A7A\u5747", 4: "\u7A7A\u52A3" };
67
- var NODE = "([A-Za-z]{1,2}\\d{0,2})";
68
- var AIR = "(\u7A7A\u786E|\u7A7A\u512A|\u7A7A\u4F18|\u7A7A\u5747|\u7A7A\u52A3)";
69
- var RANK = "(SS|S|A|B|C|D|E)";
70
- var PATTERNS = [
71
- { kind: "node-arrival", re: new RegExp(`^${NODE}\\s*\u70B9\\s*\u5230(\u8FBE|\u9054)\\s*(\\d+)\\s*\u6B21$`, "i"), order: 0 },
72
- { kind: "victory-rank", re: new RegExp(`^${NODE}\\s*\u70B9\\s*${RANK}\\s*(?:\u80DC|\u52DD)?\\s*(\\d+)\\s*\u6B21$`, "i"), order: 1 },
73
- { kind: "node-air-state", re: new RegExp(`^${NODE}\\s*\u70B9\\s*${AIR}\\s*(\\d+)\\s*\u6B21$`, "i"), order: 2 },
74
- { kind: "base-air-defense", re: new RegExp(`^(\u5B88\u5BB6|\u57FA\u5730\u9632\u7A7A|\u5BB6\u91CC)\\s*${AIR}\\s*(\\d+)\\s*\u6B21$`, "i"), order: 3 }
81
+ var CHINESE_DIGIT = { \u4E00: 1, \u4E24: 2, \u4E8C: 2, \u4E09: 3, \u56DB: 4, \u4E94: 5, \u516D: 6, \u4E03: 7, \u516B: 8, \u4E5D: 9, \u5341: 10 };
82
+ var WIN = "[\u80DC\u52DD]?";
83
+ var KILL = "[\u51FB\u64CA]\u7834";
84
+ var REACH = "\u5230[\u8FBE\u9054]";
85
+ var RANKC = "(?<rank>SS|S|A|B|C|D|E)";
86
+ var AIR = "(?<air>\u7A7A\u786E|\u7A7A\u512A|\u7A7A\u4F18|\u7A7A\u5747|\u7A7A\u52A3|\u5236\u7A7A\u786E\u4FDD|\u5236\u7A7A\u78BA\u4FDD|\u5236\u7A7A\u6743\u786E\u4FDD|\u5236\u7A7A\u6B0A\u78BA\u4FDD|\u822A\u7A7A\u512A\u52E2|\u822A\u7A7A\u4F18\u52BF)";
87
+ var CNT = "(?<cnt>\\d+|[\u4E00\u4E24\u4E8C\u4E09\u56DB\u4E94\u516D\u4E03\u516B\u4E5D\u5341])?";
88
+ var CNT_UNIT = "[\u6B21\u56DE\u51F8]?";
89
+ var NODE_DIAN = "(?<node>[A-Za-z]{1,2}\\d{0,2})[\u70B9\u70B9\u9EDE]";
90
+ var NODE_BARE = "(?<node>(?:[A-Za-z]{1,2}\\d{1,2})|(?:[A-Za-z](?!\\d)))";
91
+ var BASE = "(?<base>\u5B88\u5BB6|\u57FA\u5730\u9632\u7A7A\u6218|\u57FA\u5730\u9632\u7A7A|\u5BB6\u91CC|\u9632\u7A7A|\u57FA\u5730)";
92
+ var DECORATION = /(boss|王点|王點|門神|门神|斬殺|斩杀|斬殺線|斩杀线|第\d+[次回凸]?|第[一两二三四五六七八九十]+[次回凸]?|第)/gi;
93
+ var NOISE = /[^A-Za-z0-9〇㐀-䶿一-鿿]/g;
94
+ var FORM_TEMPLATES = [
95
+ { kind: "base-air-defense", build: () => new RegExp(`${BASE}${AIR}${CNT}${CNT_UNIT}`, "i"), usesNode: false },
96
+ { kind: "reach-after", build: (node) => new RegExp(`${REACH}${node}${CNT}${CNT_UNIT}`, "i"), usesNode: true },
97
+ { kind: "node-arrival", build: (node) => new RegExp(`${node}${REACH}${CNT}${CNT_UNIT}`, "i"), usesNode: true },
98
+ { kind: "kill", build: (node) => new RegExp(`${node}${KILL}${CNT}${CNT_UNIT}`, "i"), usesNode: true },
99
+ { kind: "victory-rank", build: (node) => new RegExp(`${node}${RANKC}${WIN}${CNT}${CNT_UNIT}`, "i"), usesNode: true },
100
+ { kind: "count-first", build: (node) => new RegExp(`${node}${CNT}${CNT_UNIT}${RANKC}${WIN}`, "i"), usesNode: true },
101
+ { kind: "node-air-state", build: (node) => new RegExp(`${node}${AIR}${CNT}${CNT_UNIT}`, "i"), usesNode: true }
75
102
  ];
103
+ var FORMS = [];
104
+ for (const template of FORM_TEMPLATES) {
105
+ if (!template.usesNode) {
106
+ FORMS.push({ kind: template.kind, re: template.build(null), countGroup: 3 });
107
+ continue;
108
+ }
109
+ FORMS.push({ kind: template.kind, re: template.build(NODE_DIAN), countGroup: 2, nodeGroup: 1 });
110
+ FORMS.push({ kind: template.kind, re: template.build(NODE_BARE), countGroup: 2, nodeGroup: 1 });
111
+ }
76
112
  function normalizeNodeLabel(value) {
77
113
  return String(value ?? "").trim().toUpperCase();
78
114
  }
115
+ function toHalfWidth(text) {
116
+ let out = "";
117
+ for (const ch of text) {
118
+ const code = ch.codePointAt(0);
119
+ if (code >= 65281 && code <= 65374) out += String.fromCharCode(code - 65248);
120
+ else if (ch === "\u3000") out += " ";
121
+ else out += ch;
122
+ }
123
+ return out;
124
+ }
125
+ function chineseCountToNumber(count) {
126
+ if (count == null || count === "") return 1;
127
+ return CHINESE_DIGIT[count] ?? Number(count);
128
+ }
79
129
  function stripAnnotation(text) {
80
130
  const notes = [];
81
- const cleaned = String(text ?? "").replace(/(([^()]*))|\(([^()]*)\)/g, (_m, a, b) => {
82
- const note = (a ?? b ?? "").trim();
83
- if (note !== "") notes.push(note);
84
- return "";
85
- });
131
+ const cleaned = String(text ?? "").replace(
132
+ /[((【〔「]([^()()【】〔〕「」]*)[))】〕」]/g,
133
+ (_m, inner) => {
134
+ const value = String(inner ?? "").trim();
135
+ if (/^[A-Za-z]{1,2}\d{0,2}[点点點]?$/.test(value)) return value;
136
+ if (value !== "") notes.push(value);
137
+ return "";
138
+ }
139
+ );
86
140
  return { cleaned: cleaned.trim(), notes };
87
141
  }
88
- function parseSegment(raw) {
89
- const segment = raw.trim();
90
- if (segment === "") return null;
142
+ function airStateOf(word) {
143
+ return AIR_ALIAS[word];
144
+ }
145
+ function parseSegmentBody(segment) {
91
146
  const { cleaned, notes } = stripAnnotation(segment);
92
- const text = cleaned.replace(/\s+/g, "");
93
- if (text === "") {
94
- return { ok: false, raw: segment, reason: "\u53BB\u6389\u62EC\u53F7\u6CE8\u8BB0\u540E\u6CA1\u6709\u6761\u4EF6\u5185\u5BB9" };
95
- }
96
- for (const pattern of PATTERNS) {
97
- const match = pattern.re.exec(text);
98
- if (match == null) continue;
99
- if (pattern.kind === "node-arrival") {
100
- return {
101
- ok: true,
102
- raw: segment,
103
- condition: {
104
- kind: "node-arrival",
105
- nodeLabel: normalizeNodeLabel(match[1]),
106
- requiredCount: Number(match[3]),
107
- notes
108
- }
109
- };
147
+ let text = toHalfWidth(cleaned).replace(DECORATION, "").replace(NOISE, "");
148
+ if (text === "") return { matches: null, notes };
149
+ const bossSeen = /boss/i.test(segment) || /boss/i.test(text);
150
+ const matches = [];
151
+ let rest = text;
152
+ let guard = 0;
153
+ while (rest.length > 0 && guard < 20) {
154
+ guard += 1;
155
+ let matched = null;
156
+ for (const form2 of FORMS) {
157
+ const m2 = form2.re.exec(rest);
158
+ if (m2 != null && (matched == null || m2.index < matched.m.index)) {
159
+ matched = { form: form2, m: m2 };
160
+ }
110
161
  }
111
- if (pattern.kind === "victory-rank") {
112
- return {
113
- ok: true,
114
- raw: segment,
115
- condition: {
116
- kind: "victory-rank",
117
- nodeLabel: normalizeNodeLabel(match[1]),
118
- requiredRank: match[2].toUpperCase(),
119
- requiredCount: Number(match[3]),
120
- notes
121
- }
122
- };
162
+ if (matched == null && bossSeen) {
163
+ const bare = /^([A-Za-z]{1,2})(\d+)?([一两二三四五六七八九十]|\d+)?[次回凸]?/.exec(rest);
164
+ if (bare != null && bare[1] != null && (bare[2] != null || bare[3] != null)) {
165
+ const letters = bare[1];
166
+ const digits = bare[2] ?? "";
167
+ const countWord = bare[3] ?? "";
168
+ const restAfter = rest.slice(bare[0].length);
169
+ const digitsAreCount = digits !== "" && /^[次回凸]/.test(restAfter);
170
+ const nodeLabel2 = normalizeNodeLabel(digitsAreCount || digits === "" ? letters : letters + digits);
171
+ const count2 = digitsAreCount ? Number(digits) : chineseCountToNumber(countWord);
172
+ matches.push({
173
+ ok: true,
174
+ condition: {
175
+ kind: "victory-rank",
176
+ nodeLabel: nodeLabel2,
177
+ requiredRank: "A",
178
+ requiredCount: count2,
179
+ notes: [...notes, "boss\u2192\u6309\u51FB\u7834(A\u80DC)\u8BA1"]
180
+ }
181
+ });
182
+ rest = restAfter;
183
+ continue;
184
+ }
185
+ break;
186
+ }
187
+ if (matched == null) break;
188
+ const { form, m } = matched;
189
+ const count = chineseCountToNumber(m.groups?.cnt);
190
+ if (!Number.isInteger(count) || count <= 0) {
191
+ return { matches: [{ ok: false, reason: "\u6B21\u6570\u5FC5\u987B\u662F\u6B63\u6574\u6570" }], notes };
123
192
  }
124
- if (pattern.kind === "node-air-state") {
125
- return {
193
+ const common = { requiredCount: count, notes };
194
+ const nodeLabel = normalizeNodeLabel(m.groups?.node);
195
+ if (form.kind === "base-air-defense") {
196
+ const airState = airStateOf(m.groups.air);
197
+ if (airState == null) return { matches: [{ ok: false, reason: `\u672A\u77E5\u5236\u7A7A\u8BCD\u300C${m.groups.air}\u300D` }], notes };
198
+ matches.push({ ok: true, condition: { kind: "base-air-defense", requiredAirState: airState, ...common } });
199
+ } else if (form.kind === "node-arrival" || form.kind === "reach-after") {
200
+ matches.push({ ok: true, condition: { kind: "node-arrival", nodeLabel, ...common } });
201
+ } else if (form.kind === "kill") {
202
+ matches.push({
126
203
  ok: true,
127
- raw: segment,
128
- condition: {
129
- kind: "node-air-state",
130
- nodeLabel: normalizeNodeLabel(match[1]),
131
- requiredAirState: AIR_TEXT_TO_STATE[match[2]],
132
- requiredCount: Number(match[3]),
133
- notes
134
- }
135
- };
204
+ condition: { kind: "victory-rank", nodeLabel, requiredRank: "A", ...common, notes: [...notes, "\u51FB\u7834\u2192\u6309A\u80DC\u8BA1"] }
205
+ });
206
+ } else if (form.kind === "victory-rank" || form.kind === "count-first") {
207
+ matches.push({ ok: true, condition: { kind: "victory-rank", nodeLabel, requiredRank: m.groups.rank.toUpperCase(), ...common } });
208
+ } else {
209
+ const airState = airStateOf(m.groups.air);
210
+ if (airState == null) return { matches: [{ ok: false, reason: `\u672A\u77E5\u5236\u7A7A\u8BCD\u300C${m.groups.air}\u300D` }], notes };
211
+ matches.push({ ok: true, condition: { kind: "node-air-state", nodeLabel, requiredAirState: airState, ...common } });
136
212
  }
137
- return {
138
- ok: true,
139
- raw: segment,
140
- condition: {
141
- kind: "base-air-defense",
142
- requiredAirState: AIR_TEXT_TO_STATE[match[2]],
143
- requiredCount: Number(match[3]),
144
- notes
145
- }
146
- };
213
+ rest = rest.slice(m.index + m[0].length);
147
214
  }
148
- return { ok: false, raw: segment, reason: "\u672A\u8BC6\u522B\u7684\u683C\u5F0F\uFF08\u793A\u4F8B\uFF1AY2\u70B9S\u80DC1\u6B21 / C2\u70B9\u5230\u8FBE1\u6B21 / P2\u70B9\u7A7A\u4F181\u6B21 / \u5B88\u5BB6\u7A7A\u4F183\u6B21\uFF09" };
215
+ return { matches, notes };
149
216
  }
150
217
  var BUCKET_HEADER = /^(解谜|解密|谜题|削甲|装甲|debuff|gimmick)[::]/;
151
218
  var BUCKET_WORDS = { \u89E3\u8C1C: "gimmick", \u89E3\u5BC6: "gimmick", \u8C1C\u9898: "gimmick", gimmick: "gimmick", \u524A\u7532: "debuff", \u88C5\u7532: "debuff", debuff: "debuff" };
152
- function bucketOf(word) {
153
- return BUCKET_WORDS[word] ?? null;
154
- }
155
219
  function parseGuideText(text, bucketDefault = "debuff") {
156
220
  const report = [];
157
221
  const conditions = [];
@@ -161,27 +225,30 @@ function parseGuideText(text, bucketDefault = "debuff") {
161
225
  let work = segment;
162
226
  const header = BUCKET_HEADER.exec(work.replace(/\s+/g, ""));
163
227
  if (header != null) {
164
- bucket = bucketOf(header[1]) ?? bucket;
228
+ bucket = BUCKET_WORDS[header[1]] ?? bucket;
165
229
  work = work.replace(/^\s*[^::]*[::]\s*/, "");
166
230
  if (work === "") {
167
231
  report.push({ ok: true, raw: segment, header: true, bucket });
168
232
  continue;
169
233
  }
170
234
  }
171
- const parsed = parseSegment(work);
172
- if (parsed == null) continue;
173
- if (!parsed.ok) {
174
- report.push(parsed);
235
+ const { matches } = parseSegmentBody(work);
236
+ if (matches == null) continue;
237
+ if (matches.length === 0 || matches.some((match) => !match.ok)) {
238
+ const failure = matches.find((match) => !match.ok);
239
+ report.push({
240
+ ok: false,
241
+ raw: segment,
242
+ reason: failure?.reason ?? "\u672A\u8BC6\u522B\u7684\u5199\u6CD5\uFF08\u793A\u4F8B\uFF1AY2\u70B9S\u80DC1\u6B21 / P3\u70B9A2\u6B21 / \u5B88\u5BB6\u7A7A\u4F183\u6B21\uFF09"
243
+ });
175
244
  continue;
176
245
  }
177
- if (!Number.isInteger(parsed.condition.requiredCount) || parsed.condition.requiredCount <= 0) {
178
- report.push({ ok: false, raw: parsed.raw, reason: "\u6B21\u6570\u5FC5\u987B\u662F\u6B63\u6574\u6570" });
179
- continue;
246
+ for (const match of matches) {
247
+ match.condition.bucket = bucket;
248
+ match.condition.raw = segment;
249
+ conditions.push(match.condition);
250
+ report.push({ ok: true, raw: segment, condition: match.condition, multiple: matches.length > 1 });
180
251
  }
181
- parsed.condition.bucket = bucket;
182
- parsed.condition.raw = parsed.raw;
183
- conditions.push(parsed.condition);
184
- report.push({ ok: true, raw: parsed.raw, condition: parsed.condition });
185
252
  }
186
253
  return { conditions, report };
187
254
  }
@@ -1314,9 +1381,9 @@ function createLedgerView(controller2) {
1314
1381
  rows: 4,
1315
1382
  value: guideText,
1316
1383
  onChange: (event) => setGuideText(event.currentTarget.value),
1317
- placeholder: "\u652F\u6301\uFF1AY2\u70B9S\u80DC1\u6B21 / L2\u70B9A\u80DC1\u6B21 / P3\u70B9A2\u6B21 / C2\u70B9\u5230\u8FBE1\u6B21 / P2\u70B9\u7A7A\u4F181\u6B21 / \u5B88\u5BB6\u7A7A\u4F183\u6B21\n\u53EF\u5E26\u6CE8\u8BB0\uFF1AG\u70B9\uFF08P1boss\uFF09S\u80DC1\u6B21\n\u53EF\u7528\u5206\u7EC4\u884C\uFF1A\u89E3\u8C1C\uFF1A \u6216 \u524A\u7532\uFF1A"
1384
+ placeholder: "\u4F8B\u5982\uFF1AY2\u70B9S\u80DC1\u6B21 / L2\u70B9A\u80DC1\u6B21 / P3\u70B9A2\u6B21 / C2\u70B9\u5230\u8FBE1\u6B21 / P2\u70B9\u7A7A\u4F181\u6B21 / \u5B88\u5BB6\u7A7A\u4F183\u6B21\n\u4E5F\u8BA4\uFF1AP1boss\u4E00\u6B21 / \u5B88\u5BB6\u7A7A\u4F18\u4E24\u6B21 / G\u70B92\u6B21S\u80DC / S\u70B9\u51FB\u78342\u56DE / \uFF30\uFF12\u70B9\u7A7A\u512A\uFF11\u6B21\n\u53EF\u5E26\u6CE8\u8BB0\u4E0E\u5206\u7EC4\u884C\uFF1AG\u70B9\uFF08P1boss\uFF09S\u80DC1\u6B21\u3001\u89E3\u8C1C\uFF1A \u6216 \u524A\u7532\uFF1A"
1318
1385
  }
1319
- ), /* @__PURE__ */ import_react.default.createElement("div", { style: styles.actions }, /* @__PURE__ */ import_react.default.createElement(import_core.HTMLSelect, { value: bucket, onChange: (event) => setBucket(event.currentTarget.value) }, /* @__PURE__ */ import_react.default.createElement("option", { value: "debuff" }, "\u5BFC\u5165\u4E3A\u524A\u7532\u6761\u4EF6"), /* @__PURE__ */ import_react.default.createElement("option", { value: "gimmick" }, "\u5BFC\u5165\u4E3A\u89E3\u8C1C\u6761\u4EF6")), /* @__PURE__ */ import_react.default.createElement(import_core.Button, { intent: "primary", onClick: importGuide, disabled: mapRef == null }, "\u5BFC\u5165\u5230\u5F53\u524D\u6D77\u57DF", mapRef != null ? ` ${mapLabel(mapRef)}` : ""), mapRef == null && /* @__PURE__ */ import_react.default.createElement("span", { style: styles.muted }, "\u7B49\u5F85\u6D77\u57DF\u8BC6\u522B\u2026")), report != null && /* @__PURE__ */ import_react.default.createElement("div", { style: { marginTop: 8 } }, report.error != null ? /* @__PURE__ */ import_react.default.createElement(import_core.Callout, { intent: "danger" }, report.error) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { style: { marginBottom: 4 } }, /* @__PURE__ */ import_react.default.createElement("strong", null, report.mapLabel), "\uFF1A\u8BC6\u522B ", report.importedCount, " \u6761\u3002"), report.report.map((line, index) => /* @__PURE__ */ import_react.default.createElement("div", { key: index, style: { ...styles.reportLine, color: line.ok ? void 0 : "#cd4a3e" } }, line.header ? "\u2699" : line.ok ? "\u2714" : "\u2718", " ", line.raw, line.header ? ` \u2192 \u5206\u7EC4\uFF1A${BUCKETS[line.bucket]}` : "", !line.ok && line.reason != null ? ` \u2014 ${line.reason}` : ""))))), mapState != null && closedRounds.length > 0 && /* @__PURE__ */ import_react.default.createElement(import_core.Card, { style: styles.section, elevation: import_core.Elevation.ZERO }, /* @__PURE__ */ import_react.default.createElement("h3", { style: { marginTop: 0 } }, mapLabel(mapState), " \xB7 \u5386\u53F2\u8F6E\u6B21"), closedRounds.map((round) => /* @__PURE__ */ import_react.default.createElement(RoundSummary, { key: round.id, round, mapState }))), otherMaps.length > 0 && /* @__PURE__ */ import_react.default.createElement(import_core.Card, { style: styles.section, elevation: import_core.Elevation.ZERO }, /* @__PURE__ */ import_react.default.createElement("h3", { style: { marginTop: 0 } }, "\u5176\u4ED6\u6D77\u57DF"), otherMaps.map((ms) => {
1386
+ ), /* @__PURE__ */ import_react.default.createElement("div", { style: styles.actions }, /* @__PURE__ */ import_react.default.createElement(import_core.HTMLSelect, { value: bucket, onChange: (event) => setBucket(event.currentTarget.value) }, /* @__PURE__ */ import_react.default.createElement("option", { value: "debuff" }, "\u5BFC\u5165\u4E3A\u524A\u7532\u6761\u4EF6"), /* @__PURE__ */ import_react.default.createElement("option", { value: "gimmick" }, "\u5BFC\u5165\u4E3A\u89E3\u8C1C\u6761\u4EF6")), /* @__PURE__ */ import_react.default.createElement(import_core.Button, { intent: "primary", onClick: importGuide, disabled: mapRef == null }, "\u5BFC\u5165\u5230\u5F53\u524D\u6D77\u57DF", mapRef != null ? ` ${mapLabel(mapRef)}` : ""), mapRef == null && /* @__PURE__ */ import_react.default.createElement("span", { style: styles.muted }, "\u7B49\u5F85\u6D77\u57DF\u8BC6\u522B\u2026")), report != null && /* @__PURE__ */ import_react.default.createElement("div", { style: { marginTop: 8 } }, report.error != null ? /* @__PURE__ */ import_react.default.createElement(import_core.Callout, { intent: "danger" }, report.error) : /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { style: { marginBottom: 4 } }, /* @__PURE__ */ import_react.default.createElement("strong", null, report.mapLabel), "\uFF1A\u8BC6\u522B ", report.importedCount, " \u6761\u3002"), report.report.map((line, index) => /* @__PURE__ */ import_react.default.createElement("div", { key: index, style: { ...styles.reportLine, color: line.ok ? void 0 : "#cd4a3e" } }, line.header ? "\u2699" : line.ok ? "\u2714" : "\u2718", " ", line.raw, line.header ? ` \u2192 \u5206\u7EC4\uFF1A${BUCKETS[line.bucket]}` : "", !line.header && line.ok && line.condition != null ? ` \u2192 ${conditionLabel(line.condition)}` : "", !line.ok && line.reason != null ? ` \u2014 ${line.reason}` : ""))))), mapState != null && closedRounds.length > 0 && /* @__PURE__ */ import_react.default.createElement(import_core.Card, { style: styles.section, elevation: import_core.Elevation.ZERO }, /* @__PURE__ */ import_react.default.createElement("h3", { style: { marginTop: 0 } }, mapLabel(mapState), " \xB7 \u5386\u53F2\u8F6E\u6B21"), closedRounds.map((round) => /* @__PURE__ */ import_react.default.createElement(RoundSummary, { key: round.id, round, mapState }))), otherMaps.length > 0 && /* @__PURE__ */ import_react.default.createElement(import_core.Card, { style: styles.section, elevation: import_core.Elevation.ZERO }, /* @__PURE__ */ import_react.default.createElement("h3", { style: { marginTop: 0 } }, "\u5176\u4ED6\u6D77\u57DF"), otherMaps.map((ms) => {
1320
1387
  const legacy = ms.rounds.find((round) => round.bucket === "legacy");
1321
1388
  const window2 = controller2.legacyWindowFor(ms.mapId);
1322
1389
  return /* @__PURE__ */ import_react.default.createElement("div", { key: ms.mapId, style: { padding: "8px 0", borderBottom: "1px solid rgba(128,128,128,.15)" } }, /* @__PURE__ */ import_react.default.createElement("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, /* @__PURE__ */ import_react.default.createElement(import_core.Tag, { minimal: true }, mapLabel(ms)), /* @__PURE__ */ import_react.default.createElement("span", { style: styles.muted }, "\u6761\u4EF6 ", ms.conditions.length, " \xB7 \u8F6E\u6B21 ", ms.rounds.length, ms.cleared ? " \xB7 \u5DF2\u901A\u5173" : "", legacy != null ? " \xB7 \u542B\u65E7\u6570\u636E\u5F52\u6863" : ""), legacy != null && window2 != null && /* @__PURE__ */ import_react.default.createElement(import_core.Button, { small: true, onClick: () => controller2.replayLegacy(ms.mapId, window2.from, window2.to) }, "\u56DE\u653E\u5386\u53F2\u524A\u7532\u4E00\u8F6E")));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poi-plugin-gimmick-tracker",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "解密·削甲进度:自动对账活动海域解密/削甲条件进度(轮次账本,阶段自动旋转,无人工确认卡)",
5
5
  "main": "index.js",
6
6
  "files": [