poi-plugin-mcp 0.2.15 → 0.2.21
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 +290 -201
- package/index.js +3 -0
- package/lib/bridge-controller.js +75 -1
- package/lib/fleet-metrics.js +249 -0
- package/lib/poi-action-events.js +405 -291
- package/lib/poi-api-responses.js +165 -0
- package/lib/poi-auto-interaction-recorder.js +399 -0
- package/lib/poi-data-query.js +254 -0
- package/lib/poi-http-bridge.js +1673 -1320
- package/lib/poi-input.js +366 -302
- package/lib/poi-interaction-recorder.js +1693 -0
- package/lib/poi-telemetry.js +515 -439
- package/lib/poi-webview-runtime.js +1181 -0
- package/lib/settings-view.js +112 -0
- package/lib/settings.js +8 -0
- package/mcp-server.js +542 -456
- package/package.json +3 -3
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
const LOS33_FORMULA_ID = 'kcwiki-phase2-33-2026-08'
|
|
2
|
+
const FIGHTER_POWER_FORMULA_ID = 'poi-hensei-getTyku-fleet-2026-08'
|
|
3
|
+
|
|
4
|
+
const LOS_EQUIP_COEFF = Object.freeze({
|
|
5
|
+
9: 0.8,
|
|
6
|
+
10: 1.2,
|
|
7
|
+
11: 1.1,
|
|
8
|
+
12: 0.6,
|
|
9
|
+
13: 0.6,
|
|
10
|
+
94: 1.2,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const EXP_TABLE = Object.freeze([0, 10, 25, 40, 55, 70, 85, 100, 121])
|
|
14
|
+
const LEVEL_BONUS = Object.freeze({
|
|
15
|
+
6: [0, 0, 2, 5, 9, 14, 14, 22, 22],
|
|
16
|
+
7: [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
17
|
+
8: [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
18
|
+
11: [0, 1, 1, 1, 1, 3, 3, 6, 6],
|
|
19
|
+
45: [0, 0, 2, 5, 9, 14, 14, 22, 22],
|
|
20
|
+
47: [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
21
|
+
48: [0, 0, 2, 5, 9, 14, 14, 22, 22],
|
|
22
|
+
56: [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
23
|
+
57: [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
24
|
+
58: [0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
25
|
+
})
|
|
26
|
+
const CARRIER_TYPES = new Set([6, 7, 8, 45, 47, 56, 57, 58])
|
|
27
|
+
|
|
28
|
+
function moraleMeaning(cond) {
|
|
29
|
+
if (cond >= 50) return '闪耀'
|
|
30
|
+
if (cond >= 30) return '平常'
|
|
31
|
+
if (cond >= 20) return '疲劳'
|
|
32
|
+
return '过劳'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function speedFromRaw(raw) {
|
|
36
|
+
if (raw >= 20) return 'fastest'
|
|
37
|
+
if (raw >= 15) return 'fast_plus'
|
|
38
|
+
if (raw >= 10) return 'fast'
|
|
39
|
+
return 'slow'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function speedMeaning(kind) {
|
|
43
|
+
return ({ slow: '低速', fast: '高速', fast_plus: '高速+', fastest: '最速' })[kind]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function round2(value) {
|
|
47
|
+
return Math.round(value * 100) / 100
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function computeLos33(ships, hqLevel, coefficient) {
|
|
51
|
+
let shipSqrtLos = 0
|
|
52
|
+
let equipmentLos = 0
|
|
53
|
+
for (const ship of ships) {
|
|
54
|
+
let equippedLos = 0
|
|
55
|
+
for (const item of ship.slots) {
|
|
56
|
+
equippedLos += item.los
|
|
57
|
+
equipmentLos += item.los * (LOS_EQUIP_COEFF[item.category] ?? 0.6)
|
|
58
|
+
}
|
|
59
|
+
shipSqrtLos += Math.sqrt(Math.max(0, ship.currentLos - equippedLos))
|
|
60
|
+
}
|
|
61
|
+
const hqPenalty = Math.ceil(0.4 * hqLevel)
|
|
62
|
+
const raw = shipSqrtLos + equipmentLos - hqPenalty
|
|
63
|
+
return {
|
|
64
|
+
formulaId: LOS33_FORMULA_ID,
|
|
65
|
+
coefficient,
|
|
66
|
+
raw,
|
|
67
|
+
score: raw * coefficient,
|
|
68
|
+
hqPenalty,
|
|
69
|
+
shipSqrtLos,
|
|
70
|
+
equipmentLos,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function slotFighterPower(slot) {
|
|
75
|
+
const count = slot.count
|
|
76
|
+
const category = slot.category
|
|
77
|
+
const tyku = slot.tyku
|
|
78
|
+
const alv = Math.max(0, Math.min(7, Math.trunc(slot.proficiency || 0)))
|
|
79
|
+
if (!(count > 0) || tyku < 0) return null
|
|
80
|
+
const bonus = LEVEL_BONUS[category]
|
|
81
|
+
if (CARRIER_TYPES.has(category)) {
|
|
82
|
+
const levelFactor = slot.bombing > 0 ? 0.25 : 0.2
|
|
83
|
+
const temp = Math.sqrt(count) * (tyku + slot.improvement * levelFactor) + (bonus ? bonus[alv] : 0)
|
|
84
|
+
return {
|
|
85
|
+
basic: Math.floor(Math.sqrt(count) * tyku),
|
|
86
|
+
min: Math.floor(temp + Math.sqrt(EXP_TABLE[alv] / 10)),
|
|
87
|
+
max: Math.floor(temp + Math.sqrt((EXP_TABLE[alv + 1] - 1) / 10)),
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (category === 11) {
|
|
91
|
+
const temp = Math.sqrt(count) * tyku + (bonus ? bonus[alv] : 0)
|
|
92
|
+
return {
|
|
93
|
+
basic: Math.floor(Math.sqrt(count) * tyku),
|
|
94
|
+
min: Math.floor(temp + Math.sqrt(EXP_TABLE[alv] / 10)),
|
|
95
|
+
max: Math.floor(temp + Math.sqrt((EXP_TABLE[alv + 1] - 1) / 10)),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function computeFleetFighterPower(slots) {
|
|
102
|
+
let basic = 0
|
|
103
|
+
let min = 0
|
|
104
|
+
let max = 0
|
|
105
|
+
for (const slot of slots) {
|
|
106
|
+
const part = slotFighterPower(slot)
|
|
107
|
+
if (!part) continue
|
|
108
|
+
basic += part.basic
|
|
109
|
+
min += part.min
|
|
110
|
+
max += part.max
|
|
111
|
+
}
|
|
112
|
+
return { formulaId: FIGHTER_POWER_FORMULA_ID, basic, min, max }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function inspectFleetMetrics(ships, hqLevel) {
|
|
116
|
+
const speedRaws = ships.map((ship) => ship.speedRaw)
|
|
117
|
+
const minSpeedRaw = speedRaws.length === 0 ? 0 : Math.min(...speedRaws)
|
|
118
|
+
const kind = speedFromRaw(minSpeedRaw)
|
|
119
|
+
const morales = ships.map((ship) => ship.morale)
|
|
120
|
+
const minMorale = morales.length === 0 ? 0 : Math.min(...morales)
|
|
121
|
+
const maxMorale = morales.length === 0 ? 0 : Math.max(...morales)
|
|
122
|
+
const catalogComplete = ships.every((ship) =>
|
|
123
|
+
ship.slots.every((slot) => slot.missingCatalog !== true),
|
|
124
|
+
)
|
|
125
|
+
const losRaw = computeLos33(
|
|
126
|
+
ships.map((ship) => ({
|
|
127
|
+
currentLos: ship.currentLos,
|
|
128
|
+
slots: ship.slots.map((slot) => ({ los: slot.los, category: slot.category })),
|
|
129
|
+
})),
|
|
130
|
+
hqLevel,
|
|
131
|
+
1,
|
|
132
|
+
)
|
|
133
|
+
const fp = computeFleetFighterPower(ships.flatMap((ship) => ship.slots))
|
|
134
|
+
return {
|
|
135
|
+
shipCount: ships.length,
|
|
136
|
+
speed: {
|
|
137
|
+
raw: minSpeedRaw,
|
|
138
|
+
kind,
|
|
139
|
+
meaning: speedMeaning(kind),
|
|
140
|
+
allSame: speedRaws.every((raw) => raw === minSpeedRaw),
|
|
141
|
+
},
|
|
142
|
+
morale: {
|
|
143
|
+
min: minMorale,
|
|
144
|
+
max: maxMorale,
|
|
145
|
+
sparkle: morales.filter((value) => value >= 50).length,
|
|
146
|
+
tired: morales.filter((value) => value < 30).length,
|
|
147
|
+
meaning: moraleMeaning(minMorale),
|
|
148
|
+
},
|
|
149
|
+
los33: {
|
|
150
|
+
formulaId: LOS33_FORMULA_ID,
|
|
151
|
+
hqLevel,
|
|
152
|
+
available: catalogComplete,
|
|
153
|
+
hqPenalty: losRaw.hqPenalty,
|
|
154
|
+
raw: losRaw.raw,
|
|
155
|
+
scores: {
|
|
156
|
+
1: round2(losRaw.raw * 1),
|
|
157
|
+
2: round2(losRaw.raw * 2),
|
|
158
|
+
3: round2(losRaw.raw * 3),
|
|
159
|
+
4: round2(losRaw.raw * 4),
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
fighterPower: {
|
|
163
|
+
formulaId: FIGHTER_POWER_FORMULA_ID,
|
|
164
|
+
available: catalogComplete,
|
|
165
|
+
basic: fp.basic,
|
|
166
|
+
min: fp.min,
|
|
167
|
+
max: fp.max,
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function describeEquipFromStore(equipmentId, count, equips, master) {
|
|
173
|
+
if (!equipmentId || equipmentId <= 0) return null
|
|
174
|
+
const equip = equips[equipmentId]
|
|
175
|
+
if (!equip) {
|
|
176
|
+
return {
|
|
177
|
+
category: 0,
|
|
178
|
+
los: 0,
|
|
179
|
+
tyku: 0,
|
|
180
|
+
bombing: 0,
|
|
181
|
+
improvement: 0,
|
|
182
|
+
proficiency: 0,
|
|
183
|
+
count,
|
|
184
|
+
missingCatalog: true,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const masterId = equip.api_slotitem_id
|
|
188
|
+
const catalog = master.equipment && master.equipment[masterId]
|
|
189
|
+
const types = catalog && Array.isArray(catalog.api_type) ? catalog.api_type : []
|
|
190
|
+
return {
|
|
191
|
+
category: types[2] || 0,
|
|
192
|
+
los: (catalog && catalog.api_saku) || 0,
|
|
193
|
+
tyku: (catalog && catalog.api_tyku) || 0,
|
|
194
|
+
bombing: (catalog && catalog.api_baku) || 0,
|
|
195
|
+
improvement: equip.api_level || 0,
|
|
196
|
+
proficiency: equip.api_alv || 0,
|
|
197
|
+
count,
|
|
198
|
+
missingCatalog: !catalog,
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function collectFleetMetricShips(fleet, ships, equips, master) {
|
|
203
|
+
return (fleet.api_ship || []).filter((id) => id > 0).map((shipId) => {
|
|
204
|
+
const ship = ships[shipId]
|
|
205
|
+
if (!ship) {
|
|
206
|
+
return {
|
|
207
|
+
instanceId: shipId,
|
|
208
|
+
speedRaw: 0,
|
|
209
|
+
morale: 0,
|
|
210
|
+
currentLos: 0,
|
|
211
|
+
slots: [],
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const masterShip = master.ships && master.ships[ship.api_ship_id]
|
|
215
|
+
const onslot = Array.isArray(ship.api_onslot) ? ship.api_onslot : []
|
|
216
|
+
const slotIds = Array.isArray(ship.api_slot) ? ship.api_slot : []
|
|
217
|
+
const slots = []
|
|
218
|
+
for (let index = 0; index < slotIds.length; index += 1) {
|
|
219
|
+
const described = describeEquipFromStore(slotIds[index], onslot[index] || 0, equips, master)
|
|
220
|
+
if (described) slots.push(described)
|
|
221
|
+
}
|
|
222
|
+
const expansion = Number(ship.api_slot_ex || 0)
|
|
223
|
+
if (expansion > 0) {
|
|
224
|
+
const described = describeEquipFromStore(expansion, 0, equips, master)
|
|
225
|
+
if (described) slots.push(described)
|
|
226
|
+
}
|
|
227
|
+
const sakuteki = Array.isArray(ship.api_sakuteki) ? ship.api_sakuteki : [0]
|
|
228
|
+
return {
|
|
229
|
+
instanceId: ship.api_id,
|
|
230
|
+
speedRaw: Number(ship.api_soku ?? (masterShip && masterShip.api_soku) ?? 0),
|
|
231
|
+
morale: ship.api_cond || 0,
|
|
232
|
+
currentLos: sakuteki[0] || 0,
|
|
233
|
+
fuel: ship.api_fuel || 0,
|
|
234
|
+
ammo: ship.api_bull || 0,
|
|
235
|
+
slots,
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
module.exports = {
|
|
241
|
+
LOS33_FORMULA_ID,
|
|
242
|
+
FIGHTER_POWER_FORMULA_ID,
|
|
243
|
+
moraleMeaning,
|
|
244
|
+
speedFromRaw,
|
|
245
|
+
speedMeaning,
|
|
246
|
+
inspectFleetMetrics,
|
|
247
|
+
collectFleetMetricShips,
|
|
248
|
+
computeFleetFighterPower,
|
|
249
|
+
}
|