poi-plugin-equips-farm 1.0.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.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # 装备养殖助手 / Equipment Farming Assistant
2
+
3
+ 由Antigravity强力驱动
4
+ Coding by Antigravity
5
+
6
+ 一个帮助舰队Collection玩家追踪和养殖特定装备的 POI 插件。
7
+
8
+ A POI plugin to assist Kantai Collection players in tracking and farming specific equipment from ship remodels.
9
+
10
+ [English](#english) | [中文](#中文)
11
+
12
+ ---
13
+
14
+ ## 中文
15
+
16
+ ### 功能特性
17
+
18
+ - 📋 **装备追踪**: 设置需要养殖的装备目标数量
19
+ - 🚢 **舰娘信息**: 查看哪些舰娘可以提供目标装备及其改造等级
20
+ - 📊 **进度显示**: 实时显示当前拥有量与目标数量的对比
21
+ - 🔔 **掉落提醒**: 当获得有用的舰娘时自动弹出提示(格式:🔒{舰娘}可获得{装备}⚙️)
22
+ - 🔍 **多语言搜索**: 支持中文、日文、平假名、罗马音搜索
23
+ - 中文:五十铃、长门
24
+ - 日文:五十鈴、長門
25
+ - 平假名:いすず、ながと
26
+ - 罗马音:isuzu、nagato
27
+ - 🎯 **智能过滤**: 按装备类型、名称、标记状态筛选
28
+ - 💾 **自动保存**: 目标设置自动保存,重启后保留
29
+
30
+ ### 安装方法
31
+
32
+ #### 方法一:通过 NPM(推荐)
33
+ ```bash
34
+ npm install poi-plugin-equips-farm
35
+ ```
36
+
37
+ #### 方法二:通过 POI 插件商店
38
+ 1. 打开 POI
39
+ 2. 进入 设置 → 插件
40
+ 3. 搜索 "poi-plugin-equips-farm"
41
+ 4. 点击安装
42
+
43
+ #### 方法三:手动安装
44
+ 1. 下载最新版本的插件
45
+ 2. 解压到 POI 插件目录:
46
+ - Windows: `%APPDATA%\poi\plugins\node_modules\`
47
+ - macOS/Linux: `~/.config/poi/plugins/node_modules/`
48
+ 3. 重启 POI
49
+
50
+ ### 使用说明
51
+
52
+ #### 1. 设置目标装备
53
+ - 在"Equipments"标签页中找到需要的装备
54
+ - 使用 `+` `-` 按钮或直接输入数字设置目标数量
55
+ - 点击装备可展开查看提供该装备的舰娘列表
56
+
57
+ #### 2. 查看舰娘
58
+ - 切换到"Ships"标签页
59
+ - 查看每艘舰娘可以提供哪些装备
60
+ - 按改造等级排序显示
61
+
62
+ #### 3. 搜索功能
63
+ - **装备搜索**:支持中文、日文、罗马音
64
+ - 示例:搜索 "12cm单装炮" 或 "12cm単装砲" 或 "tansouhou"
65
+ - **舰娘搜索**:支持中文、日文、平假名、罗马音
66
+ - 示例:搜索 "五十铃" 或 "五十鈴" 或 "いすず" 或 "isuzu"
67
+
68
+ #### 4. 过滤功能
69
+ - 使用搜索框按名称筛选
70
+ - 点击装备类型图标筛选特定类型
71
+ - 使用 All/Marked/Unmarked 切换显示状态
72
+
73
+ ### 数据来源
74
+
75
+ 本插件使用 [WhoCallsTheFleet (WCTF)](https://github.com/TeamFleet/WhoCallsTheFleet) 数据库提供装备获取信息和多语言名称支持。
76
+
77
+ ### 开发
78
+
79
+ ```bash
80
+ # 克隆仓库
81
+ git clone https://github.com/Lanyangzhi/poi-plugin-equips-farm.git
82
+ cd poi-plugin-equips-farm
83
+
84
+ # 安装依赖
85
+ npm install
86
+
87
+ # 链接到 POI 插件目录(Windows)
88
+ cd %APPDATA%\poi\plugins\node_modules
89
+ mklink /J poi-plugin-equips-farm "D:\path\to\poi-plugin-equips-farm"
90
+
91
+ # 链接到 POI 插件目录(macOS/Linux)
92
+ cd ~/.config/poi/plugins/node_modules
93
+ ln -s /path/to/poi-plugin-equips-farm poi-plugin-equips-farm
94
+
95
+ # 重启 POI 进行测试
96
+ ```
97
+
98
+ ### 更新日志
99
+
100
+ #### v4.2.1
101
+ - ✨ 新增多语言搜索支持(中文、日文、平假名、罗马音)
102
+ - 🔧 优化通知格式,显示舰娘和装备名称
103
+ - 🐛 修复 WCTF 数据结构访问问题
104
+ - 🧹 清理未使用的 i18n 依赖
105
+
106
+ ### 许可证
107
+
108
+ MIT License
109
+
110
+ ---
111
+
112
+ ## English
113
+
114
+ ### Features
115
+
116
+ - 📋 **Equipment Tracking**: Set target quantities for equipment you want to farm
117
+ - 🚢 **Ship Information**: View which ships provide target equipment and their remodel levels
118
+ - 📊 **Progress Display**: Real-time comparison of current inventory vs target quantity
119
+ - 🔔 **Drop Notifications**: Automatic alerts when you acquire useful ships (Format: 🔒{ship}可获得{equipment}⚙️)
120
+ - 🔍 **Multi-language Search**: Search in Chinese, Japanese, Hiragana, and Romaji
121
+ - Chinese: 五十铃, 长门
122
+ - Japanese: 五十鈴, 長門
123
+ - Hiragana: いすず, ながと
124
+ - Romaji: isuzu, nagato
125
+ - 🎯 **Smart Filtering**: Filter by equipment type, name, or marked status
126
+ - 💾 **Auto-save**: Target settings are automatically saved and persist across restarts
127
+
128
+ ### Installation
129
+
130
+ #### Method 1: Via NPM (Recommended)
131
+ ```bash
132
+ npm install poi-plugin-equips-farm
133
+ ```
134
+
135
+ #### Method 2: Via POI Plugin Store
136
+ 1. Open POI
137
+ 2. Go to Settings → Plugins
138
+ 3. Search for "poi-plugin-equips-farm"
139
+ 4. Click Install
140
+
141
+ #### Method 3: Manual Installation
142
+ 1. Download the latest release
143
+ 2. Extract to POI plugins directory:
144
+ - Windows: `%APPDATA%\poi\plugins\node_modules\`
145
+ - macOS/Linux: `~/.config/poi/plugins/node_modules/`
146
+ 3. Restart POI
147
+
148
+ ### Usage
149
+
150
+ #### 1. Set Target Equipment
151
+ - Find the equipment you need in the "Equipments" tab
152
+ - Use `+` `-` buttons or type directly to set target quantity
153
+ - Click on equipment to expand and view ships that provide it
154
+
155
+ #### 2. View Ships
156
+ - Switch to "Ships" tab
157
+ - See what equipment each ship can provide
158
+ - Sorted by remodel level
159
+
160
+ #### 3. Search Functionality
161
+ - **Equipment Search**: Supports Chinese, Japanese, and Romaji
162
+ - Example: Search "12cm单装炮" or "12cm単装砲" or "tansouhou"
163
+ - **Ship Search**: Supports Chinese, Japanese, Hiragana, and Romaji
164
+ - Example: Search "五十铃" or "五十鈴" or "いすず" or "isuzu"
165
+
166
+ #### 4. Filtering
167
+ - Use search box to filter by name
168
+ - Click equipment type icons to filter by type
169
+ - Use All/Marked/Unmarked to toggle display status
170
+
171
+ ### Data Source
172
+
173
+ This plugin uses the [WhoCallsTheFleet (WCTF)](https://github.com/TeamFleet/WhoCallsTheFleet) database for equipment acquisition information and multi-language name support.
174
+
175
+ ### Development
176
+
177
+ ```bash
178
+ # Clone repository
179
+ git clone https://github.com/Lanyangzhi/poi-plugin-equips-farm.git
180
+ cd poi-plugin-equips-farm
181
+
182
+ # Install dependencies
183
+ npm install
184
+
185
+ # Link to POI plugins directory (Windows)
186
+ cd %APPDATA%\poi\plugins\node_modules
187
+ mklink /J poi-plugin-equips-farm "D:\path\to\poi-plugin-equips-farm"
188
+
189
+ # Link to POI plugins directory (macOS/Linux)
190
+ cd ~/.config/poi/plugins/node_modules
191
+ ln -s /path/to/poi-plugin-equips-farm poi-plugin-equips-farm
192
+
193
+ # Restart POI for testing
194
+ ```
195
+
196
+ ### Changelog
197
+
198
+ #### v4.2.1
199
+ - ✨ Added multi-language search support (Chinese, Japanese, Hiragana, Romaji)
200
+ - 🔧 Improved notification format to display ship and equipment names
201
+ - 🐛 Fixed WCTF data structure access issues
202
+ - 🧹 Cleaned up unused i18n dependencies
203
+
204
+ ### License
205
+
206
+ MIT License
@@ -0,0 +1,180 @@
1
+ [
2
+ {
3
+ "shipId": 110,
4
+ "farming": [
5
+ {
6
+ "equipId": 13,
7
+ "level": 12,
8
+ "remodel": true
9
+ }
10
+ ]
11
+ },
12
+ {
13
+ "shipId": 120,
14
+ "name": "Mikuma",
15
+ "farming": [
16
+ {
17
+ "equipId": 50,
18
+ "level": 30,
19
+ "remodel": true
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ "shipId": 123,
25
+ "name": "Kinugasa",
26
+ "farming": [
27
+ {
28
+ "equipId": 50,
29
+ "level": 55,
30
+ "remodel": true
31
+ }
32
+ ]
33
+ },
34
+ {
35
+ "shipId": 460,
36
+ "name": "Hayashimo",
37
+ "farming": [
38
+ {
39
+ "equipId": 101,
40
+ "level": 30,
41
+ "remodel": true
42
+ },
43
+ {
44
+ "equipId": 88,
45
+ "level": 30,
46
+ "remodel": true
47
+ }
48
+ ]
49
+ },
50
+ {
51
+ "shipId": 414,
52
+ "name": "Kiyoshimo",
53
+ "farming": [
54
+ {
55
+ "equipId": 88,
56
+ "level": 30,
57
+ "remodel": true
58
+ },
59
+ {
60
+ "equipId": 106,
61
+ "level": 30,
62
+ "remodel": true
63
+ }
64
+ ]
65
+ },
66
+ {
67
+ "shipId": 453,
68
+ "name": "Kazagumo",
69
+ "farming": [
70
+ {
71
+ "equipId": 129,
72
+ "level": 30,
73
+ "remodel": true
74
+ }
75
+ ]
76
+ },
77
+ {
78
+ "shipId": 424,
79
+ "name": "Takanami",
80
+ "farming": [
81
+ {
82
+ "equipId": 129,
83
+ "level": 30,
84
+ "remodel": true
85
+ }
86
+ ]
87
+ },
88
+ {
89
+ "shipId": 183,
90
+ "name": "Ooyodo",
91
+ "farming": [
92
+ {
93
+ "equipId": 101,
94
+ "level": 35,
95
+ "remodel": true
96
+ },
97
+ {
98
+ "equipId": 106,
99
+ "level": 35,
100
+ "remodel": true
101
+ }
102
+ ]
103
+ },
104
+ {
105
+ "shipId": 90,
106
+ "name": "Souryuu",
107
+ "farming": [
108
+ {
109
+ "equipId": 100,
110
+ "level": 78,
111
+ "remodel": true
112
+ }
113
+ ]
114
+ },
115
+ {
116
+ "shipId": 91,
117
+ "name": "Hiryuu",
118
+ "farming": [
119
+ {
120
+ "equipId": 94,
121
+ "level": 77,
122
+ "remodel": true
123
+ }
124
+ ]
125
+ },
126
+ {
127
+ "shipId": 24,
128
+ "name": "Ooi",
129
+ "farming": [
130
+ {
131
+ "equipId": 58,
132
+ "level": 10,
133
+ "remodel": true
134
+ },
135
+ {
136
+ "equipId": 15,
137
+ "level": 50,
138
+ "remodel": true
139
+ }
140
+ ]
141
+ },
142
+ {
143
+ "shipId": 25,
144
+ "name": "Kitakami",
145
+ "farming": [
146
+ {
147
+ "equipId": 58,
148
+ "level": 10,
149
+ "remodel": true
150
+ },
151
+ {
152
+ "equipId": 15,
153
+ "level": 50,
154
+ "remodel": true
155
+ }
156
+ ]
157
+ },
158
+ {
159
+ "shipId": 19,
160
+ "name": "Kiso",
161
+ "farming": [
162
+ {
163
+ "equipId": 15,
164
+ "level": 65,
165
+ "remodel": true
166
+ }
167
+ ]
168
+ },
169
+ {
170
+ "shipId": 409,
171
+ "name": "Maruyu",
172
+ "farming": [
173
+ {
174
+ "equipId": 32,
175
+ "level": 20,
176
+ "remodel": true
177
+ }
178
+ ]
179
+ }
180
+ ]
package/index.es ADDED
@@ -0,0 +1,107 @@
1
+ import { syncConfig } from './redux/actions'
2
+ import { targetsSelector, wctfDataSelector, userEquipsSelector, userShipsSelector, masterShipsSelector, masterEquipmentsSelector } from './redux/selectors'
3
+ import { getFarmingMap, checkQuota } from './lib/data-processor'
4
+
5
+ // Export Redux Reducer
6
+ export { reducer } from './redux'
7
+
8
+ // Export UI
9
+ export { reactClass } from './views'
10
+
11
+ const EXTENSION_KEY = 'poi-plugin-equips-farm'
12
+
13
+ // Config Observer
14
+ let unsubscribeObserver = null
15
+ const configPath = `${EXTENSION_KEY}.targets`
16
+
17
+ // Event Handler for Game Response
18
+ const handleGameResponse = (e) => {
19
+ const { path, body } = e.detail
20
+
21
+ let gotShipId = -1
22
+
23
+ if (path === '/kcsapi/api_req_sortie/battleresult') {
24
+ if (body.api_get_ship) {
25
+ gotShipId = body.api_get_ship.api_ship_id
26
+ }
27
+ } else if (path === '/kcsapi/api_req_kousyou/getship') {
28
+ gotShipId = body.api_ship_id
29
+ }
30
+
31
+ if (gotShipId > 0 && window.store) {
32
+ const state = window.store.getState()
33
+ const targets = targetsSelector(state)
34
+ const wctf = wctfDataSelector(state)
35
+ const userEquips = userEquipsSelector(state)
36
+ const userShips = userShipsSelector(state)
37
+ const $ships = masterShipsSelector(state)
38
+ const $equipments = masterEquipmentsSelector(state)
39
+
40
+ const farmingMap = getFarmingMap(wctf)
41
+ const shipInfo = farmingMap[gotShipId]
42
+
43
+ if (shipInfo && shipInfo.provides) {
44
+ const hits = []
45
+
46
+ shipInfo.provides.forEach(p => {
47
+ const targetCount = targets[p.equipId] || 0
48
+ if (targetCount > 0) {
49
+ const quota = checkQuota(targetCount, p.equipId, userEquips, userShips, farmingMap)
50
+
51
+ if (!quota.isSatisfied) {
52
+ const equipMaster = $equipments[p.equipId]
53
+ const eqName = equipMaster ? equipMaster.api_name : `#${p.equipId}`
54
+ hits.push(`${eqName} (Lv.${p.level})`)
55
+ }
56
+ }
57
+ })
58
+
59
+ if (hits.length > 0) {
60
+ // Get ship name
61
+ const shipMaster = $ships[gotShipId]
62
+ const shipName = shipMaster ? shipMaster.api_name : `Ship#${gotShipId}`
63
+
64
+ // Format: 🔒{ship}可获得{equip}⚙️
65
+ const equipList = hits.join('、')
66
+ window.toast(`🔒${shipName}可获得${equipList}⚙️`, { type: 'success' })
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ export function pluginDidLoad() {
73
+ const savedTargets = window.config.get(configPath, {})
74
+
75
+ if (window.store) {
76
+ // Initialize state with saved config
77
+ window.store.dispatch(syncConfig(savedTargets))
78
+
79
+ // Use store.subscribe for persistence with proper comparison
80
+ let currentTargetsJson = JSON.stringify(savedTargets)
81
+
82
+ unsubscribeObserver = window.store.subscribe(() => {
83
+ try {
84
+ const state = window.store.getState()
85
+ const newTargets = targetsSelector(state)
86
+ const newTargetsJson = JSON.stringify(newTargets)
87
+
88
+ // Only save if targets actually changed (deep comparison via JSON)
89
+ if (newTargetsJson !== currentTargetsJson) {
90
+ window.config.set(configPath, newTargets)
91
+ currentTargetsJson = newTargetsJson
92
+ }
93
+ } catch (error) {
94
+ console.error('[Farming Assistant] Error in store subscription:', error)
95
+ }
96
+ })
97
+ }
98
+
99
+ window.addEventListener('game.response', handleGameResponse)
100
+ }
101
+
102
+ export function pluginWillUnload() {
103
+ if (unsubscribeObserver) {
104
+ unsubscribeObserver()
105
+ }
106
+ window.removeEventListener('game.response', handleGameResponse)
107
+ }
@@ -0,0 +1,168 @@
1
+ // lib/data-processor.es
2
+ // Handles WCTF Data Processing and Inventory Quota Logic
3
+
4
+ let cachedFarmingMap = null
5
+ let cachedWctfShips = null
6
+
7
+ // --- 1. WCTF Processing ---
8
+
9
+ export function getFarmingMap(wctf) {
10
+ if (!wctf || !wctf.ships) return {}
11
+ if (cachedFarmingMap && cachedWctfShips === wctf.ships) return cachedFarmingMap
12
+
13
+ const map = {}
14
+ const ships = wctf.ships
15
+
16
+ // 1st Pass: Identify direct providers (Who comes with what stock eq)
17
+ // Map<ShipId, { level, stock: [] }>
18
+
19
+ // Pre-Pass: Build Level Requirements Map (Child -> Level to reach from Parent)
20
+ const levelMap = {}
21
+ Object.keys(ships).forEach(sId => {
22
+ const ship = ships[sId]
23
+ if (ship.remodel && ship.remodel.next && ship.remodel.next_lvl) {
24
+ const nextId = parseInt(ship.remodel.next) // Target ID
25
+ const nextLvl = parseInt(ship.remodel.next_lvl)
26
+ levelMap[nextId] = nextLvl
27
+ }
28
+ })
29
+
30
+ const directProvision = {}
31
+
32
+ Object.keys(ships).forEach(sId => {
33
+ const ship = ships[sId]
34
+ // Try multiple known keys for stock equipment
35
+ const rawEquip = ship.equip || ship.slots || ship.init_equip
36
+
37
+ if (!rawEquip || !Array.isArray(rawEquip)) return
38
+
39
+ const myStock = []
40
+ rawEquip.forEach(slot => {
41
+ let eId = -1
42
+ if (typeof slot === 'number') eId = slot
43
+ else if (slot && slot.id) eId = slot.id
44
+
45
+ if (eId > 0) {
46
+ myStock.push(eId)
47
+ }
48
+ })
49
+
50
+ if (myStock.length > 0) {
51
+ // Get the level required to BECOME this ship
52
+ const pId = parseInt(sId)
53
+ const requiredLevel = levelMap[pId] || 1 // Default to 1 if no parent req (Base)
54
+
55
+ directProvision[pId] = {
56
+ shipId: pId,
57
+ stock: myStock,
58
+ level: requiredLevel
59
+ }
60
+ }
61
+ })
62
+
63
+ // 2nd Pass: Build Parent Map (Child -> Parent) for fast lookups
64
+ const parentMap = {}
65
+ Object.keys(ships).forEach(sId => {
66
+ const ship = ships[sId]
67
+ if (ship.remodel && ship.remodel.prev) {
68
+ parentMap[sId] = parseInt(ship.remodel.prev)
69
+ }
70
+ })
71
+
72
+ // Helper: Find Base Ancestor
73
+ const findRoot = (id) => {
74
+ let curr = id
75
+ let steps = 0
76
+ // Walk up until no parent found
77
+ while(parentMap[curr] && steps < 20) {
78
+ curr = parentMap[curr]
79
+ steps++
80
+ }
81
+ return curr
82
+ }
83
+
84
+ // 3rd Pass: Group by Base Ancestor
85
+ Object.keys(directProvision).forEach(providerIdStr => {
86
+ const providerId = parseInt(providerIdStr)
87
+ const providerData = directProvision[providerId]
88
+
89
+ const rootId = findRoot(providerId)
90
+
91
+ // Initialize Root Entry
92
+ if (!map[rootId]) {
93
+ map[rootId] = {
94
+ baseId: rootId,
95
+ provides: []
96
+ }
97
+ }
98
+
99
+ // Add items to Root
100
+ providerData.stock.forEach(eqId => {
101
+ // No Deduplication: Show ALL forms that provide the item.
102
+ // This ensures intermediate forms (like Tan Yang) appear in the Ship List
103
+ // even if they provide the same equipment as an earlier form.
104
+
105
+ map[rootId].provides.push({
106
+ equipId: eqId,
107
+ providerId: providerId,
108
+ level: providerData.level,
109
+ })
110
+ })
111
+ })
112
+
113
+ cachedFarmingMap = map
114
+ cachedWctfShips = wctf.ships
115
+ return map
116
+ }
117
+
118
+ // --- 2. Inventory Check ---
119
+
120
+ export function checkQuota(targetCount, equipId, userEquips, userShips, farmingMap) {
121
+ if (targetCount <= 0) return { isSatisfied: true, current: 0 }
122
+
123
+ let holding = 0
124
+ Object.values(userEquips).forEach(item => {
125
+ if (item.api_slotitem_id === equipId) {
126
+ holding++
127
+ }
128
+ })
129
+
130
+ let potential = 0
131
+ // Check Potential: Do I have a ship that is an ancestor of a Provider?
132
+ Object.values(userShips).forEach(ship => {
133
+ const masterId = ship.api_ship_id
134
+
135
+ // farmingMap is Keyed by BASE ID.
136
+ // If masterId is in farmingMap, it is a Base ship match.
137
+ // What if masterId is an intermediate form (e.g. Kai), but not yet Provider (Kai Ni)?
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.
146
+
147
+ // Let's rely on the fact that most farming involves keeping Base ships.
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).
150
+
151
+ const info = farmingMap[masterId]
152
+ if (info && info.provides) {
153
+ 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
+ if (useful) {
156
+ potential++
157
+ }
158
+ }
159
+ })
160
+
161
+ const current = holding + potential
162
+ return {
163
+ current,
164
+ holding,
165
+ potential,
166
+ isSatisfied: current >= targetCount
167
+ }
168
+ }