poi-plugin-compass 0.1.0
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 +79 -0
- package/air-status.js +28 -0
- package/assets/compass.css +647 -0
- package/assets/kcnav/icons/map_common/136.png +0 -0
- package/assets/kcnav/icons/map_common/137.png +0 -0
- package/assets/kcnav/icons/map_common/19.png +0 -0
- package/assets/kcnav/icons/map_main/17.png +0 -0
- package/assets/kcnav/icons/map_main/18.png +0 -0
- package/assets/kcnav/icons/map_main/40.png +0 -0
- package/assets/kcnav/icons/map_main/42.png +0 -0
- package/assets/kcnav/icons/map_main/46.png +0 -0
- package/assets/kcnav/icons/map_main/47.png +0 -0
- package/assets/kcnav/icons/map_main/48.png +0 -0
- package/assets/kcnav/icons/map_main/53.png +0 -0
- package/assets/kcnav/icons/map_main/54.png +0 -0
- package/assets/kcnav/icons/map_main/62.png +0 -0
- package/assets/kcnav/maps/1-1.webp +0 -0
- package/assets/kcnav/maps/1-2.webp +0 -0
- package/assets/kcnav/maps/1-3.webp +0 -0
- package/assets/kcnav/maps/1-4.webp +0 -0
- package/assets/kcnav/maps/1-5.webp +0 -0
- package/assets/kcnav/maps/1-6.webp +0 -0
- package/assets/kcnav/maps/2-1.webp +0 -0
- package/assets/kcnav/maps/2-2.webp +0 -0
- package/assets/kcnav/maps/2-3.webp +0 -0
- package/assets/kcnav/maps/2-4.webp +0 -0
- package/assets/kcnav/maps/2-5.webp +0 -0
- package/assets/kcnav/maps/3-1.webp +0 -0
- package/assets/kcnav/maps/3-2.webp +0 -0
- package/assets/kcnav/maps/3-3.webp +0 -0
- package/assets/kcnav/maps/3-4.webp +0 -0
- package/assets/kcnav/maps/3-5.webp +0 -0
- package/assets/kcnav/maps/4-1.webp +0 -0
- package/assets/kcnav/maps/4-2.webp +0 -0
- package/assets/kcnav/maps/4-3.webp +0 -0
- package/assets/kcnav/maps/4-4.webp +0 -0
- package/assets/kcnav/maps/4-5.webp +0 -0
- package/assets/kcnav/maps/5-1.webp +0 -0
- package/assets/kcnav/maps/5-2.webp +0 -0
- package/assets/kcnav/maps/5-3.webp +0 -0
- package/assets/kcnav/maps/5-4.webp +0 -0
- package/assets/kcnav/maps/5-5.webp +0 -0
- package/assets/kcnav/maps/5-6.webp +0 -0
- package/assets/kcnav/maps/6-1.webp +0 -0
- package/assets/kcnav/maps/6-2.webp +0 -0
- package/assets/kcnav/maps/6-3.webp +0 -0
- package/assets/kcnav/maps/6-4.webp +0 -0
- package/assets/kcnav/maps/6-5.webp +0 -0
- package/assets/kcnav/maps/7-1.webp +0 -0
- package/assets/kcnav/maps/7-2.webp +0 -0
- package/assets/kcnav/maps/7-3.webp +0 -0
- package/assets/kcnav/maps/7-4.webp +0 -0
- package/assets/kcnav/maps/7-5.webp +0 -0
- package/data/kc3kai-master-ids.json +1 -0
- package/data/kc3kai-ship-stats.json +21545 -0
- package/data/kcnav-node-types-by-map.json +606 -0
- package/data/kcnav-node-types.json +176 -0
- package/data/los-config.json +42 -0
- package/data/maps.json +590 -0
- package/data/normal-maps.json +1590 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-1/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-1/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-1/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-2/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-2/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-2/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-2/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-3/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-3/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-3/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-3/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-4/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-4/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-4/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-4/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-4/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-4/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-5/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-5/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-5/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-5/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-5/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-5/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-5/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-5/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-6/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-6/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-6/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-6/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-6/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-6/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-6/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-6/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/1-6/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-1/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-1/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-1/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-1/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-1/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-2/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-2/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-2/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-2/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-2/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-3/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-3/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-3/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-3/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-3/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-3/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-3/N.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-4/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-4/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-4/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-4/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-4/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-4/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-4/P.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-5/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-5/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-5/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-5/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-5/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-5/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/2-5/O.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-1/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-1/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-1/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-1/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-1/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-2/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-2/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-2/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-2/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-2/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-2/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-3/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-3/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-3/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-3/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-3/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-3/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-4/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-4/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-4/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-4/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-4/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-4/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-4/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-4/N.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-4/P.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-5/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-5/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-5/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-5/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-5/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-5/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/3-5/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-1/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-1/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-1/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-1/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-1/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-1/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-1/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-2/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-2/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-2/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-2/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-2/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-2/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-2/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-2/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-3/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-3/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-3/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-3/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-3/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-3/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-3/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-3/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-3/N.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-4/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-4/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-4/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-4/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-4/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-4/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-4/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-4/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/N.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/O.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/S.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/4-5/T.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-1/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-1/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-1/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-1/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-1/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-1/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-2/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-2/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-2/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-2/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-2/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-2/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-2/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-2/O.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-3/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-3/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-3/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-3/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-3/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-3/N.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-3/P.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-3/Q.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-4/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-4/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-4/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-4/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-4/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-4/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-4/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-4/P.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-5/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-5/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-5/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-5/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-5/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-5/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-5/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-5/N.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-5/P.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-5/S.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/A1.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/A2.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/C1.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/C2.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/K1.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/K2.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/N.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/P.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/Q.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/Q1.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/Q2.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/T.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/U.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/V.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/W.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/X.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/5-6/Z.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-1/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-1/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-1/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-1/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-1/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-1/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-1/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-2/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-2/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-2/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-2/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-2/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-2/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-2/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-3/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-3/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-3/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-3/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-3/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-3/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-4/N.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/6-5/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-1/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-1/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-1/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-1/F.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-1/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-1/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-1/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-2/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-2/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-2/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-2/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-2/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-2/I.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-2/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-2/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-3/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-3/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-3/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-3/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-3/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-3/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-3/N.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-3/P.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/H.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-4/P.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/A.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/B.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/C.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/D.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/E.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/G.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/J.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/K.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/L.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/M.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/N.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/Q.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/R.json +1 -0
- package/data/sources/kcnav/enemies/2026-08-11_2026-09-11/7-5/T.json +1 -0
- package/data/sources/kcnav/enemies/manifest.json +2120 -0
- package/data/sources/kcnav/maps/1-1.json +175 -0
- package/data/sources/kcnav/maps/1-2.json +277 -0
- package/data/sources/kcnav/maps/1-3.json +517 -0
- package/data/sources/kcnav/maps/1-4.json +652 -0
- package/data/sources/kcnav/maps/1-5.json +553 -0
- package/data/sources/kcnav/maps/1-6.json +668 -0
- package/data/sources/kcnav/maps/2-1.json +451 -0
- package/data/sources/kcnav/maps/2-2.json +557 -0
- package/data/sources/kcnav/maps/2-3.json +757 -0
- package/data/sources/kcnav/maps/2-4.json +817 -0
- package/data/sources/kcnav/maps/2-5.json +779 -0
- package/data/sources/kcnav/maps/3-1.json +379 -0
- package/data/sources/kcnav/maps/3-2.json +588 -0
- package/data/sources/kcnav/maps/3-3.json +654 -0
- package/data/sources/kcnav/maps/3-4.json +910 -0
- package/data/sources/kcnav/maps/3-5.json +643 -0
- package/data/sources/kcnav/maps/4-1.json +561 -0
- package/data/sources/kcnav/maps/4-2.json +664 -0
- package/data/sources/kcnav/maps/4-3.json +867 -0
- package/data/sources/kcnav/maps/4-4.json +690 -0
- package/data/sources/kcnav/maps/4-5.json +1232 -0
- package/data/sources/kcnav/maps/5-1.json +517 -0
- package/data/sources/kcnav/maps/5-2.json +791 -0
- package/data/sources/kcnav/maps/5-3.json +889 -0
- package/data/sources/kcnav/maps/5-4.json +851 -0
- package/data/sources/kcnav/maps/5-5.json +1070 -0
- package/data/sources/kcnav/maps/5-6.json +3200 -0
- package/data/sources/kcnav/maps/6-1.json +527 -0
- package/data/sources/kcnav/maps/6-2.json +717 -0
- package/data/sources/kcnav/maps/6-3.json +501 -0
- package/data/sources/kcnav/maps/6-4.json +870 -0
- package/data/sources/kcnav/maps/6-5.json +814 -0
- package/data/sources/kcnav/maps/7-1.json +640 -0
- package/data/sources/kcnav/maps/7-2.json +976 -0
- package/data/sources/kcnav/maps/7-3.json +1529 -0
- package/data/sources/kcnav/maps/7-4.json +1000 -0
- package/data/sources/kcnav/maps/7-5.json +1570 -0
- package/data/sources/kcnav/maps/manifest.json +340 -0
- package/data/sources/kcwiki/routes/1-1.wiki +2 -0
- package/data/sources/kcwiki/routes/1-2.wiki +1 -0
- package/data/sources/kcwiki/routes/1-3.wiki +1 -0
- package/data/sources/kcwiki/routes/1-4.wiki +1 -0
- package/data/sources/kcwiki/routes/1-5.wiki +1 -0
- package/data/sources/kcwiki/routes/1-6.wiki +1 -0
- package/data/sources/kcwiki/routes/2-1.wiki +1 -0
- package/data/sources/kcwiki/routes/2-2.wiki +1 -0
- package/data/sources/kcwiki/routes/2-3.wiki +1 -0
- package/data/sources/kcwiki/routes/2-4.wiki +81 -0
- package/data/sources/kcwiki/routes/2-5.wiki +1 -0
- package/data/sources/kcwiki/routes/3-1.wiki +1 -0
- package/data/sources/kcwiki/routes/3-2.wiki +1 -0
- package/data/sources/kcwiki/routes/3-3.wiki +1 -0
- package/data/sources/kcwiki/routes/3-4.wiki +1 -0
- package/data/sources/kcwiki/routes/3-5.wiki +1 -0
- package/data/sources/kcwiki/routes/4-1.wiki +1 -0
- package/data/sources/kcwiki/routes/4-2.wiki +1 -0
- package/data/sources/kcwiki/routes/4-3.wiki +1 -0
- package/data/sources/kcwiki/routes/4-4.wiki +1 -0
- package/data/sources/kcwiki/routes/4-5.wiki +1 -0
- package/data/sources/kcwiki/routes/5-1.wiki +57 -0
- package/data/sources/kcwiki/routes/5-2.wiki +1 -0
- package/data/sources/kcwiki/routes/5-3.wiki +1 -0
- package/data/sources/kcwiki/routes/5-4.wiki +1 -0
- package/data/sources/kcwiki/routes/5-5.wiki +80 -0
- package/data/sources/kcwiki/routes/5-6.wiki +753 -0
- package/data/sources/kcwiki/routes/6-1.wiki +1 -0
- package/data/sources/kcwiki/routes/6-2.wiki +1 -0
- package/data/sources/kcwiki/routes/6-3.wiki +1 -0
- package/data/sources/kcwiki/routes/6-4.wiki +1 -0
- package/data/sources/kcwiki/routes/6-5.wiki +1 -0
- package/data/sources/kcwiki/routes/7-1.wiki +1 -0
- package/data/sources/kcwiki/routes/7-2.wiki +1 -0
- package/data/sources/kcwiki/routes/7-3.wiki +1 -0
- package/data/sources/kcwiki/routes/7-4.wiki +1 -0
- package/data/sources/kcwiki/routes/7-5.wiki +1 -0
- package/data/sources/kcwiki/routes/README.md +11 -0
- package/data/sources/kcwiki/routes/manifest.json +450 -0
- package/docs/research.md +445 -0
- package/docs/sources.md +85 -0
- package/enemies.js +56 -0
- package/exporters.js +182 -0
- package/index.js +1081 -0
- package/logic.js +603 -0
- package/overview.js +54 -0
- package/package.json +66 -0
- package/scripts/cache-kcnav-enemies.js +97 -0
- package/scripts/cache-kcnav-maps.js +109 -0
- package/scripts/cache-kcwiki-routes.js +96 -0
- package/scripts/compact-kcnav-enemies.js +53 -0
- package/scripts/compress-kcnav-maps.js +90 -0
package/logic.js
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const SHIP_CATEGORIES = {
|
|
4
|
+
DE: new Set([1]),
|
|
5
|
+
DD: new Set([2]),
|
|
6
|
+
CL: new Set([3]),
|
|
7
|
+
CLT: new Set([4]),
|
|
8
|
+
CT: new Set([21]),
|
|
9
|
+
CL_ALL: new Set([3, 4, 21]),
|
|
10
|
+
CA: new Set([5]),
|
|
11
|
+
CAV: new Set([6]),
|
|
12
|
+
CVL: new Set([7]),
|
|
13
|
+
BB: new Set([8, 9]),
|
|
14
|
+
BBV: new Set([10]),
|
|
15
|
+
BB_ALL: new Set([8, 9, 10]),
|
|
16
|
+
CV: new Set([11, 12, 18]),
|
|
17
|
+
CV_ALL: new Set([7, 11, 12, 18]),
|
|
18
|
+
SS: new Set([13, 14]),
|
|
19
|
+
AO: new Set([15]),
|
|
20
|
+
AV: new Set([16]),
|
|
21
|
+
LHA: new Set([17]),
|
|
22
|
+
AR: new Set([19]),
|
|
23
|
+
AS: new Set([20]),
|
|
24
|
+
CV_MAIN: new Set([11, 12, 18]),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const CATEGORY_LABELS = {
|
|
28
|
+
DE: '海防',
|
|
29
|
+
DD: '驱逐',
|
|
30
|
+
CL: '轻巡',
|
|
31
|
+
CLT: '雷巡',
|
|
32
|
+
CT: '练巡',
|
|
33
|
+
CL_ALL: 'CL系',
|
|
34
|
+
CA: '重巡',
|
|
35
|
+
CAV: '航巡',
|
|
36
|
+
CVL: '轻空母',
|
|
37
|
+
BB: '战舰(BB/FBB)',
|
|
38
|
+
BBV: '航战',
|
|
39
|
+
BB_ALL: 'BB系',
|
|
40
|
+
CV: '空母(CV/CVB)',
|
|
41
|
+
CV_ALL: 'CV系',
|
|
42
|
+
SS: '潜水系',
|
|
43
|
+
AO: '补给',
|
|
44
|
+
AV: '水母',
|
|
45
|
+
LHA: '扬陆',
|
|
46
|
+
AR: '工作舰',
|
|
47
|
+
AS: '潜母',
|
|
48
|
+
CV_MAIN: '正规/装甲空母',
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const EQUIPMENT_CATEGORY_LABELS = {
|
|
52
|
+
drum: '运输桶',
|
|
53
|
+
radar: '电探',
|
|
54
|
+
daihatsu: '大发系',
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function number(value) {
|
|
58
|
+
const parsed = Number(value)
|
|
59
|
+
return Number.isFinite(parsed) ? parsed : 0
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function integer(value) {
|
|
63
|
+
const parsed = Number(value)
|
|
64
|
+
return Number.isInteger(parsed) ? parsed : null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeMapId(value) {
|
|
68
|
+
if (typeof value === 'string') {
|
|
69
|
+
const match = value.trim().match(/^(\d+)[-](\d+)$/)
|
|
70
|
+
if (match) return `${number(match[1])}-${number(match[2])}`
|
|
71
|
+
const compact = value.trim().match(/^(\d+)(\d)$/)
|
|
72
|
+
if (compact) return `${number(compact[1])}-${number(compact[2])}`
|
|
73
|
+
}
|
|
74
|
+
const parsed = integer(value)
|
|
75
|
+
if (parsed != null && parsed >= 11) return `${Math.floor(parsed / 10)}-${parsed % 10}`
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function compare(left, operator, right) {
|
|
80
|
+
switch (operator) {
|
|
81
|
+
case '==':
|
|
82
|
+
return left === right
|
|
83
|
+
case '!=':
|
|
84
|
+
return left !== right
|
|
85
|
+
case '>':
|
|
86
|
+
return left > right
|
|
87
|
+
case '>=':
|
|
88
|
+
return left >= right
|
|
89
|
+
case '<':
|
|
90
|
+
return left < right
|
|
91
|
+
case '<=':
|
|
92
|
+
return left <= right
|
|
93
|
+
default:
|
|
94
|
+
return false
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function statusResult(status, predicate, label) {
|
|
99
|
+
return {
|
|
100
|
+
status,
|
|
101
|
+
predicate,
|
|
102
|
+
label: label || predicate?.label || predicateLabel(predicate),
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function categoryLabel(category) {
|
|
107
|
+
const value = String(category || '舰队')
|
|
108
|
+
if (value.endsWith('_ALL')) return `${value.slice(0, -4)}系`
|
|
109
|
+
return CATEGORY_LABELS[value] || value
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function predicateLabel(predicate) {
|
|
113
|
+
if (!predicate || typeof predicate !== 'object') return '未知条件'
|
|
114
|
+
if (predicate.label) return predicate.label
|
|
115
|
+
if (predicate.kind === 'always') return '始终适用'
|
|
116
|
+
if (predicate.kind === 'count') {
|
|
117
|
+
const category = Array.isArray(predicate.category)
|
|
118
|
+
? predicate.category.map(categoryLabel).join('+')
|
|
119
|
+
: categoryLabel(predicate.category)
|
|
120
|
+
return `${category} ${predicate.op || '=='} ${predicate.value}`
|
|
121
|
+
}
|
|
122
|
+
if (predicate.kind === 'equipmentShips') {
|
|
123
|
+
return `${EQUIPMENT_CATEGORY_LABELS[predicate.equipment] || predicate.equipment}舰数 ${predicate.op || '=='} ${predicate.value}`
|
|
124
|
+
}
|
|
125
|
+
if (predicate.kind === 'los') return `索敌 ${predicate.op || '=='} ${predicate.value}`
|
|
126
|
+
if (predicate.kind === 'losRange') return `索敌 ${predicate.min}~${predicate.max}`
|
|
127
|
+
if (predicate.kind === 'speed') {
|
|
128
|
+
if (predicate.mode === 'hasLow') return '含低速舰'
|
|
129
|
+
if (predicate.mode === 'highPlus') return '高速+舰队'
|
|
130
|
+
if (predicate.mode === 'fastest') return '最速舰队'
|
|
131
|
+
return '全高速'
|
|
132
|
+
}
|
|
133
|
+
if (predicate.kind === 'onlyCategories') {
|
|
134
|
+
return `仅含 ${(predicate.categories || []).map(categoryLabel).join('、')}`
|
|
135
|
+
}
|
|
136
|
+
if (predicate.kind === 'visited') return `已经过 ${predicate.node} 点`
|
|
137
|
+
if (predicate.kind === 'containsMaster') return `包含指定舰娘 (${(predicate.masterIds || []).join('/')})`
|
|
138
|
+
if (predicate.kind === 'containsName') return `包含 ${(predicate.names || []).join('/')}`
|
|
139
|
+
if (predicate.kind === 'nameCount') return `${(predicate.names || []).join('/')} ${predicate.op || '=='} ${predicate.value}`
|
|
140
|
+
if (predicate.kind === 'flag') return `状态:${predicate.name || predicate.flag}`
|
|
141
|
+
if (predicate.kind === 'phase') return `阶段:${predicate.phase || predicate.value}`
|
|
142
|
+
if (predicate.kind === 'equipmentShipsTotal') {
|
|
143
|
+
return `${(predicate.equipment || []).map((equipment) => EQUIPMENT_CATEGORY_LABELS[equipment] || equipment).join('+')} 舰数 ${predicate.op || '=='} ${predicate.value}`
|
|
144
|
+
}
|
|
145
|
+
if (predicate.kind === 'all') return (predicate.predicates || []).map(predicateLabel).join(' 且 ')
|
|
146
|
+
if (predicate.kind === 'any') return (predicate.predicates || []).map(predicateLabel).join(' 或 ')
|
|
147
|
+
return predicate.kind || '未知条件'
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function categoryCount(context, category) {
|
|
151
|
+
const categories = Array.isArray(category) ? category : [category]
|
|
152
|
+
if (categories.includes('__all__')) return number(context.shipCount)
|
|
153
|
+
return categories.reduce((total, value) => total + number(context.counts?.[value]), 0)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function evaluatePredicate(predicate, context = {}) {
|
|
157
|
+
if (!predicate || typeof predicate !== 'object') return statusResult('unknown', predicate, '未知条件')
|
|
158
|
+
|
|
159
|
+
switch (predicate.kind) {
|
|
160
|
+
case 'always':
|
|
161
|
+
return statusResult('true', predicate)
|
|
162
|
+
case 'count': {
|
|
163
|
+
const actual = predicate.field === 'ships'
|
|
164
|
+
? number(context.shipCount)
|
|
165
|
+
: categoryCount(context, predicate.category)
|
|
166
|
+
if (context.complete === false) return statusResult('unknown', predicate)
|
|
167
|
+
return statusResult(compare(actual, predicate.op || '==', number(predicate.value)) ? 'true' : 'false', predicate)
|
|
168
|
+
}
|
|
169
|
+
case 'equipmentShips': {
|
|
170
|
+
const actual = number(context.equipmentShips?.[predicate.equipment])
|
|
171
|
+
if (context.complete === false) return statusResult('unknown', predicate)
|
|
172
|
+
return statusResult(compare(actual, predicate.op || '==', number(predicate.value)) ? 'true' : 'false', predicate)
|
|
173
|
+
}
|
|
174
|
+
case 'speed': {
|
|
175
|
+
if (context.complete === false) return statusResult('unknown', predicate)
|
|
176
|
+
if (predicate.mode === 'highPlus' || predicate.mode === 'fastest') {
|
|
177
|
+
const enhancedSpeedClass = context.enhancedSpeedClass || 'unknown'
|
|
178
|
+
if (enhancedSpeedClass === 'unknown') return statusResult('unknown', predicate)
|
|
179
|
+
const matches = predicate.mode === 'fastest'
|
|
180
|
+
? enhancedSpeedClass === 'fastest'
|
|
181
|
+
: ['highPlus', 'fastest'].includes(enhancedSpeedClass)
|
|
182
|
+
return statusResult(matches ? 'true' : 'false', predicate)
|
|
183
|
+
}
|
|
184
|
+
if (!context.speedClass || context.speedClass === 'unknown') return statusResult('unknown', predicate)
|
|
185
|
+
const matches = predicate.mode === 'hasLow'
|
|
186
|
+
? context.speedClass === 'low'
|
|
187
|
+
: context.speedClass === 'high'
|
|
188
|
+
return statusResult(matches ? 'true' : 'false', predicate)
|
|
189
|
+
}
|
|
190
|
+
case 'los': {
|
|
191
|
+
if (context.losScore == null || !Number.isFinite(Number(context.losScore))) return statusResult('unknown', predicate)
|
|
192
|
+
return statusResult(compare(Number(context.losScore), predicate.op || '==', number(predicate.value)) ? 'true' : 'false', predicate)
|
|
193
|
+
}
|
|
194
|
+
case 'losRange': {
|
|
195
|
+
if (context.losScore == null || !Number.isFinite(Number(context.losScore))) return statusResult('unknown', predicate)
|
|
196
|
+
const score = Number(context.losScore)
|
|
197
|
+
const min = number(predicate.min)
|
|
198
|
+
const max = number(predicate.max)
|
|
199
|
+
return statusResult(score >= min && score < max ? 'true' : 'false', predicate)
|
|
200
|
+
}
|
|
201
|
+
case 'flagshipCategory': {
|
|
202
|
+
if (!context.flagship || context.flagship.categoryIds == null) return statusResult('unknown', predicate)
|
|
203
|
+
const matches = (Array.isArray(predicate.categories) ? predicate.categories : [predicate.category])
|
|
204
|
+
.some((category) => context.flagship.categoryIds.includes(category))
|
|
205
|
+
return statusResult(matches ? 'true' : 'false', predicate)
|
|
206
|
+
}
|
|
207
|
+
case 'onlyCategories': {
|
|
208
|
+
const categories = Array.isArray(predicate.categories) ? predicate.categories : []
|
|
209
|
+
if (context.complete === false || !context.ships?.length || !categories.length) {
|
|
210
|
+
return statusResult('unknown', predicate)
|
|
211
|
+
}
|
|
212
|
+
const matches = context.ships.every((ship) =>
|
|
213
|
+
categories.some((category) => ship.categoryIds?.includes(category)),
|
|
214
|
+
)
|
|
215
|
+
return statusResult(matches ? 'true' : 'false', predicate)
|
|
216
|
+
}
|
|
217
|
+
case 'visited': {
|
|
218
|
+
if (!Array.isArray(context.passedNodes)) return statusResult('unknown', predicate)
|
|
219
|
+
return statusResult(context.passedNodes.includes(predicate.node) ? 'true' : 'false', predicate)
|
|
220
|
+
}
|
|
221
|
+
case 'containsMaster': {
|
|
222
|
+
if (context.complete === false || !Array.isArray(context.ships)) return statusResult('unknown', predicate)
|
|
223
|
+
const masterIds = (predicate.masterIds || []).map((masterId) => number(masterId))
|
|
224
|
+
const matches = context.ships.some((ship) => masterIds.includes(number(ship.masterId)))
|
|
225
|
+
return statusResult(matches ? 'true' : 'false', predicate)
|
|
226
|
+
}
|
|
227
|
+
case 'containsName': {
|
|
228
|
+
if (context.complete === false || !Array.isArray(context.ships)) return statusResult('unknown', predicate)
|
|
229
|
+
const names = (predicate.names || []).map((name) => String(name))
|
|
230
|
+
const matches = context.ships.some((ship) => names.some((name) => String(ship.name || '').includes(name)))
|
|
231
|
+
return statusResult(matches ? 'true' : 'false', predicate)
|
|
232
|
+
}
|
|
233
|
+
case 'nameCount': {
|
|
234
|
+
if (context.complete === false || !Array.isArray(context.ships)) return statusResult('unknown', predicate)
|
|
235
|
+
const names = (predicate.names || []).map((name) => String(name))
|
|
236
|
+
const actual = context.ships.filter((ship) => names.some((name) => String(ship.name || '').includes(name))).length
|
|
237
|
+
return statusResult(compare(actual, predicate.op || '==', number(predicate.value)) ? 'true' : 'false', predicate)
|
|
238
|
+
}
|
|
239
|
+
case 'flag': {
|
|
240
|
+
if (!context.flags || typeof context.flags[predicate.flag] !== 'boolean') return statusResult('unknown', predicate)
|
|
241
|
+
return statusResult(context.flags[predicate.flag] === Boolean(predicate.value) ? 'true' : 'false', predicate)
|
|
242
|
+
}
|
|
243
|
+
case 'phase': {
|
|
244
|
+
if (typeof context.phase !== 'string' || !context.phase) return statusResult('unknown', predicate)
|
|
245
|
+
return statusResult(context.phase === String(predicate.phase || predicate.value) ? 'true' : 'false', predicate)
|
|
246
|
+
}
|
|
247
|
+
case 'equipmentShipsTotal': {
|
|
248
|
+
if (context.complete === false) return statusResult('unknown', predicate)
|
|
249
|
+
const actual = (predicate.equipment || []).reduce((total, equipment) => total + number(context.equipmentShips?.[equipment]), 0)
|
|
250
|
+
return statusResult(compare(actual, predicate.op || '==', number(predicate.value)) ? 'true' : 'false', predicate)
|
|
251
|
+
}
|
|
252
|
+
case 'all': {
|
|
253
|
+
const results = (predicate.predicates || []).map((child) => evaluatePredicate(child, context))
|
|
254
|
+
if (results.some((result) => result.status === 'false')) return statusResult('false', predicate)
|
|
255
|
+
if (results.some((result) => result.status === 'unknown')) return statusResult('unknown', predicate)
|
|
256
|
+
return statusResult('true', predicate)
|
|
257
|
+
}
|
|
258
|
+
case 'any': {
|
|
259
|
+
const results = (predicate.predicates || []).map((child) => evaluatePredicate(child, context))
|
|
260
|
+
if (results.some((result) => result.status === 'true')) return statusResult('true', predicate)
|
|
261
|
+
if (results.some((result) => result.status === 'unknown')) return statusResult('unknown', predicate)
|
|
262
|
+
return statusResult('false', predicate)
|
|
263
|
+
}
|
|
264
|
+
case 'not': {
|
|
265
|
+
const result = evaluatePredicate(predicate.predicate, context)
|
|
266
|
+
if (result.status === 'unknown') return statusResult('unknown', predicate)
|
|
267
|
+
return statusResult(result.status === 'true' ? 'false' : 'true', predicate)
|
|
268
|
+
}
|
|
269
|
+
default:
|
|
270
|
+
return statusResult('unknown', predicate)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function rulesForNode(mapDefinition, node) {
|
|
275
|
+
return (mapDefinition?.rules || [])
|
|
276
|
+
.filter((rule) => rule.node === node)
|
|
277
|
+
.sort((left, right) => number(left.priority) - number(right.priority))
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function containsPhasePredicate(predicate) {
|
|
281
|
+
if (!predicate || typeof predicate !== 'object') return false
|
|
282
|
+
if (predicate.kind === 'phase') return true
|
|
283
|
+
if (Array.isArray(predicate.predicates) && predicate.predicates.some(containsPhasePredicate)) return true
|
|
284
|
+
return containsPhasePredicate(predicate.predicate)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function hasPhaseRules(mapDefinition, node) {
|
|
288
|
+
return rulesForNode(mapDefinition, node).some((rule) => containsPhasePredicate(rule.predicate))
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function selectRouteRule(mapDefinition, node, context) {
|
|
292
|
+
const rules = rulesForNode(mapDefinition, node)
|
|
293
|
+
for (const rule of rules) {
|
|
294
|
+
const predicate = evaluatePredicate(rule.predicate, context)
|
|
295
|
+
if (predicate.status === 'true') {
|
|
296
|
+
const status = rule.terminal === true
|
|
297
|
+
? 'terminal'
|
|
298
|
+
: rule.outcomes?.length ? 'matched' : 'unknown'
|
|
299
|
+
return { status, rule, predicate }
|
|
300
|
+
}
|
|
301
|
+
if (predicate.status === 'unknown') return { status: 'unknown', rule, predicate }
|
|
302
|
+
}
|
|
303
|
+
return { status: rules.length ? 'unknown' : 'terminal', rule: null, predicate: null }
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function normalizeOutcomes(outcomes, rule = null, options = {}) {
|
|
307
|
+
const normalized = (Array.isArray(outcomes) ? outcomes : [])
|
|
308
|
+
.filter((outcome) => outcome && typeof outcome.to === 'string')
|
|
309
|
+
.map((outcome) => ({
|
|
310
|
+
to: outcome.to,
|
|
311
|
+
probability: outcome.probability == null
|
|
312
|
+
? null
|
|
313
|
+
: Number.isFinite(Number(outcome.probability)) ? Number(outcome.probability) : null,
|
|
314
|
+
}))
|
|
315
|
+
|
|
316
|
+
const hasMissingProbability = normalized.some((outcome) => outcome.probability == null)
|
|
317
|
+
const equalize = options.equalizeUnknown !== false && (hasMissingProbability || rule?.confidence === 'unknown')
|
|
318
|
+
const estimated = equalize || rule?.confidence === 'approximate' && normalized.length > 1
|
|
319
|
+
return normalized.map((outcome) => {
|
|
320
|
+
const normalizedOutcome = {
|
|
321
|
+
to: outcome.to,
|
|
322
|
+
probability: equalize && normalized.length ? 1 / normalized.length : outcome.probability,
|
|
323
|
+
}
|
|
324
|
+
if (estimated) normalizedOutcome.estimated = true
|
|
325
|
+
return normalizedOutcome
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function routeDecision(mapDefinition, node, context, overrides = {}) {
|
|
330
|
+
const selected = selectRouteRule(mapDefinition, node, context)
|
|
331
|
+
const configuredManualOutcomes = mapDefinition?.manualOutcomes?.[node]
|
|
332
|
+
const manual = selected.rule?.manual === true
|
|
333
|
+
|| Array.isArray(mapDefinition?.manualNodes) && mapDefinition.manualNodes.includes(node)
|
|
334
|
+
const manualOutcomes = Array.isArray(configuredManualOutcomes)
|
|
335
|
+
? normalizeOutcomes(configuredManualOutcomes, { confidence: 'unknown' }, { equalizeUnknown: false })
|
|
336
|
+
: null
|
|
337
|
+
const autoOutcomes = selected.status === 'matched'
|
|
338
|
+
? normalizeOutcomes(selected.rule?.outcomes, selected.rule)
|
|
339
|
+
: []
|
|
340
|
+
const phaseRuleMatched = manual && hasPhaseRules(mapDefinition, node) && selected.status === 'matched'
|
|
341
|
+
const baseOutcomes = phaseRuleMatched
|
|
342
|
+
? autoOutcomes
|
|
343
|
+
: manualOutcomes || autoOutcomes
|
|
344
|
+
const override = overrides[node]
|
|
345
|
+
const selectableOutcomes = manualOutcomes || baseOutcomes
|
|
346
|
+
const validOverride = manual && selectableOutcomes.some((outcome) => outcome.to === override)
|
|
347
|
+
const outcomes = validOverride
|
|
348
|
+
? [{ to: override, probability: 1 }]
|
|
349
|
+
: baseOutcomes
|
|
350
|
+
return {
|
|
351
|
+
node,
|
|
352
|
+
status: selected.status,
|
|
353
|
+
rule: selected.rule,
|
|
354
|
+
predicate: selected.predicate,
|
|
355
|
+
baseOutcomes,
|
|
356
|
+
manualOutcomes,
|
|
357
|
+
outcomes,
|
|
358
|
+
manual,
|
|
359
|
+
phaseRuleMatched,
|
|
360
|
+
manualChoiceRequired: manual && !phaseRuleMatched && !validOverride,
|
|
361
|
+
manualOverride: validOverride ? override : null,
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function edgeKey(from, to) {
|
|
366
|
+
return `${from}->${to}`
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function propagateProbability(start, decisions) {
|
|
370
|
+
const reach = {}
|
|
371
|
+
const edgeMass = {}
|
|
372
|
+
const unknownNodes = new Set()
|
|
373
|
+
const unknownEdges = new Set()
|
|
374
|
+
const estimatedNodes = new Set()
|
|
375
|
+
const estimatedEdges = new Set()
|
|
376
|
+
|
|
377
|
+
function walk(node, mass, path, estimatedPath = false) {
|
|
378
|
+
if (!Number.isFinite(mass) || mass <= 0) return
|
|
379
|
+
reach[node] = number(reach[node]) + mass
|
|
380
|
+
if (estimatedPath) estimatedNodes.add(node)
|
|
381
|
+
if (path.has(node)) {
|
|
382
|
+
unknownNodes.add(node)
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
const decision = decisions[node]
|
|
386
|
+
if (!decision || decision.status === 'unknown' || decision.status === 'terminal' && !decision.outcomes.length) {
|
|
387
|
+
if (decision?.status === 'unknown') unknownNodes.add(node)
|
|
388
|
+
return
|
|
389
|
+
}
|
|
390
|
+
const nextPath = new Set(path)
|
|
391
|
+
nextPath.add(node)
|
|
392
|
+
decision.outcomes.forEach((outcome) => {
|
|
393
|
+
const key = edgeKey(node, outcome.to)
|
|
394
|
+
if (outcome.probability == null) {
|
|
395
|
+
unknownEdges.add(key)
|
|
396
|
+
return
|
|
397
|
+
}
|
|
398
|
+
const edgeEstimated = estimatedPath || outcome.estimated === true
|
|
399
|
+
if (edgeEstimated) estimatedEdges.add(key)
|
|
400
|
+
edgeMass[key] = number(edgeMass[key]) + mass * outcome.probability
|
|
401
|
+
walk(outcome.to, mass * outcome.probability, nextPath, edgeEstimated)
|
|
402
|
+
})
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
walk(start, 1, new Set())
|
|
406
|
+
return {
|
|
407
|
+
reach,
|
|
408
|
+
edgeMass,
|
|
409
|
+
unknownNodes: Array.from(unknownNodes),
|
|
410
|
+
unknownEdges: Array.from(unknownEdges),
|
|
411
|
+
estimatedNodes: Array.from(estimatedNodes),
|
|
412
|
+
estimatedEdges: Array.from(estimatedEdges),
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function evaluateMap(mapDefinition, context = {}, overrides = {}) {
|
|
417
|
+
const nodes = new Set([mapDefinition?.start || '1'])
|
|
418
|
+
;(mapDefinition?.rules || []).forEach((rule) => nodes.add(rule.node))
|
|
419
|
+
const decisions = {}
|
|
420
|
+
nodes.forEach((node) => {
|
|
421
|
+
decisions[node] = routeDecision(mapDefinition, node, context, overrides)
|
|
422
|
+
decisions[node].outcomes.forEach((outcome) => nodes.add(outcome.to))
|
|
423
|
+
})
|
|
424
|
+
// A rule outcome can introduce another node; resolve its decision once as well.
|
|
425
|
+
Array.from(nodes).forEach((node) => {
|
|
426
|
+
if (!decisions[node]) decisions[node] = routeDecision(mapDefinition, node, context, overrides)
|
|
427
|
+
})
|
|
428
|
+
const probability = propagateProbability(mapDefinition?.start || '1', decisions)
|
|
429
|
+
return { decisions, probability }
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function mapNodeLabel(mapId, cell, maps) {
|
|
433
|
+
const normalized = normalizeMapId(mapId)
|
|
434
|
+
const route = maps?.[normalized]?.route
|
|
435
|
+
const entry = route?.[number(cell)]
|
|
436
|
+
return Array.isArray(entry) && typeof entry[1] === 'string' ? entry[1] : ''
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function shipCategoryIds(typeId) {
|
|
440
|
+
return Object.entries(SHIP_CATEGORIES)
|
|
441
|
+
.filter(([, ids]) => ids.has(number(typeId)))
|
|
442
|
+
.map(([category]) => category)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function equipmentKind(master) {
|
|
446
|
+
const name = String(master?.api_name || '')
|
|
447
|
+
if (name.includes('ドラム缶') || name.includes('运输桶')) return 'drum'
|
|
448
|
+
if ([12, 13].includes(number(master?.api_type?.[2])) || name.includes('電探') || name.includes('电探')) return 'radar'
|
|
449
|
+
const excludedDaihatsu = ['特大発動艇+戦車第11連隊', 'M4A1 DD', '装甲艇(AB艇)', '特大動艇+战车第11连队']
|
|
450
|
+
if (!excludedDaihatsu.some((item) => name.includes(item)) && (name.includes('大発') || name.includes('大发') || name.includes('内火艇'))) return 'daihatsu'
|
|
451
|
+
return null
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function getFleet(state, deckId = 1) {
|
|
455
|
+
const fleets = state?.info?.fleets
|
|
456
|
+
if (Array.isArray(fleets)) return fleets[deckId - 1] || null
|
|
457
|
+
return fleets?.[deckId - 1] || fleets?.[deckId] || null
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function fleetContextFromState(state, deckId = 1, options = {}) {
|
|
461
|
+
const fleet = getFleet(state, deckId)
|
|
462
|
+
const instanceShips = state?.info?.ships || {}
|
|
463
|
+
const masterShips = state?.const?.$ships || {}
|
|
464
|
+
const instanceEquips = state?.info?.equips || {}
|
|
465
|
+
const masterEquips = state?.const?.$equips || {}
|
|
466
|
+
const shipIds = Array.isArray(fleet?.api_ship) ? fleet.api_ship : []
|
|
467
|
+
const activeShipIds = shipIds.filter((id) => number(id) > 0)
|
|
468
|
+
const dataIssues = []
|
|
469
|
+
if (!fleet) dataIssues.push('舰队')
|
|
470
|
+
if (!Array.isArray(fleet?.api_ship)) dataIssues.push('舰队槽位')
|
|
471
|
+
if (!activeShipIds.length) dataIssues.push('舰娘')
|
|
472
|
+
const ships = shipIds
|
|
473
|
+
.filter((id) => number(id) > 0)
|
|
474
|
+
.map((instanceId, index) => {
|
|
475
|
+
const instance = instanceShips[instanceId]
|
|
476
|
+
const master = masterShips[instance?.api_ship_id]
|
|
477
|
+
if (!instance) {
|
|
478
|
+
dataIssues.push(`舰娘实例 #${instanceId}`)
|
|
479
|
+
return null
|
|
480
|
+
}
|
|
481
|
+
if (!master) {
|
|
482
|
+
dataIssues.push(`舰娘 master #${instance.api_ship_id}`)
|
|
483
|
+
return null
|
|
484
|
+
}
|
|
485
|
+
if (!Number.isFinite(Number(master.api_stype))) dataIssues.push(`舰种 #${instance.api_ship_id}`)
|
|
486
|
+
if (!Array.isArray(instance.api_slot)) dataIssues.push(`装备槽位 #${instanceId}`)
|
|
487
|
+
const equipmentIds = Array.isArray(instance.api_slot) ? instance.api_slot : []
|
|
488
|
+
const equipments = []
|
|
489
|
+
for (const id of equipmentIds.filter((value) => number(value) > 0)) {
|
|
490
|
+
const equipment = instanceEquips[id]
|
|
491
|
+
const masterEquipment = masterEquips[equipment?.api_slotitem_id]
|
|
492
|
+
if (!equipment || !masterEquipment) {
|
|
493
|
+
dataIssues.push(`装备 #${id}`)
|
|
494
|
+
continue
|
|
495
|
+
}
|
|
496
|
+
equipments.push(masterEquipment)
|
|
497
|
+
}
|
|
498
|
+
if (!Number.isFinite(Number(instance.api_soku))) dataIssues.push(`速力 #${instanceId}`)
|
|
499
|
+
const categoryIds = shipCategoryIds(master.api_stype)
|
|
500
|
+
return {
|
|
501
|
+
instanceId: number(instanceId),
|
|
502
|
+
masterId: number(instance.api_ship_id),
|
|
503
|
+
name: master.api_name || `#${instance.api_ship_id}`,
|
|
504
|
+
categoryIds,
|
|
505
|
+
typeId: number(master.api_stype),
|
|
506
|
+
speed: Number.isFinite(Number(instance.api_soku)) ? Number(instance.api_soku) : null,
|
|
507
|
+
baseLos: Number.isFinite(Number(master.api_sakuteki)) ? Number(master.api_sakuteki) : null,
|
|
508
|
+
equipments,
|
|
509
|
+
equipmentKinds: equipments.map(equipmentKind).filter(Boolean),
|
|
510
|
+
flagship: index === 0,
|
|
511
|
+
}
|
|
512
|
+
})
|
|
513
|
+
.filter(Boolean)
|
|
514
|
+
|
|
515
|
+
const fleetComplete = dataIssues.length === 0 && ships.length === activeShipIds.length
|
|
516
|
+
const counts = {}
|
|
517
|
+
Object.keys(SHIP_CATEGORIES).forEach((category) => {
|
|
518
|
+
counts[category] = ships.filter((ship) => ship.categoryIds.includes(category)).length
|
|
519
|
+
})
|
|
520
|
+
const speedKnown = fleetComplete && ships.length > 0 && ships.every((ship) => Number.isFinite(ship.speed))
|
|
521
|
+
const fleetSpeed = speedKnown ? Math.min(...ships.map((ship) => ship.speed)) : null
|
|
522
|
+
const speedClass = fleetSpeed == null ? 'unknown' : fleetSpeed < 10 ? 'low' : 'high'
|
|
523
|
+
const enhancedSpeedClass = fleetSpeed == null
|
|
524
|
+
? 'unknown'
|
|
525
|
+
: fleetSpeed >= 20 ? 'fastest'
|
|
526
|
+
: fleetSpeed >= 15 ? 'highPlus'
|
|
527
|
+
: 'high'
|
|
528
|
+
const equipmentShips = {
|
|
529
|
+
drum: ships.filter((ship) => ship.equipmentKinds.includes('drum')).length,
|
|
530
|
+
radar: ships.filter((ship) => ship.equipmentKinds.includes('radar')).length,
|
|
531
|
+
daihatsu: ships.filter((ship) => ship.equipmentKinds.includes('daihatsu')).length,
|
|
532
|
+
}
|
|
533
|
+
const losCoefficients = [1, 2, 3, 4]
|
|
534
|
+
const poiLosResults = losCoefficients.map((coefficient) => {
|
|
535
|
+
if (typeof options.losCalculator !== 'function') return null
|
|
536
|
+
try {
|
|
537
|
+
return options.losCalculator(state, deckId, coefficient)
|
|
538
|
+
} catch (_) {
|
|
539
|
+
return null
|
|
540
|
+
}
|
|
541
|
+
})
|
|
542
|
+
const poiLosScores = poiLosResults.map((result) => {
|
|
543
|
+
const score = result && typeof result === 'object' ? result.total : result
|
|
544
|
+
return score != null && Number.isFinite(Number(score)) ? Number(score) : null
|
|
545
|
+
})
|
|
546
|
+
const requestedCoefficient = Number(options.losCoefficient)
|
|
547
|
+
const selectedCoefficient = losCoefficients.includes(requestedCoefficient) ? requestedCoefficient : 1
|
|
548
|
+
const selectedIndex = selectedCoefficient - 1
|
|
549
|
+
const poiLos = poiLosResults[selectedIndex]
|
|
550
|
+
const poiLosScore = poiLosScores[selectedIndex]
|
|
551
|
+
const hasPoiLos = fleetComplete && poiLosScores.every((score) => score != null)
|
|
552
|
+
if (!hasPoiLos) dataIssues.push('poi 33式索敌')
|
|
553
|
+
const dataAvailable = fleetComplete && hasPoiLos
|
|
554
|
+
return {
|
|
555
|
+
complete: dataAvailable,
|
|
556
|
+
fleetComplete,
|
|
557
|
+
dataAvailable,
|
|
558
|
+
dataIssues: Array.from(new Set(dataIssues)),
|
|
559
|
+
shipCount: ships.length,
|
|
560
|
+
ships,
|
|
561
|
+
flagship: ships[0] || null,
|
|
562
|
+
counts,
|
|
563
|
+
equipmentShips,
|
|
564
|
+
fleetSpeed,
|
|
565
|
+
speedClass,
|
|
566
|
+
enhancedSpeedClass,
|
|
567
|
+
phase: typeof options.phase === 'string' ? options.phase : null,
|
|
568
|
+
passedNodes: Array.isArray(options.passedNodes) ? options.passedNodes : null,
|
|
569
|
+
flags: options.flags || null,
|
|
570
|
+
losScore: hasPoiLos ? poiLosScore : null,
|
|
571
|
+
losScores: hasPoiLos ? poiLosScores : [null, null, null, null],
|
|
572
|
+
losCoefficient: selectedCoefficient,
|
|
573
|
+
admiralCoefficient: options.admiralCoefficient ?? null,
|
|
574
|
+
losApproximate: false,
|
|
575
|
+
losSource: hasPoiLos ? 'poi-33' : 'unavailable',
|
|
576
|
+
losDetails: hasPoiLos && typeof poiLos === 'object' ? poiLos : null,
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function formatPercent(value, estimated = false) {
|
|
581
|
+
if (!Number.isFinite(Number(value))) return '未知'
|
|
582
|
+
const suffix = estimated ? '?' : ''
|
|
583
|
+
return `${(Number(value) * 100).toFixed(1).replace(/\.0$/, '')}%${suffix}`
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
module.exports = {
|
|
587
|
+
CATEGORY_LABELS,
|
|
588
|
+
SHIP_CATEGORIES,
|
|
589
|
+
categoryCount,
|
|
590
|
+
edgeKey,
|
|
591
|
+
evaluateMap,
|
|
592
|
+
evaluatePredicate,
|
|
593
|
+
fleetContextFromState,
|
|
594
|
+
formatPercent,
|
|
595
|
+
mapNodeLabel,
|
|
596
|
+
normalizeMapId,
|
|
597
|
+
normalizeOutcomes,
|
|
598
|
+
predicateLabel,
|
|
599
|
+
propagateProbability,
|
|
600
|
+
routeDecision,
|
|
601
|
+
selectRouteRule,
|
|
602
|
+
shipCategoryIds,
|
|
603
|
+
}
|
package/overview.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { routeDecision } = require('./logic')
|
|
4
|
+
const { loadEnemies } = require('./enemies')
|
|
5
|
+
const types = require('./data/kcnav-node-types-by-map.json')
|
|
6
|
+
const catalog = require('./data/kcnav-node-types.json')
|
|
7
|
+
|
|
8
|
+
function losBounds(predicate) {
|
|
9
|
+
if (!predicate) return []
|
|
10
|
+
if (predicate.kind === 'los') return [predicate.value]
|
|
11
|
+
if (predicate.kind === 'losRange') return [predicate.min, predicate.max]
|
|
12
|
+
return [...(predicate.predicates || []).flatMap(losBounds), ...losBounds(predicate.predicate)].filter(Number.isFinite)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function mapOverview(mapId, definition, context, overrides = {}, loader = loadEnemies) {
|
|
16
|
+
const visited = new Set(), missing = new Set(), los = []
|
|
17
|
+
let partial = false, steps = 0, superiority = null, supremacy = null
|
|
18
|
+
function walk(node, path) {
|
|
19
|
+
if (++steps > 10000 || path.includes(node)) { partial = true; return }
|
|
20
|
+
visited.add(node)
|
|
21
|
+
const current = { ...context, passedNodes: path }
|
|
22
|
+
const decide = score => routeDecision(definition, node, { ...current, losScore: score }, overrides)
|
|
23
|
+
const signature = decision => JSON.stringify([decision.status, decision.manualChoiceRequired, decision.outcomes])
|
|
24
|
+
// Only count LOS boundaries that actually change this fleet's decision.
|
|
25
|
+
// Earlier fixed rules and inapplicable fleet/phase branches must not count.
|
|
26
|
+
const bounds = new Set((definition.rules || []).filter(rule => rule.node === node).flatMap(rule => losBounds(rule.predicate)))
|
|
27
|
+
for (const value of bounds) {
|
|
28
|
+
const decisions = [decide(value - 0.000001), decide(value), decide(value + 0.000001)]
|
|
29
|
+
if (new Set(decisions.map(signature)).size > 1) los.push({ node, value })
|
|
30
|
+
}
|
|
31
|
+
const decision = routeDecision(definition, node, current, overrides)
|
|
32
|
+
if (decision.manualChoiceRequired || decision.status === 'unknown') { partial = true; return }
|
|
33
|
+
for (const outcome of decision.outcomes) if (outcome.probability > 0) walk(outcome.to, [...path, node])
|
|
34
|
+
}
|
|
35
|
+
walk(definition.start, [])
|
|
36
|
+
for (const node of visited) {
|
|
37
|
+
if (!catalog[types[mapId]?.[node]]?.hasEnemies) continue
|
|
38
|
+
const data = loader(mapId, node)
|
|
39
|
+
const rows = data.status === 'ready' ? data.rows.filter(row => row.count > 0) : []
|
|
40
|
+
if (!rows.length) missing.add(node)
|
|
41
|
+
for (const row of rows) {
|
|
42
|
+
if (!row.thresholds || row.uncertain) missing.add(node)
|
|
43
|
+
if (row.thresholds) {
|
|
44
|
+
superiority = Math.max(superiority ?? 0, row.thresholds.superiority)
|
|
45
|
+
supremacy = Math.max(supremacy ?? 0, row.thresholds.supremacy)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const maxLos = los.length ? Math.max(...los.map(item => item.value)) : null
|
|
50
|
+
return { nodes: [...visited], partial, missing: [...missing], superiority, supremacy, maxLos,
|
|
51
|
+
losNodes: [...new Set(los.filter(item => item.value === maxLos).map(item => item.node))] }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = { mapOverview }
|