poi-plugin-equips-farm 1.0.2 → 1.0.5
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 +6 -0
- package/index.es +9 -2
- package/initial_equip_ships.json +5502 -0
- package/lib/data-processor.es +147 -18
- package/lib/master-cache.es +56 -0
- package/package.json +13 -2
- package/views/components/ShipList.es +12 -3
package/lib/data-processor.es
CHANGED
|
@@ -3,12 +3,28 @@
|
|
|
3
3
|
|
|
4
4
|
let cachedFarmingMap = null
|
|
5
5
|
let cachedWctfShips = null
|
|
6
|
+
let cachedMasterShips = null
|
|
7
|
+
let cachedParentMap = {} // Cache for parent lookups
|
|
8
|
+
|
|
9
|
+
// Helper: Find Base Ancestor (Module Level)
|
|
10
|
+
const findRoot = (id) => {
|
|
11
|
+
let curr = id
|
|
12
|
+
let steps = 0
|
|
13
|
+
while(cachedParentMap[curr] && steps < 20) {
|
|
14
|
+
curr = cachedParentMap[curr]
|
|
15
|
+
steps++
|
|
16
|
+
}
|
|
17
|
+
return curr
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
6
22
|
|
|
7
23
|
// --- 1. WCTF Processing ---
|
|
8
24
|
|
|
9
|
-
export function getFarmingMap(wctf) {
|
|
25
|
+
export function getFarmingMap(wctf, $ships) {
|
|
10
26
|
if (!wctf || !wctf.ships) return {}
|
|
11
|
-
if (cachedFarmingMap && cachedWctfShips === wctf.ships) return cachedFarmingMap
|
|
27
|
+
if (cachedFarmingMap && cachedWctfShips === wctf.ships && cachedMasterShips === $ships) return cachedFarmingMap
|
|
12
28
|
|
|
13
29
|
const map = {}
|
|
14
30
|
const ships = wctf.ships
|
|
@@ -69,6 +85,39 @@ export function getFarmingMap(wctf) {
|
|
|
69
85
|
}
|
|
70
86
|
})
|
|
71
87
|
|
|
88
|
+
// 2.1 Pass: Supplement Parent Map from Master Data (Block 2 & Runtime)
|
|
89
|
+
// Master Data links Forward (api_aftershipid), so we reverse it to get Child -> Parent
|
|
90
|
+
const processMasterForParents = (source) => {
|
|
91
|
+
if (!source) return
|
|
92
|
+
Object.values(source).forEach(s => {
|
|
93
|
+
const afterId = parseInt(s.api_aftershipid)
|
|
94
|
+
const currentId = s.api_id
|
|
95
|
+
|
|
96
|
+
// If this ship remodels into something valid
|
|
97
|
+
if (afterId > 0) {
|
|
98
|
+
// If the target (afterId) doesn't have a known parent yet, record this connection
|
|
99
|
+
// This links "Next Form" back to "Current Form"
|
|
100
|
+
if (!parentMap[afterId]) {
|
|
101
|
+
parentMap[afterId] = currentId
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Load Master Cache if not already loaded (Optimization: Reuse if loaded later? No, needed here for findRoot)
|
|
108
|
+
// We need to move the cache loading up or do it twice.
|
|
109
|
+
// Let's load it once at the top of function for consistency.
|
|
110
|
+
let masterCache = { ships: {} }
|
|
111
|
+
try {
|
|
112
|
+
const { loadMasterCache } = require('./master-cache')
|
|
113
|
+
masterCache = loadMasterCache() || { ships: {} }
|
|
114
|
+
} catch (e) {
|
|
115
|
+
// console.warn ...
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
processMasterForParents(masterCache.ships)
|
|
119
|
+
processMasterForParents($ships)
|
|
120
|
+
|
|
72
121
|
// Helper: Find Base Ancestor
|
|
73
122
|
const findRoot = (id) => {
|
|
74
123
|
let curr = id
|
|
@@ -110,8 +159,95 @@ export function getFarmingMap(wctf) {
|
|
|
110
159
|
})
|
|
111
160
|
})
|
|
112
161
|
|
|
162
|
+
// 4th Pass: Merge External Initial Equipment Data (from Akashi-list)
|
|
163
|
+
try {
|
|
164
|
+
// Master Cache already loaded above
|
|
165
|
+
|
|
166
|
+
const initialEquipData = require('../initial_equip_ships.json')
|
|
167
|
+
if (initialEquipData) {
|
|
168
|
+
Object.keys(initialEquipData).forEach(eqIdStr => {
|
|
169
|
+
const eqId = parseInt(eqIdStr, 10)
|
|
170
|
+
const providers = initialEquipData[eqIdStr]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
providers.forEach(p => {
|
|
176
|
+
// p has { name, level }
|
|
177
|
+
|
|
178
|
+
let foundId = -1;
|
|
179
|
+
|
|
180
|
+
// Priority 1: Check WCTF (Block 1) - Best for multilingual support
|
|
181
|
+
for (const sId in ships) {
|
|
182
|
+
const s = ships[sId];
|
|
183
|
+
const names = s.name || {};
|
|
184
|
+
if (
|
|
185
|
+
names.ja_jp === p.name ||
|
|
186
|
+
names.zh_cn === p.name ||
|
|
187
|
+
names.japanese === p.name ||
|
|
188
|
+
(s.api_name && s.api_name === p.name)
|
|
189
|
+
) {
|
|
190
|
+
foundId = parseInt(sId);
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Priority 2: Check Master Cache (Block 2) - For new ships via stored cache
|
|
196
|
+
if (foundId === -1 && masterCache && masterCache.ships) {
|
|
197
|
+
Object.values(masterCache.ships).forEach(ms => {
|
|
198
|
+
if (ms.api_name === p.name) {
|
|
199
|
+
foundId = ms.api_id
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Priority 3: Check Runtime Master Data ($ships) - For immediate validation if passed
|
|
205
|
+
if (foundId === -1 && $ships) {
|
|
206
|
+
Object.values($ships).forEach(ms => {
|
|
207
|
+
if (ms.api_name === p.name) {
|
|
208
|
+
foundId = ms.api_id
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (foundId > 0) {
|
|
214
|
+
// Ensure foundId is treated as a valid ship even if not in wctf.ships
|
|
215
|
+
// Effectively "mounting" the ID.
|
|
216
|
+
const rootId = findRoot(foundId) || foundId // Fallback to self if no root found
|
|
217
|
+
|
|
218
|
+
if (!map[rootId]) {
|
|
219
|
+
map[rootId] = {
|
|
220
|
+
baseId: rootId,
|
|
221
|
+
provides: []
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check for duplicates
|
|
226
|
+
const exists = map[rootId].provides.some(existing =>
|
|
227
|
+
existing.equipId === eqId &&
|
|
228
|
+
existing.providerId === foundId &&
|
|
229
|
+
existing.level === p.level
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
if (!exists) {
|
|
233
|
+
map[rootId].provides.push({
|
|
234
|
+
equipId: eqId,
|
|
235
|
+
providerId: foundId,
|
|
236
|
+
level: p.level,
|
|
237
|
+
isInitial: true
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
} catch (e) {
|
|
245
|
+
console.error("Failed to load initial_equip_ships.json", e)
|
|
246
|
+
}
|
|
247
|
+
|
|
113
248
|
cachedFarmingMap = map
|
|
114
249
|
cachedWctfShips = wctf.ships
|
|
250
|
+
cachedMasterShips = $ships
|
|
115
251
|
return map
|
|
116
252
|
}
|
|
117
253
|
|
|
@@ -132,26 +268,19 @@ export function checkQuota(targetCount, equipId, userEquips, userShips, farmingM
|
|
|
132
268
|
Object.values(userShips).forEach(ship => {
|
|
133
269
|
const masterId = ship.api_ship_id
|
|
134
270
|
|
|
135
|
-
//
|
|
136
|
-
// If
|
|
137
|
-
|
|
138
|
-
// Our farmingMap keys are ROOTS.
|
|
139
|
-
// We need to know if `masterId` belongs to the tree of a Root that provides `equipId`.
|
|
140
|
-
|
|
141
|
-
// Simplification for v4:
|
|
142
|
-
// We check if `masterId` matches the 'baseId' of a group.
|
|
143
|
-
// (This assumes we keep Base copies. If we have intermediate, this check might fail.)
|
|
144
|
-
// Ideally we check: findRoot(masterId) -> match map Key.
|
|
145
|
-
// But `wctf` is needed for findRoot. We closed over it? No.
|
|
271
|
+
// Use findRoot to handle merged ships (e.g. Eidsvold Kai -> Eidsvold)
|
|
272
|
+
// If we don't do this, we won't find the entry in farmingMap which is keyed by Root ID.
|
|
273
|
+
const rootId = findRoot(masterId)
|
|
146
274
|
|
|
147
|
-
|
|
148
|
-
// Or, we can do a quick lookup if we exported the parentMap or findRoot logic.
|
|
149
|
-
// For now, strict Base ID match is "Safe" (undercounts rather than overcounts).
|
|
275
|
+
const info = farmingMap[rootId] || farmingMap[masterId] // Try Root first, then direct (fallback)
|
|
150
276
|
|
|
151
|
-
const info = farmingMap[masterId]
|
|
152
277
|
if (info && info.provides) {
|
|
278
|
+
// Check if this ship (or its family) provides the target equip
|
|
279
|
+
// And specifically, if the form I have is NOT the one that provides it (or I have multiple forms)
|
|
280
|
+
// Simplified Logic: If I have a ship in this family, count it as potential.
|
|
281
|
+
// Refined: If I have usage for this ship family to get the equip.
|
|
282
|
+
|
|
153
283
|
const useful = info.provides.some(p => p.equipId === equipId && p.providerId !== masterId)
|
|
154
|
-
// If I have the Base, and Base != Provider, it's potential.
|
|
155
284
|
if (useful) {
|
|
156
285
|
potential++
|
|
157
286
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const fs = require('fs-extra')
|
|
2
|
+
const path = require('path-extra')
|
|
3
|
+
|
|
4
|
+
const CACHE_DIR = path.join(window.APPDATA_PATH, 'poi-plugin-equips-farm')
|
|
5
|
+
const MASTER_CACHE_FILE = path.join(CACHE_DIR, 'master_cache.json')
|
|
6
|
+
|
|
7
|
+
// Ensure directory exists
|
|
8
|
+
fs.ensureDirSync(CACHE_DIR)
|
|
9
|
+
|
|
10
|
+
export function saveMasterCache(body) {
|
|
11
|
+
try {
|
|
12
|
+
if (!body || !body.api_mst_ship || !body.api_mst_shipgraph) return
|
|
13
|
+
|
|
14
|
+
const cacheData = {
|
|
15
|
+
ships: {},
|
|
16
|
+
shipgraph: {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Extract essential ship data
|
|
20
|
+
body.api_mst_ship.forEach(s => {
|
|
21
|
+
cacheData.ships[s.api_id] = {
|
|
22
|
+
api_id: s.api_id,
|
|
23
|
+
api_name: s.api_name,
|
|
24
|
+
api_yomi: s.api_yomi,
|
|
25
|
+
api_sortno: s.api_sortno,
|
|
26
|
+
api_aftershipid: s.api_aftershipid,
|
|
27
|
+
api_stype: s.api_stype
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
// Extract essential graph data (filename)
|
|
32
|
+
body.api_mst_shipgraph.forEach(g => {
|
|
33
|
+
cacheData.shipgraph[g.api_id] = {
|
|
34
|
+
api_id: g.api_id,
|
|
35
|
+
api_filename: g.api_filename,
|
|
36
|
+
api_version: g.api_version
|
|
37
|
+
}
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
fs.writeJsonSync(MASTER_CACHE_FILE, cacheData)
|
|
41
|
+
console.log('[Farming Plugin] Master data cached successfully.')
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.error('[Farming Plugin] Failed to save master cache:', e)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function loadMasterCache() {
|
|
48
|
+
try {
|
|
49
|
+
if (fs.existsSync(MASTER_CACHE_FILE)) {
|
|
50
|
+
return fs.readJsonSync(MASTER_CACHE_FILE)
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
console.error('[Farming Plugin] Failed to load master cache:', e)
|
|
54
|
+
}
|
|
55
|
+
return null
|
|
56
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "poi-plugin-equips-farm",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "A POI plugin to assist players in farming ships for specific equipment with enhanced multi-language search support",
|
|
5
5
|
"main": "index.es",
|
|
6
6
|
"poiPlugin": {
|
|
@@ -9,12 +9,23 @@
|
|
|
9
9
|
"icon": "fa/wrench",
|
|
10
10
|
"priority": 100
|
|
11
11
|
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.es",
|
|
14
|
+
"lib",
|
|
15
|
+
"views",
|
|
16
|
+
"redux",
|
|
17
|
+
"assets",
|
|
18
|
+
"initial_equip_ships.json"
|
|
19
|
+
],
|
|
12
20
|
"scripts": {
|
|
13
21
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
22
|
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"registry": "https://registry.npmjs.org/"
|
|
25
|
+
},
|
|
15
26
|
"repository": {
|
|
16
27
|
"type": "git",
|
|
17
|
-
"url": "https://github.com/Lanyangzhi/poi-plugin-equips-farm.git"
|
|
28
|
+
"url": "git+https://github.com/Lanyangzhi/poi-plugin-equips-farm.git"
|
|
18
29
|
},
|
|
19
30
|
"keywords": [
|
|
20
31
|
"poi",
|
|
@@ -41,9 +41,12 @@ export default class ShipList extends Component {
|
|
|
41
41
|
const chineseName = wctfShip.name && (wctfShip.name.zh_cn || wctfShip.name.chs || wctfShip.name.chinese)
|
|
42
42
|
const yomiName = wctfShip.name && wctfShip.name.yomi
|
|
43
43
|
|
|
44
|
+
// Fallback for names if WCTF is missing (e.g. for Initial only ships if they somehow aren't in WCTF index but exist in $ships)
|
|
45
|
+
const displayName = s.shipName || masterShip.api_name || chineseName
|
|
46
|
+
|
|
44
47
|
shipMap[s.shipId] = {
|
|
45
48
|
baseId: s.shipId,
|
|
46
|
-
name:
|
|
49
|
+
name: displayName,
|
|
47
50
|
// Add fields for enhanced search
|
|
48
51
|
api_name: masterShip.api_name,
|
|
49
52
|
chinese_name: chineseName || masterShip.chinese_name,
|
|
@@ -51,6 +54,10 @@ export default class ShipList extends Component {
|
|
|
51
54
|
api_yomi: masterShip.api_yomi,
|
|
52
55
|
filename: wctfShip.filename || masterShip.filename,
|
|
53
56
|
wiki_id: wctfShip.wiki_id || masterShip.wiki_id,
|
|
57
|
+
|
|
58
|
+
// IMPORTANT: ensure we have something to search against if WCTF is missing
|
|
59
|
+
// This handles cases where we matched by ID but WCTF data might be sparse
|
|
60
|
+
_rawName: s.shipName,
|
|
54
61
|
items: [],
|
|
55
62
|
hasActiveTarget: false
|
|
56
63
|
}
|
|
@@ -62,7 +69,8 @@ export default class ShipList extends Component {
|
|
|
62
69
|
level: s.level,
|
|
63
70
|
isTarget: isTarget,
|
|
64
71
|
providerName: s.providerName,
|
|
65
|
-
providerId: s.providerId
|
|
72
|
+
providerId: s.providerId,
|
|
73
|
+
isInitial: s.isInitial
|
|
66
74
|
})
|
|
67
75
|
|
|
68
76
|
if (isTarget) shipMap[s.shipId].hasActiveTarget = true
|
|
@@ -127,9 +135,10 @@ export default class ShipList extends Component {
|
|
|
127
135
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
128
136
|
<span style={{ fontWeight: '600' }}>{item.equipName}</span>
|
|
129
137
|
<span className="bp3-text-muted" style={{ fontSize: '0.85em' }}>
|
|
130
|
-
via
|
|
138
|
+
{item.isInitial ? 'Initial' : 'via ' + item.providerName}
|
|
131
139
|
</span>
|
|
132
140
|
</div>
|
|
141
|
+
{item.isInitial && <Tag minimal={true} intent="success" style={{marginRight: 5}}>Init</Tag>}
|
|
133
142
|
<Tag minimal={true} className={item.isTarget ? "bp3-intent-primary" : ""}>Lv.{item.level}</Tag>
|
|
134
143
|
</div>
|
|
135
144
|
))}
|