poi-plugin-inventory-cleaner 1.14.2 → 1.15.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.
@@ -0,0 +1 @@
1
+ {"A1":101,"A2":102,"A3":103,"A4":104,"A5":105,"A7":106,"A12":107,"A8":108,"A14":109,"A15":110,"A17":111,"A18":112,"A13":113,"A19":114,"A6":115,"A9":116,"A11":117,"A16":118,"A20":119,"A10":120,"A21":121,"A22":122,"A23":123,"A24":124,"A25":125,"A26":126,"A27":127,"A28":128,"A29":129,"A30":130,"A31":131,"A32":132,"A33":133,"WA01":134,"WA02":135,"A34":136,"A35":137,"A36":138,"A37":139,"A38":140,"A39":141,"A40":142,"A41":143,"A42":144,"A43":145,"A45":146,"A44":147,"A46":148,"A47":149,"A48":150,"A49":151,"A50":152,"A51":153,"A52":154,"A53":155,"A54":156,"A55":157,"A92":158,"A56":161,"A57":162,"A58":163,"A59":164,"A60":165,"A61":166,"A62":167,"A63":168,"A64":169,"A65":170,"A66":171,"A67":172,"A68":173,"A69":174,"A70":175,"A71":176,"A72":177,"A73":178,"A74":179,"A75":180,"A76":181,"A77":182,"A78":183,"A79":184,"A80":185,"A81":186,"A82":187,"A83":188,"A84":189,"A85":190,"A86":191,"A87":192,"A88":193,"A89":194,"A90":195,"A91":196,"A93":197,"A94":198,"L2606A1":199,"Bd1":201,"B1":202,"B2":203,"B4":204,"B5":205,"B6":206,"B7":207,"B8":208,"B9":209,"Bd3":210,"Bd4":211,"Bd6":212,"Bw3":213,"Bw1":214,"B3":215,"Bd2":216,"B10":217,"Bd5":218,"B11":219,"Bw2":220,"Bw4":221,"B12":222,"B13":223,"B14":224,"B15":225,"Bd7":226,"B16":227,"Bw5":228,"Bw6":229,"Bd8":230,"B17":231,"B18":232,"B19":233,"B135":235,"B136":236,"B138":237,"B20":239,"B21":240,"Bw7":241,"Bw8":242,"Bw9":243,"B22":244,"WB01":245,"WB02":246,"B23":247,"B24":248,"Bm1":249,"B25":250,"B26":251,"B27":252,"B28":253,"B29":254,"B30":255,"Bm2":256,"Bm3":257,"B31":258,"Bm4":259,"B32":260,"Bw10":261,"B33":262,"B34":263,"Bm6":264,"Bm5":265,"Bm7":266,"B35":267,"B36":268,"B37":269,"B39":270,"B38":271,"B40":272,"B41":273,"B42":274,"B43":275,"B44":276,"B45":277,"B46":278,"B47":279,"Bm8":280,"B129":281,"B130":282,"B137":283,"Bq11":284,"B49":285,"B48":286,"B50":287,"B51":288,"B52":289,"B128":290,"B134":291,"B133":292,"B53":293,"B54":294,"B55":295,"B56":296,"B57":297,"B124":298,"B125":299,"C1":301,"Cw1":302,"Cd1":303,"Cd2":304,"WC01":306,"C5":307,"C6":308,"C7":309,"Cm1":311,"C9":312,"Cs8":313,"Cs2":314,"Cs3":315,"C13":316,"C15":317,"Cm2":318,"C17":319,"C18":320,"C20":322,"C21":323,"C22":324,"C23":325,"Cs7":326,"C25":327,"C27":328,"Cq1":330,"C31":331,"C32":332,"C33":333,"C34":334,"C35":335,"C37":336,"Cq2":337,"C39":338,"Cq3":339,"Cq4":342,"C46":343,"C48":344,"Cy1":345,"Cy2":346,"C51":347,"Cy3":348,"Cy4":350,"C56":351,"C57":352,"Cy5":353,"Cy6":354,"Cy7":355,"Cy8":356,"Cy9":357,"C68":358,"C70":360,"Cy10":362,"C73":364,"C75":365,"C76":366,"Cy11":368,"Cy12":371,"Cy13":372,"Cy14":373,"Cy15":375,"C77":376,"Cy16":377,"L2507C1":380,"L2508C1":381,"L2606C1":382,"L2609C1":384,"L2609C2":385,"D1":401,"Dd1":402,"Dd2":403,"Dw1":404,"D5":405,"D6":406,"D7":408,"D8":409,"Dw2":410,"Dw3":411,"D10":412,"D12":413,"D13":414,"D14":415,"D15":416,"D16":417,"D17":418,"D18":419,"D19":420,"D20":422,"D21":423,"Dm1":424,"D23":425,"Dq1":426,"D25":427,"Dq2":428,"D27":429,"D28":430,"D29":431,"D30":432,"D31":433,"Dy1":434,"Dy2":436,"Dy3":437,"Dy4":438,"Dy5":439,"Dy6":440,"Dy7":442,"D39":443,"Dy8":444,"D41":445,"D42":446,"D43":447,"L2603D1":450,"E1":501,"E2":502,"Ed1":503,"Ed2":504,"F1":601,"F2":602,"F3":603,"F4":604,"Fd1":605,"Fd2":606,"Fd3":607,"Fd4":608,"Fd5":609,"F10":610,"WF01":611,"F11":612,"Fw1":613,"F13":614,"F14":615,"F15":616,"F16":617,"F17":618,"Fd6":619,"F88":620,"F89":621,"F19":622,"F20":623,"F21":624,"F23":625,"Fm1":626,"F24":627,"Fm2":628,"F26":629,"F28":630,"F27":631,"F29":632,"F30":633,"F31":634,"F32":635,"F33":636,"Fq1":637,"Fw2":638,"F36":639,"SF1":640,"F37":641,"F38":642,"Fq2":643,"F40":644,"Fm3":645,"F42":646,"F43":647,"F44":648,"F45":649,"F46":650,"F47":651,"F48":652,"Fq9":653,"Fy2":654,"Fy3":655,"F49":656,"Fy1":657,"F50":658,"F51":659,"F52":660,"F53":661,"F54":662,"Fq3":663,"F56":664,"F57":665,"F58":666,"F59":667,"F60":668,"F61":669,"F63":670,"F62":671,"F64":672,"Fd7":673,"Fd8":674,"Fq4":675,"Fw3":676,"Fw4":677,"Fq5":678,"F71":679,"Fq6":680,"Fy4":681,"F73":682,"F74":683,"F75":684,"F76":685,"Fq7":686,"F78":687,"Fq8":688,"F80":689,"F81":690,"F82":691,"F83":692,"F84":693,"F85":694,"F86":695,"F87":696,"F91":698,"G1":701,"Gd1":702,"Gw1":703,"G4":704,"G5":705,"Gy1":714,"Gy2":715,"Gy3":716,"Gy4":717,"G10":718,"G11":719,"B58":805,"B59":806,"B60":807,"B61":808,"B62":809,"B63":810,"B64":811,"B65":812,"B66":813,"B68":814,"B69":815,"B67":816,"B70":817,"B71":818,"B72":819,"B73":820,"B74":821,"Bq1":822,"B75":823,"B76":824,"B77":825,"B78":826,"B79":827,"B80":828,"B81":829,"B82":830,"B139":833,"B83":834,"B84":835,"B85":836,"B86":837,"B87":838,"B88":839,"B89":842,"B90":844,"Bq12":845,"B91":846,"B163":847,"B92":848,"B93":849,"B94":850,"B95":851,"B96":852,"B97":853,"Bq2":854,"B98":855,"B99":856,"B100":857,"B101":858,"B102":859,"B103":860,"Bq3":861,"Bq4":862,"B104":863,"B105":864,"B106":865,"B107":869,"B108":870,"B109":871,"Bq10":872,"Bq5":873,"B110":874,"Bq6":875,"B111":876,"B112":877,"B113":878,"B114":879,"B115":880,"B116":881,"B117":884,"B118":885,"B119":886,"B120":887,"Bq7":888,"B121":889,"B122":890,"B123":891,"B126":892,"Bq8":893,"Bq9":894,"B127":895,"B131":896,"B132":897,"B162":898,"B140":901,"B141":902,"Bq13":903,"By1":904,"By2":905,"B142":911,"By3":912,"B143":913,"By4":914,"L2212B1":915,"B144":916,"B145":917,"B146":918,"B147":919,"B148":920,"B149":921,"B150":922,"B151":923,"B152":924,"B153":925,"B154":926,"B155":927,"By5":928,"B156":929,"B157":930,"B158":931,"B159":933,"B160":934,"B161":935,"B164":936,"B165":937,"B166":938,"B167":939,"B168":940,"B169":943,"By6":944,"By7":945,"By8":946,"By9":947,"By10":948,"B170":949,"B171":952,"B172":957,"B173":958,"B174":961,"B175":966,"B176":967,"B177":968,"B178":969,"B179":970,"By11":973,"B180":974,"By12":975,"B181":976,"B182":977,"B183":978,"B184":980,"B185":981,"B186":982,"B187":983,"B188":989,"B193":993,"B189":995,"B190":996,"B191":997,"B192":998,"B194":1001,"B195":1003,"B196":1004,"By13":1005,"B197":1006,"B198":1007,"B199":1008,"B212":1010,"By14":1012,"B200":1013,"B201":1014,"B202":1015,"B203":1016,"B204":1017,"By15":1018,"B205":1019,"L2409B1":1020,"B206":1021,"B207":1023,"B208":1025,"B209":1029,"B210":1033,"B211":1036,"L2508B1":1037,"L2610B6":1039,"B213":1041,"B214":1042,"B215":1044,"By16":1045,"B216":1047,"L2606B1":1048,"L2606B2":1049,"By17":1050,"By18":1051,"B217":1052,"F96":1101,"F97":1102,"Fy5":1103,"Fy6":1104,"Fy7":1105,"F101":1106,"Fy8":1107,"F103":1108,"F104":1109,"F105":1111,"F106":1112,"F107":1113,"F108":1114,"F109":1115,"F110":1116,"F111":1117,"F112":1118,"Fy9":1120,"Fy10":1123,"F115":1127,"F116":1128,"F117":1129,"F118":1130,"F119":1131,"F120":1132,"F121":1133,"F122":1134,"F123":1137,"Fy11":1138,"F124":1139,"F125":1140,"F126":1141,"F127":1142,"F128":1143,"F129":1145,"F130":1148,"L2605F2":1150,"F131":1151,"F132":1152,"F133":1153,"F134":1154,"F135":1155,"F136":1158,"F137":1159,"F138":1160,"F139":1161,"F140":1162,"F141":1163,"F142":1164,"Fd9":1166,"Fw5":1167,"F143":1170}
package/i18n/en-US.json CHANGED
@@ -29,6 +29,7 @@
29
29
  "stock.consumeQuestEquip": "quest (equip)",
30
30
  "stock.consumeQuestStore": "quest (store)",
31
31
  "stock.consumeOther": "other",
32
+ "stock.consumeHidden": "{{count}} completed quest(s) hidden",
32
33
  "stock.keepLabel": "Keep",
33
34
  "stock.keepUnit": "",
34
35
  "stock.keepPlaceholder": "guide",
package/i18n/zh-CN.json CHANGED
@@ -29,6 +29,7 @@
29
29
  "stock.consumeQuestEquip": "任务·装",
30
30
  "stock.consumeQuestStore": "任务·库",
31
31
  "stock.consumeOther": "其他",
32
+ "stock.consumeHidden": "已隐藏 {{count}} 个已完成任务",
32
33
  "stock.keepLabel": "保留",
33
34
  "stock.keepUnit": "件",
34
35
  "stock.keepPlaceholder": "攻略",
package/index.js CHANGED
@@ -374,6 +374,13 @@ function StockRow({ row, onKeepChange }) {
374
374
  ),
375
375
  ),
376
376
  ),
377
+ row.consumedHidden > 0
378
+ ? h(
379
+ 'div',
380
+ { className: 'stock-consume-hidden' },
381
+ t('stock.consumeHidden', { count: row.consumedHidden }),
382
+ )
383
+ : null,
377
384
  )
378
385
  : null,
379
386
  )
package/lib/inventory.js CHANGED
@@ -13,9 +13,11 @@ const INDEX = require('../data/equips-index.json')
13
13
  const SCRAP = require('../data/scrap-guide.json')
14
14
  const CONSUMED = require('../data/consumed-materials.json')
15
15
  const GUIDE = require('../data/equipment-guide.json')
16
+ const QUEST_CODES = require('../data/quest-codes.json')
16
17
  const { getProtection } = require('./protect')
17
18
  const { buildQuestNeeds } = require('./quest-needs')
18
19
  const { buildTypeIndex } = require('./equip-types')
20
+ const { getCompletedQuestNos } = require('./quest-status')
19
21
 
20
22
  function isRecord(value) {
21
23
  return typeof value === 'object' && value !== null && !Array.isArray(value)
@@ -36,9 +38,16 @@ const CONSUME_KINDS = {
36
38
  /**
37
39
  * 把「《锤》xxx《拆》yyy」这种素材用途串拆成条目,任务类排在改修类前面。
38
40
  */
39
- function parseConsumed(text) {
41
+ function questNoFromText(text) {
42
+ const matched = String(text || '').match(/^(?:\([^)]*\)\s*)?([A-Za-z]{1,2}\d{1,3})\s*[::]/)
43
+ if (!matched) return 0
44
+ return QUEST_CODES[matched[1]] || 0
45
+ }
46
+
47
+ function parseConsumed(text, options = {}) {
40
48
  const value = String(text || '').trim()
41
49
  if (!value) return []
50
+ const completed = options.completed || null
42
51
 
43
52
  const parts = value
44
53
  .split(/(?=《)/)
@@ -49,7 +58,13 @@ function parseConsumed(text) {
49
58
  const matched = raw.match(/^《([^》]{1,3})》\s*(.*)$/)
50
59
  const tag = matched ? matched[1] : ''
51
60
  const detail = matched ? matched[2] : raw
52
- return { tag, kind: CONSUME_KINDS[tag] || 'other', text: detail }
61
+ const kind = CONSUME_KINDS[tag] || 'other'
62
+ let done = false
63
+ if (kind === 'quest' && completed) {
64
+ const no = questNoFromText(detail)
65
+ done = no ? completed.has(no) : false
66
+ }
67
+ return { tag, kind, text: detail, done }
53
68
  })
54
69
 
55
70
  const rank = { quest: 0, improve: 1, other: 2 }
@@ -82,6 +97,7 @@ function buildInventory(state, options = {}) {
82
97
  const planned = protection.planned || new Set()
83
98
  const keeps = options.keeps || {}
84
99
  const typeIndex = options.typeIndex || buildTypeIndex(state)
100
+ const completedQuests = options.completedQuests || getCompletedQuestNos(state)
85
101
  const questNeeds = options.questNeeds || buildQuestNeeds(state)
86
102
 
87
103
  const byId = new Map()
@@ -108,6 +124,7 @@ function buildInventory(state, options = {}) {
108
124
  const consumed = CONSUMED[String(row.id)] || ''
109
125
 
110
126
  const typeInfo = typeIndex.get(row.id) || null
127
+ const allConsumed = parseConsumed(consumed, { completed: completedQuests })
111
128
  const verdict = rule ? rule.verdict : 'unknown'
112
129
  const inPlan = planned.has(row.id)
113
130
  const need = questNeeds.byEquip.get(row.id) || null
@@ -151,7 +168,8 @@ function buildInventory(state, options = {}) {
151
168
  usage: rule ? rule.usage : '',
152
169
  obtain: rule ? rule.obtain : '',
153
170
  consumed,
154
- consumedItems: parseConsumed(consumed),
171
+ consumedItems: allConsumed.filter((item) => !item.done),
172
+ consumedHidden: allConsumed.filter((item) => item.done).length,
155
173
  stage: guide ? guide.stage : '',
156
174
  note: guide ? guide.note : '',
157
175
  inPlan,
@@ -284,6 +302,7 @@ function filterRows(rows, options = {}) {
284
302
  module.exports = {
285
303
  OTHER_CATEGORY,
286
304
  parseConsumed,
305
+ questNoFromText,
287
306
  SORTS,
288
307
  sortRows,
289
308
  applyCategoryKeep,
@@ -0,0 +1,121 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * 已完成任务的判定。
5
+ *
6
+ * 来源(都是只读):
7
+ * - poi 的进行中任务里 api_state === 3 的(已接取且已完成,尚未领奖时还在列表里);
8
+ * - 任务信息插件 ext 里 api_state === 3 的;
9
+ * - 任务规划插件的 completed 集合(最完整)。
10
+ */
11
+
12
+ const fs = require('fs')
13
+ const path = require('path')
14
+ const os = require('os')
15
+
16
+ const CACHE_TTL = 15000
17
+ let plannerCache = { at: 0, mtime: 0, nos: null }
18
+
19
+ function appDataPath() {
20
+ if (process.env.POI_APPDATA_PATH) return process.env.POI_APPDATA_PATH
21
+ if (process.env.APPDATA) return process.env.APPDATA
22
+ return path.join(os.homedir(), '.config')
23
+ }
24
+
25
+ function questPlannerStatePath() {
26
+ if (process.env.QUEST_PLANNER_STATE) return process.env.QUEST_PLANNER_STATE
27
+ const root = path.join(appDataPath(), 'poi', 'quest-planner')
28
+ try {
29
+ const entries = fs.readdirSync(root, { withFileTypes: true })
30
+ let best = null
31
+ for (const entry of entries) {
32
+ if (!entry.isDirectory()) continue
33
+ const file = path.join(root, entry.name, 'state.json')
34
+ try {
35
+ const stat = fs.statSync(file)
36
+ if (!best || stat.mtimeMs > best.mtimeMs) best = { file, mtimeMs: stat.mtimeMs }
37
+ } catch (_error) {
38
+ // 该目录没有 state.json
39
+ }
40
+ }
41
+ if (best) return best.file
42
+ } catch (_error) {
43
+ // 没装任务规划插件
44
+ }
45
+ return path.join(root, 'state.json')
46
+ }
47
+
48
+ function readPlannerCompleted(now = Date.now()) {
49
+ if (plannerCache.nos && now - plannerCache.at < CACHE_TTL) return plannerCache.nos
50
+
51
+ const file = questPlannerStatePath()
52
+ try {
53
+ const stat = fs.statSync(file)
54
+ if (plannerCache.nos && plannerCache.mtime === stat.mtimeMs) {
55
+ plannerCache.at = now
56
+ return plannerCache.nos
57
+ }
58
+ const raw = JSON.parse(fs.readFileSync(file, 'utf8').replace(/^\uFEFF/, ''))
59
+ const completed = raw && raw.completed && typeof raw.completed === 'object' ? raw.completed : {}
60
+ const nos = new Set()
61
+ for (const key of Object.keys(completed)) {
62
+ const no = Number(key)
63
+ if (no) nos.add(no)
64
+ }
65
+ plannerCache = { at: now, mtime: stat.mtimeMs, nos }
66
+ return nos
67
+ } catch (_error) {
68
+ plannerCache = { at: now, mtime: 0, nos: new Set() }
69
+ return plannerCache.nos
70
+ }
71
+ }
72
+
73
+ function collectFromState(state) {
74
+ const nos = new Set()
75
+ const active =
76
+ state && state.info && state.info.quests ? state.info.quests.activeQuests : null
77
+ if (active && typeof active === 'object') {
78
+ for (const entry of Object.values(active)) {
79
+ const detail = entry && entry.detail ? entry.detail : entry
80
+ if (!detail || Number(detail.api_state) !== 3) continue
81
+ const no = Number(detail.api_no)
82
+ if (no) nos.add(no)
83
+ }
84
+ }
85
+
86
+ const ext = state && state.ext ? state.ext : null
87
+ if (ext) {
88
+ for (const id of ['poi-plugin-quest-info-2', 'poi-plugin-quest-info']) {
89
+ const entry = ext[id]
90
+ if (!entry) continue
91
+ const list =
92
+ (Array.isArray(entry.questList) && entry.questList) ||
93
+ (entry._ && Array.isArray(entry._.questList) && entry._.questList) ||
94
+ null
95
+ if (!list) continue
96
+ for (const detail of list) {
97
+ if (!detail || Number(detail.api_state) !== 3) continue
98
+ const no = Number(detail.api_no)
99
+ if (no) nos.add(no)
100
+ }
101
+ break
102
+ }
103
+ }
104
+ return nos
105
+ }
106
+
107
+ function getCompletedQuestNos(state) {
108
+ const nos = new Set(collectFromState(state))
109
+ for (const no of readPlannerCompleted()) nos.add(no)
110
+ return nos
111
+ }
112
+
113
+ function resetCaches() {
114
+ plannerCache = { at: 0, mtime: 0, nos: null }
115
+ }
116
+
117
+ module.exports = {
118
+ getCompletedQuestNos,
119
+ readPlannerCompleted,
120
+ resetCaches,
121
+ }
package/lib/style.js CHANGED
@@ -437,6 +437,12 @@ const CSS = `
437
437
  background: rgba(6, 182, 212, 0.14);
438
438
  }
439
439
 
440
+ .stock-consume-hidden {
441
+ margin-top: 2px;
442
+ font-size: 10px;
443
+ opacity: 0.4;
444
+ }
445
+
440
446
  .stock-consume-text {
441
447
  flex: 1 1 auto;
442
448
  min-width: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poi-plugin-inventory-cleaner",
3
- "version": "1.14.2",
3
+ "version": "1.15.1",
4
4
  "description": "库存清理大师:对照改修攻略与保留上限,找出囤积过量、可以清理的装备,并标出不能拆的改修材料。",
5
5
  "main": "index.js",
6
6
  "files": [