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.
@@ -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
+ }