poi-plugin-kai-planner 1.0.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.
Files changed (54) hide show
  1. package/README.md +48 -0
  2. package/index.js +35 -0
  3. package/package.json +25 -0
  4. package/src/app/KaiPlannerApp.js +211 -0
  5. package/src/app/tabs/daily/DailyTab.js +439 -0
  6. package/src/app/tabs/debug/DebugTab.js +554 -0
  7. package/src/app/tabs/wishlist/CreatePlanForm.js +185 -0
  8. package/src/app/tabs/wishlist/WishlistTab.js +704 -0
  9. package/src/app/tabs/wishlist/components/MouseComboBox.js +185 -0
  10. package/src/app/tabs/wishlist/components/WishlistExpandedDetail.js +170 -0
  11. package/src/app/tabs/wishlist/components/WishlistTable.js +253 -0
  12. package/src/core/poi/secretaryName.js +64 -0
  13. package/src/data/indexes/buildArrangementIndex.js +103 -0
  14. package/src/data/loaders/loadStaticData.js +182 -0
  15. package/src/data/static/data_manifest.json +15 -0
  16. package/src/data/static/equip_base_cost.json +5000 -0
  17. package/src/data/static/equipment_upgrade_path.json +3555 -0
  18. package/src/data/static/improvement_arrangement.json +15677 -0
  19. package/src/data/static/improvement_consume_item.json +8933 -0
  20. package/src/data/static/improvement_consume_step.json +9284 -0
  21. package/src/data/static/improvement_upgrade_cost.json +4766 -0
  22. package/src/data/static/improvement_upgrade_target.json +2641 -0
  23. package/src/data/static/material.json +90 -0
  24. package/src/services/common/secretaryDisplay.js +42 -0
  25. package/src/services/daily/buildDailyViewModel.js +402 -0
  26. package/src/services/planner/buildUpgradePath.js +79 -0
  27. package/src/services/planner/calcImproveSteps.js +104 -0
  28. package/src/services/planner/calcRemainingPlan.js +169 -0
  29. package/src/services/planner/calcRoutePlan.js +85 -0
  30. package/src/services/planner/calcUpgradeStep.js +85 -0
  31. package/src/services/planner/detectCurrentPosition.js +57 -0
  32. package/src/services/planner/summarizeShortage.js +76 -0
  33. package/src/services/player/countPlayerEquipByMasterId.js +27 -0
  34. package/src/services/player/getEquipOwnerShip.js +66 -0
  35. package/src/services/player/getPlayerData.js +14 -0
  36. package/src/services/player/getPlayerItemCountByUseitemId.js +45 -0
  37. package/src/services/player/getReduxStateFromEnvWindow.js +12 -0
  38. package/src/services/player/resolveMaterialKeyToUseitemId.js +54 -0
  39. package/src/services/static/indexes/buildConsumeIndexes.js +28 -0
  40. package/src/services/static/indexes/buildPathIndexes.js +29 -0
  41. package/src/services/static/indexes/buildUpgradeIndexes.js +57 -0
  42. package/src/services/static/version/dataSourceConfig.js +25 -0
  43. package/src/services/static/version/dataUpdateManager.js +176 -0
  44. package/src/services/static/version/versionStore.js +205 -0
  45. package/src/services/utils/toInt.js +11 -0
  46. package/src/services/utils/tokyoTime.js +58 -0
  47. package/src/services/wishlist/buildWishlistViewModel.js +56 -0
  48. package/src/services/wishlist/dropdownInteraction.js +30 -0
  49. package/src/services/wishlist/wishlistActions.js +485 -0
  50. package/src/storage/userPlans/fileStore.js +106 -0
  51. package/src/storage/userPlans/localStorageStore.js +50 -0
  52. package/src/storage/userPlans/migrate.js +60 -0
  53. package/src/storage/userPlans/planStore.js +107 -0
  54. package/src/storage/userPlans/storeAdapter.js +77 -0
@@ -0,0 +1,104 @@
1
+ /* src/services/planner/calcImproveSteps.js */
2
+
3
+ const { toInt } = require("../utils/toInt");
4
+
5
+ function getEquipName(masterEquipsById, equipId) {
6
+ const row = masterEquipsById && masterEquipsById[String(equipId)];
7
+ if (row && row.api_name) return String(row.api_name);
8
+ return `UnknownEquip(${equipId})`;
9
+ }
10
+
11
+ function inStarRange(star, starFrom, starTo) {
12
+ if (starFrom == null && starTo == null) return true;
13
+ if (starFrom == null) return star <= toInt(starTo, 999);
14
+ if (starTo == null) return star >= toInt(starFrom, 0);
15
+ return star >= toInt(starFrom, 0) && star <= toInt(starTo, 999);
16
+ }
17
+
18
+ function normalizeItemRow(row, staticData, masterEquipsById) {
19
+ if (!row) return null;
20
+
21
+ if (row.item_equipment_id != null) {
22
+ const id = toInt(row.item_equipment_id, 0);
23
+ if (!id) return null;
24
+ return {
25
+ kind: "equipment",
26
+ id,
27
+ name: getEquipName(masterEquipsById, id),
28
+ count: toInt(row.count, 0),
29
+ };
30
+ }
31
+
32
+ if (row.item_material_key != null) {
33
+ const key = String(row.item_material_key);
34
+ const m = staticData.materialByKey ? staticData.materialByKey[key] : null;
35
+ return {
36
+ kind: "material",
37
+ key,
38
+ name: m && m.name ? String(m.name) : key,
39
+ count: toInt(row.count, 0),
40
+ };
41
+ }
42
+
43
+ return null;
44
+ }
45
+
46
+ function calcImproveSteps({
47
+ equipId,
48
+ fromStar,
49
+ toStar,
50
+ consumeIndexes,
51
+ staticData,
52
+ masterEquipsById,
53
+ }) {
54
+ const eqId = toInt(equipId, 0);
55
+ const from = Math.max(0, toInt(fromStar, 0));
56
+ const to = Math.max(0, toInt(toStar, 0));
57
+ if (!eqId || to <= from) return [];
58
+
59
+ const stepById = consumeIndexes && consumeIndexes.stepById ? consumeIndexes.stepById : {};
60
+ const itemsByStepId = consumeIndexes && consumeIndexes.itemsByStepId ? consumeIndexes.itemsByStepId : {};
61
+ const eqName = getEquipName(masterEquipsById, eqId);
62
+
63
+ const steps = [];
64
+ for (let star = from; star < to; star += 1) {
65
+ const phase = star < 6 ? 0 : 1;
66
+ const stepId = `${eqId}_${phase}`;
67
+ const meta = stepById[stepId] || null;
68
+ const itemRows = itemsByStepId[stepId] || [];
69
+
70
+ const devEach = phase === 0 ? toInt(meta && meta.dev_min, 0) : toInt(meta && meta.dev_max, 0);
71
+ const screwEach = phase === 0 ? toInt(meta && meta.screw_min, 0) : toInt(meta && meta.screw_max, 0);
72
+
73
+ const costItems = [];
74
+ for (const r of itemRows) {
75
+ if (!r || r.is_deleted === true) continue;
76
+ if (!inStarRange(star, r.star_from, r.star_to)) continue;
77
+ const item = normalizeItemRow(r, staticData, masterEquipsById);
78
+ if (!item || !item.count) continue;
79
+ costItems.push(item);
80
+ }
81
+
82
+ steps.push({
83
+ kind: "improve",
84
+ equipId: eqId,
85
+ equipName: eqName,
86
+ fromStar: star,
87
+ toStar: star + 1,
88
+ phase,
89
+ step_id: stepId,
90
+ calcCost: {
91
+ dev: devEach,
92
+ screw: screwEach,
93
+ items: costItems,
94
+ policy: phase === 0 ? "improve_phase0_min" : "improve_phase1_max",
95
+ },
96
+ });
97
+ }
98
+
99
+ return steps;
100
+ }
101
+
102
+ module.exports = {
103
+ calcImproveSteps,
104
+ };
@@ -0,0 +1,169 @@
1
+ /* src/services/planner/calcRemainingPlan.js */
2
+
3
+ const { toInt } = require("../utils/toInt");
4
+ const { calcRoutePlan } = require("./calcRoutePlan");
5
+ const { calcImproveSteps } = require("./calcImproveSteps");
6
+ const { calcUpgradeStep } = require("./calcUpgradeStep");
7
+ const { summarizeShortage } = require("./summarizeShortage");
8
+
9
+ function buildDisplaySteps(steps) {
10
+ return (steps || []).map((s, idx) => {
11
+ if (s.kind === "improve") {
12
+ return {
13
+ index: idx,
14
+ kind: "improve",
15
+ key: `improve:${s.equipId}:${s.fromStar}->${s.toStar}`,
16
+ label: `${s.equipName} +${s.fromStar} -> +${s.toStar}`,
17
+ equipId: s.equipId,
18
+ fromStar: s.fromStar,
19
+ toStar: s.toStar,
20
+ };
21
+ }
22
+ return {
23
+ index: idx,
24
+ kind: "upgrade",
25
+ key: `upgrade:${s.equipId}->${s.nextEquipId}`,
26
+ label: `${s.equipName} MAX -> ${s.nextEquipName} +0`,
27
+ equipId: s.equipId,
28
+ nextEquipId: s.nextEquipId,
29
+ routeKind: s.route_kind_required || null,
30
+ };
31
+ });
32
+ }
33
+
34
+ function calcTotals(steps) {
35
+ const totals = {
36
+ dev: 0,
37
+ screw: 0,
38
+ equipments: {},
39
+ materials: {},
40
+ };
41
+
42
+ for (const s of steps || []) {
43
+ const calcCost = s && s.calcCost ? s.calcCost : null;
44
+ if (!calcCost) continue;
45
+ totals.dev += toInt(calcCost.dev, 0);
46
+ totals.screw += toInt(calcCost.screw, 0);
47
+
48
+ for (const it of calcCost.items || []) {
49
+ if (!it || !it.kind) continue;
50
+ const amount = toInt(it.count != null ? it.count : it.need, 0);
51
+ if (amount <= 0) continue;
52
+
53
+ if (it.kind === "equipment") {
54
+ const k = String(it.id);
55
+ totals.equipments[k] = (totals.equipments[k] || 0) + amount;
56
+ } else if (it.kind === "material") {
57
+ const k = String(it.key);
58
+ totals.materials[k] = (totals.materials[k] || 0) + amount;
59
+ }
60
+ }
61
+ }
62
+
63
+ return totals;
64
+ }
65
+
66
+ function calcRemainingPlan({
67
+ staticData,
68
+ state,
69
+ apiId,
70
+ targetEquipId,
71
+ targetLevel,
72
+ consumeIndexes,
73
+ upgradeIndexes,
74
+ pathByPair,
75
+ masterEquipsById,
76
+ masterUseitemsById,
77
+ playerEquip,
78
+ playerHelpers,
79
+ }) {
80
+ const current = playerEquip || null;
81
+ if (!current) return { ok: false, error: "playerEquip is required" };
82
+
83
+ const fromEquipId = toInt(current.api_slotitem_id, 0);
84
+ const fromStar = Math.max(0, toInt(current.api_level, 0));
85
+ const toEquipId = toInt(targetEquipId, 0);
86
+ const toStar = Math.max(0, Math.min(10, toInt(targetLevel, 10)));
87
+ if (!fromEquipId || !toEquipId) {
88
+ return { ok: false, error: "invalid input equip ids" };
89
+ }
90
+
91
+ const routePlan = calcRoutePlan({
92
+ fromEquipId,
93
+ toEquipId,
94
+ pathByPair,
95
+ upgradeIndexes,
96
+ });
97
+ if (!routePlan.ok) return { ok: false, error: routePlan.error || "route not found" };
98
+
99
+ const routeEquipIds = routePlan.routeEquipIds || [];
100
+ const transitions = routePlan.transitions || [];
101
+
102
+ const steps = [];
103
+ for (let i = 0; i < routeEquipIds.length; i += 1) {
104
+ const equipId = toInt(routeEquipIds[i], 0);
105
+ if (!equipId) continue;
106
+
107
+ const isLastEquip = i === routeEquipIds.length - 1;
108
+ const improveFromStar = i === 0 ? fromStar : 0;
109
+ const improveToStar = isLastEquip ? toStar : 10;
110
+
111
+ if (improveToStar > improveFromStar) {
112
+ steps.push(
113
+ ...calcImproveSteps({
114
+ equipId,
115
+ fromStar: improveFromStar,
116
+ toStar: improveToStar,
117
+ consumeIndexes,
118
+ staticData,
119
+ masterEquipsById,
120
+ })
121
+ );
122
+ }
123
+
124
+ if (!isLastEquip) {
125
+ const tr = transitions[i] || null;
126
+ if (!tr) continue;
127
+ const up = calcUpgradeStep({
128
+ fromEquipId: tr.fromEquipId,
129
+ toEquipId: tr.toEquipId,
130
+ routeKind: tr.routeKind,
131
+ upgradeIndexes,
132
+ staticData,
133
+ masterEquipsById,
134
+ });
135
+ if (up) steps.push(up);
136
+ }
137
+ }
138
+
139
+ const totals = calcTotals(steps);
140
+ const shortage = summarizeShortage({
141
+ totals,
142
+ state,
143
+ staticData,
144
+ masterEquipsById: masterEquipsById || {},
145
+ masterUseitemsById: masterUseitemsById || {},
146
+ playerHelpers: playerHelpers || {},
147
+ });
148
+
149
+ return {
150
+ ok: true,
151
+ inputs: {
152
+ apiId: toInt(apiId, 0),
153
+ fromEquipId,
154
+ fromStar,
155
+ toEquipId,
156
+ toStar,
157
+ },
158
+ route: routeEquipIds,
159
+ transitions,
160
+ steps,
161
+ displaySteps: buildDisplaySteps(steps),
162
+ totals,
163
+ shortage,
164
+ };
165
+ }
166
+
167
+ module.exports = {
168
+ calcRemainingPlan,
169
+ };
@@ -0,0 +1,85 @@
1
+ /* src/services/planner/calcRoutePlan.js */
2
+
3
+ const { toInt } = require("../utils/toInt");
4
+ const { buildUpgradePath } = require("./buildUpgradePath");
5
+
6
+ function findUpgradeRow(upgradeIndexes, fromEquipId, toEquipId) {
7
+ if (!upgradeIndexes) return null;
8
+ const key = `${Number(fromEquipId)}|${Number(toEquipId)}`;
9
+ const rows = upgradeIndexes.targetByKey ? (upgradeIndexes.targetByKey[key] || []) : [];
10
+ if (!rows.length) return null;
11
+ return rows[0] || null; // confirmed by product rule: A->B only one route_kind
12
+ }
13
+
14
+ function buildTransitions(routeEquipIds, upgradeIndexes) {
15
+ const transitions = [];
16
+ for (let i = 0; i < routeEquipIds.length - 1; i += 1) {
17
+ const fromEquipId = toInt(routeEquipIds[i], 0);
18
+ const toEquipId = toInt(routeEquipIds[i + 1], 0);
19
+ if (!fromEquipId || !toEquipId) continue;
20
+
21
+ const row = findUpgradeRow(upgradeIndexes, fromEquipId, toEquipId);
22
+ transitions.push({
23
+ fromEquipId,
24
+ toEquipId,
25
+ routeKind: row && row.route_kind != null ? String(row.route_kind) : null,
26
+ upgradeId: row && row.upgrade_id != null ? Number(row.upgrade_id) : toEquipId,
27
+ });
28
+ }
29
+ return transitions;
30
+ }
31
+
32
+ function calcRoutePlan({ fromEquipId, toEquipId, pathByPair, upgradeIndexes }) {
33
+ const fromId = toInt(fromEquipId, 0);
34
+ const toId = toInt(toEquipId, 0);
35
+ if (!fromId || !toId) return { ok: false, error: "invalid route input", routeEquipIds: [], transitions: [] };
36
+
37
+ if (fromId === toId) {
38
+ return {
39
+ ok: true,
40
+ routeEquipIds: [fromId],
41
+ transitions: [],
42
+ };
43
+ }
44
+
45
+ const pairKey = `${fromId}|${toId}`;
46
+ const path = pathByPair && Array.isArray(pathByPair[pairKey]) ? pathByPair[pairKey] : null;
47
+ if (path && path.length >= 2) {
48
+ return {
49
+ ok: true,
50
+ routeEquipIds: path.map((x) => Number(x)),
51
+ transitions: buildTransitions(path, upgradeIndexes),
52
+ };
53
+ }
54
+
55
+ const fallback = buildUpgradePath({
56
+ equipId: fromId,
57
+ targetEquipId: toId,
58
+ upgradeIndexes,
59
+ });
60
+ const steps = (fallback && fallback.steps) || [];
61
+ if (!steps.length) {
62
+ return {
63
+ ok: false,
64
+ error: `no upgrade route from ${fromId} to ${toId}`,
65
+ routeEquipIds: [],
66
+ transitions: [],
67
+ };
68
+ }
69
+
70
+ const routeEquipIds = [fromId];
71
+ for (const s of steps) {
72
+ const nextId = toInt(s.toEquipId, 0);
73
+ if (nextId) routeEquipIds.push(nextId);
74
+ }
75
+
76
+ return {
77
+ ok: true,
78
+ routeEquipIds,
79
+ transitions: buildTransitions(routeEquipIds, upgradeIndexes),
80
+ };
81
+ }
82
+
83
+ module.exports = {
84
+ calcRoutePlan,
85
+ };
@@ -0,0 +1,85 @@
1
+ /* src/services/planner/calcUpgradeStep.js */
2
+
3
+ const { toInt } = require("../utils/toInt");
4
+
5
+ function getEquipName(masterEquipsById, equipId) {
6
+ const row = masterEquipsById && masterEquipsById[String(equipId)];
7
+ if (row && row.api_name) return String(row.api_name);
8
+ return `UnknownEquip(${equipId})`;
9
+ }
10
+
11
+ function pickUpgradeRow(rows, routeKind) {
12
+ if (!rows || !rows.length) return null;
13
+ if (!routeKind) return rows[0];
14
+ return rows.find((r) => String(r.route_kind || "") === String(routeKind)) || rows[0];
15
+ }
16
+
17
+ function calcUpgradeStep({
18
+ fromEquipId,
19
+ toEquipId,
20
+ routeKind,
21
+ upgradeIndexes,
22
+ staticData,
23
+ masterEquipsById,
24
+ }) {
25
+ const fromId = toInt(fromEquipId, 0);
26
+ const toId = toInt(toEquipId, 0);
27
+ if (!fromId || !toId) return null;
28
+
29
+ const key = `${fromId}|${toId}`;
30
+ const targetRows = upgradeIndexes && upgradeIndexes.targetByKey ? (upgradeIndexes.targetByKey[key] || []) : [];
31
+ const costRows = upgradeIndexes && upgradeIndexes.costByKey ? (upgradeIndexes.costByKey[key] || []) : [];
32
+ if (!targetRows.length) return null;
33
+
34
+ const targetRow = pickUpgradeRow(targetRows, routeKind);
35
+ if (!targetRow) return null;
36
+
37
+ const items = [];
38
+ for (const r of costRows) {
39
+ if (!r) continue;
40
+ const count = toInt(r.count, 0);
41
+ if (count <= 0) continue;
42
+
43
+ if (r.item_equipment_id != null) {
44
+ const id = toInt(r.item_equipment_id, 0);
45
+ if (!id) continue;
46
+ items.push({
47
+ kind: "equipment",
48
+ id,
49
+ name: getEquipName(masterEquipsById, id),
50
+ count,
51
+ });
52
+ continue;
53
+ }
54
+
55
+ if (r.item_material_key != null) {
56
+ const key2 = String(r.item_material_key);
57
+ const m = staticData.materialByKey ? staticData.materialByKey[key2] : null;
58
+ items.push({
59
+ kind: "material",
60
+ key: key2,
61
+ name: m && m.name ? String(m.name) : key2,
62
+ count,
63
+ });
64
+ }
65
+ }
66
+
67
+ return {
68
+ kind: "upgrade",
69
+ equipId: fromId,
70
+ equipName: getEquipName(masterEquipsById, fromId),
71
+ nextEquipId: toId,
72
+ nextEquipName: getEquipName(masterEquipsById, toId),
73
+ route_kind_required: targetRow.route_kind == null ? null : String(targetRow.route_kind),
74
+ calcCost: {
75
+ dev: toInt(targetRow.consume_development_max, 0),
76
+ screw: toInt(targetRow.consume_improvement_max, 0),
77
+ items,
78
+ policy: "upgrade_max",
79
+ },
80
+ };
81
+ }
82
+
83
+ module.exports = {
84
+ calcUpgradeStep,
85
+ };
@@ -0,0 +1,57 @@
1
+ /* src/services/planner/detectCurrentPosition.js */
2
+
3
+ function toInt(x) {
4
+ const n = Number(x);
5
+ return Number.isFinite(n) ? n : null;
6
+ }
7
+
8
+ function detectCurrentPosition({ state, plan }) {
9
+ const apiId = plan && plan.startApiId != null ? String(plan.startApiId) : "";
10
+ if (!apiId) {
11
+ return {
12
+ isValid: false,
13
+ reason: "missing_start_api_id",
14
+ apiId: "",
15
+ currentEquipId: null,
16
+ currentLevel: null,
17
+ reachedTargetEquip: false,
18
+ reachedTargetLevel: false,
19
+ };
20
+ }
21
+
22
+ const equips = state && state.info && state.info.equips;
23
+ const current = equips ? equips[apiId] : null;
24
+ if (!current) {
25
+ return {
26
+ isValid: false,
27
+ reason: "start_equip_not_found",
28
+ apiId,
29
+ currentEquipId: null,
30
+ currentLevel: null,
31
+ reachedTargetEquip: false,
32
+ reachedTargetLevel: false,
33
+ };
34
+ }
35
+
36
+ const currentEquipId = toInt(current.api_slotitem_id);
37
+ const currentLevel = toInt(current.api_level) || 0;
38
+ const targetEquipId = toInt(plan && plan.targetEquipId);
39
+ const targetLevel = toInt(plan && plan.targetLevel) || 0;
40
+
41
+ const reachedTargetEquip = currentEquipId != null && targetEquipId != null && currentEquipId === targetEquipId;
42
+ const reachedTargetLevel = reachedTargetEquip && currentLevel >= targetLevel;
43
+
44
+ return {
45
+ isValid: true,
46
+ reason: null,
47
+ apiId,
48
+ currentEquipId,
49
+ currentLevel,
50
+ reachedTargetEquip,
51
+ reachedTargetLevel,
52
+ };
53
+ }
54
+
55
+ module.exports = {
56
+ detectCurrentPosition,
57
+ };
@@ -0,0 +1,76 @@
1
+ /* src/services/planner/summarizeShortage.js */
2
+
3
+ const { toInt } = require("../utils/toInt");
4
+
5
+ function getEquipName(masterEquipsById, equipId) {
6
+ const row = masterEquipsById && masterEquipsById[String(equipId)];
7
+ if (row && row.api_name) return String(row.api_name);
8
+ return `UnknownEquip(${equipId})`;
9
+ }
10
+
11
+ function summarizeShortage({
12
+ totals,
13
+ state,
14
+ staticData,
15
+ masterEquipsById,
16
+ masterUseitemsById,
17
+ playerHelpers,
18
+ }) {
19
+ const countPlayerEquipByMasterId =
20
+ playerHelpers && typeof playerHelpers.countPlayerEquipByMasterId === "function"
21
+ ? playerHelpers.countPlayerEquipByMasterId
22
+ : () => 0;
23
+ const resolveMaterialKeyToUseitemId =
24
+ playerHelpers && typeof playerHelpers.resolveMaterialKeyToUseitemId === "function"
25
+ ? playerHelpers.resolveMaterialKeyToUseitemId
26
+ : () => null;
27
+ const getPlayerItemCountByUseitemId =
28
+ playerHelpers && typeof playerHelpers.getPlayerItemCountByUseitemId === "function"
29
+ ? playerHelpers.getPlayerItemCountByUseitemId
30
+ : () => 0;
31
+
32
+ const equipments = [];
33
+ for (const [equipId, need] of Object.entries(totals.equipments || {})) {
34
+ const needN = toInt(need, 0);
35
+ const ownedN = toInt(countPlayerEquipByMasterId(state, equipId), 0);
36
+ equipments.push({
37
+ kind: "equipment",
38
+ id: toInt(equipId, 0),
39
+ name: getEquipName(masterEquipsById, equipId),
40
+ need: needN,
41
+ owned: ownedN,
42
+ missing: Math.max(0, needN - ownedN),
43
+ });
44
+ }
45
+
46
+ const materials = [];
47
+ for (const [materialKey, need] of Object.entries(totals.materials || {})) {
48
+ const useitemId = resolveMaterialKeyToUseitemId(
49
+ { materialByKey: staticData.materialByKey, masterUseitemsById },
50
+ materialKey
51
+ );
52
+ const ownedN = useitemId ? toInt(getPlayerItemCountByUseitemId(state, useitemId), 0) : 0;
53
+ const needN = toInt(need, 0);
54
+ const m = staticData.materialByKey ? staticData.materialByKey[String(materialKey)] : null;
55
+ materials.push({
56
+ kind: "material",
57
+ key: String(materialKey),
58
+ name: m && m.name ? String(m.name) : String(materialKey),
59
+ need: needN,
60
+ useitemId: useitemId == null ? null : Number(useitemId),
61
+ owned: ownedN,
62
+ missing: Math.max(0, needN - ownedN),
63
+ });
64
+ }
65
+
66
+ return {
67
+ dev: { need: toInt(totals.dev, 0) },
68
+ screw: { need: toInt(totals.screw, 0) },
69
+ equipments,
70
+ materials,
71
+ };
72
+ }
73
+
74
+ module.exports = {
75
+ summarizeShortage,
76
+ };
@@ -0,0 +1,27 @@
1
+ /* src/services/player/countPlayerEquipByMasterId.js */
2
+
3
+ function toInt(x) {
4
+ const n = Number(x);
5
+ return Number.isFinite(n) ? n : null;
6
+ }
7
+
8
+ /**
9
+ * 统计玩家持有“同图鉴装备(master equip id = api_slotitem_id)”数量
10
+ */
11
+ function countPlayerEquipByMasterId(state, masterEquipId) {
12
+ const equips = state && state.info && state.info.equips;
13
+ if (!equips) return 0;
14
+
15
+ let c = 0;
16
+ const target = toInt(masterEquipId);
17
+ if (target == null) return 0;
18
+
19
+ for (const k of Object.keys(equips)) {
20
+ const e = equips[k];
21
+ if (!e) continue;
22
+ if ((toInt(e.api_slotitem_id) || 0) === target) c += 1;
23
+ }
24
+ return c;
25
+ }
26
+
27
+ module.exports = { countPlayerEquipByMasterId };
@@ -0,0 +1,66 @@
1
+ /* src/services/player/getEquipOwnerShip.js */
2
+
3
+ function toInt(x) {
4
+ const n = Number(x);
5
+ return Number.isFinite(n) ? Math.trunc(n) : 0;
6
+ }
7
+
8
+ function getShipName(masterShipsById, masterShipId) {
9
+ if (!masterShipId) return null;
10
+ const row = masterShipsById && masterShipsById[String(masterShipId)];
11
+ return row && row.api_name ? String(row.api_name) : null;
12
+ }
13
+
14
+ function buildEquipOwnerShipIndex(state) {
15
+ const ships = (state && state.info && state.info.ships) || {};
16
+ const masterShipsById = (state && state.const && state.const.$ships) || {};
17
+ const index = {};
18
+
19
+ for (const k of Object.keys(ships)) {
20
+ const ship = ships[k];
21
+ if (!ship) continue;
22
+
23
+ const shipApiId = toInt(ship.api_id) || toInt(k);
24
+ const masterShipId = toInt(ship.api_ship_id);
25
+ const shipName = getShipName(masterShipsById, masterShipId) || `UnknownShip(${masterShipId || shipApiId})`;
26
+ const slots = Array.isArray(ship.api_slot) ? ship.api_slot : [];
27
+
28
+ for (const slotItem of slots) {
29
+ const equipApiId = toInt(slotItem);
30
+ if (equipApiId <= 0) continue;
31
+ const key = String(equipApiId);
32
+ if (!index[key]) {
33
+ index[key] = {
34
+ shipApiId,
35
+ masterShipId,
36
+ shipName,
37
+ };
38
+ }
39
+ }
40
+ }
41
+
42
+ return index;
43
+ }
44
+
45
+ function getEquipOwnerShipByApiId(equipOwnerShipIndex, equipApiId) {
46
+ if (!equipOwnerShipIndex || equipApiId == null) return null;
47
+ return equipOwnerShipIndex[String(equipApiId)] || null;
48
+ }
49
+
50
+ function buildPlanOwnerShipNameMap(plans, equipOwnerShipIndex) {
51
+ const map = {};
52
+ for (const p of plans || []) {
53
+ if (!p || p.startApiId == null) continue;
54
+ const planId = String(p.id || "");
55
+ if (!planId) continue;
56
+ const owner = getEquipOwnerShipByApiId(equipOwnerShipIndex, p.startApiId);
57
+ map[planId] = owner && owner.shipName ? owner.shipName : "";
58
+ }
59
+ return map;
60
+ }
61
+
62
+ module.exports = {
63
+ buildEquipOwnerShipIndex,
64
+ getEquipOwnerShipByApiId,
65
+ buildPlanOwnerShipNameMap,
66
+ };
@@ -0,0 +1,14 @@
1
+ /* src/services/player/getPlayerData.js */
2
+
3
+ /**
4
+ * poi: store.info.equips is object map, key=player equip api_id
5
+ */
6
+ function getPlayerEquipByApiId(state, apiId) {
7
+ const equips = state && state.info && state.info.equips;
8
+ if (!equips) return null;
9
+ return equips[String(apiId)] || null;
10
+ }
11
+
12
+ module.exports = {
13
+ getPlayerEquipByApiId,
14
+ };