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 +206 -0
- package/assets/ships.json +180 -0
- package/index.es +107 -0
- package/lib/data-processor.es +168 -0
- package/lib/search-utils.es +141 -0
- package/lib/utils.es +106 -0
- package/package.json +42 -0
- package/redux/actions.es +25 -0
- package/redux/index.es +3 -0
- package/redux/reducer.es +45 -0
- package/redux/selectors.es +61 -0
- package/views/components/EquipmentList.es +273 -0
- package/views/components/ShipList.es +145 -0
- package/views/index.es +179 -0
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
|
+
}
|