poi-plugin-leveling-plan 0.0.4 → 0.0.5

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/assets/main.css CHANGED
@@ -178,6 +178,71 @@
178
178
  position: relative;
179
179
  }
180
180
 
181
+ #leveling-plan .map-selector-trigger {
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 4px;
185
+ min-height: 32px;
186
+ padding: 2px 4px;
187
+ border: 1px solid #ccc;
188
+ border-radius: 3px;
189
+ cursor: pointer;
190
+ background: #fff;
191
+ }
192
+
193
+ #leveling-plan .map-selector-trigger:hover {
194
+ border-color: #137cbd;
195
+ }
196
+
197
+ .bp5-dark #leveling-plan .map-selector-trigger {
198
+ background: #30404d;
199
+ border-color: #5f6b7c;
200
+ }
201
+
202
+ .bp5-dark #leveling-plan .map-selector-trigger:hover {
203
+ border-color: #137cbd;
204
+ }
205
+
206
+ #leveling-plan .map-selector-trigger-tags {
207
+ display: flex;
208
+ flex-wrap: wrap;
209
+ gap: 4px;
210
+ flex: 1;
211
+ }
212
+
213
+ #leveling-plan .map-selector-trigger-placeholder {
214
+ flex: 1;
215
+ padding: 0 4px;
216
+ color: #aaa;
217
+ font-size: 13px;
218
+ }
219
+
220
+ #leveling-plan .map-selector-popover-header {
221
+ display: flex;
222
+ align-items: center;
223
+ justify-content: space-between;
224
+ padding: 6px 8px;
225
+ border-bottom: 1px solid #e5e5e5;
226
+ }
227
+
228
+ .bp5-dark #leveling-plan .map-selector-popover-header {
229
+ border-bottom-color: #404854;
230
+ }
231
+
232
+ #leveling-plan .map-selector-popover-title {
233
+ font-size: 13px;
234
+ font-weight: 600;
235
+ color: #333;
236
+ }
237
+
238
+ .bp5-dark #leveling-plan .map-selector-popover-title {
239
+ color: #ddd;
240
+ }
241
+
242
+ #leveling-plan .map-selector-popover-search {
243
+ padding: 4px 8px;
244
+ }
245
+
181
246
  #leveling-plan .map-selector-dropdown {
182
247
  max-height: 200px;
183
248
  overflow-y: auto;
@@ -539,3 +604,10 @@
539
604
  object-fit: contain;
540
605
  vertical-align: middle;
541
606
  }
607
+
608
+ /* 养殖计划行分隔线 */
609
+ #leveling-plan .farming-divider {
610
+ height: 0;
611
+ border-top: 1px solid rgba(128, 128, 128, 0.2);
612
+ margin: 2px 0;
613
+ }
package/i18n/en-US.json CHANGED
@@ -52,5 +52,13 @@
52
52
  "Sample Count": "Sample Count",
53
53
  "Currently Used": "Currently Used",
54
54
  "Samples Insufficient": "Samples Insufficient",
55
- "No personal data": "No personal data"
55
+ "No personal data": "No personal data",
56
+ "Farming": "Farming",
57
+ "Add Farming Plan": "Add Farming Plan",
58
+ "Edit Farming Plan": "Edit Farming Plan",
59
+ "Ship Type": "Ship Type",
60
+ "No farming plans yet": "No farming plans yet",
61
+ "Below target": "Below target",
62
+ "ships": "ships",
63
+ "All ships have reached the target level": "All ships have reached the target level"
56
64
  }
package/i18n/ja-JP.json CHANGED
@@ -52,5 +52,13 @@
52
52
  "Sample Count": "サンプル数",
53
53
  "Currently Used": "現在使用中",
54
54
  "Samples Insufficient": "サンプル不足",
55
- "No personal data": "個人データなし"
55
+ "No personal data": "個人データなし",
56
+ "Farming": "養殖",
57
+ "Add Farming Plan": "養殖計画を追加",
58
+ "Edit Farming Plan": "養殖計画を編集",
59
+ "Ship Type": "艦種",
60
+ "No farming plans yet": "養殖計画がありません",
61
+ "Below target": "未達成",
62
+ "ships": "隻",
63
+ "All ships have reached the target level": "全隻目標レベル達成"
56
64
  }
package/i18n/zh-CN.json CHANGED
@@ -52,5 +52,13 @@
52
52
  "Sample Count": "样本数",
53
53
  "Currently Used": "当前使用",
54
54
  "Samples Insufficient": "样本不足",
55
- "No personal data": "无个人数据"
55
+ "No personal data": "无个人数据",
56
+ "Farming": "养殖",
57
+ "Add Farming Plan": "添加养殖计划",
58
+ "Edit Farming Plan": "编辑养殖计划",
59
+ "Ship Type": "船型",
60
+ "No farming plans yet": "暂无养殖计划",
61
+ "Below target": "未达标",
62
+ "ships": "艘",
63
+ "All ships have reached the target level": "所有船只已达标"
56
64
  }
package/i18n/zh-TW.json CHANGED
@@ -52,5 +52,13 @@
52
52
  "Sample Count": "樣本數",
53
53
  "Currently Used": "當前使用",
54
54
  "Samples Insufficient": "樣本不足",
55
- "No personal data": "無個人數據"
55
+ "No personal data": "無個人數據",
56
+ "Farming": "養殖",
57
+ "Add Farming Plan": "新增養殖計劃",
58
+ "Edit Farming Plan": "編輯養殖計劃",
59
+ "Ship Type": "艦種",
60
+ "No farming plans yet": "暫無養殖計劃",
61
+ "Below target": "未達標",
62
+ "ships": "艘",
63
+ "All ships have reached the target level": "所有船隻已達標"
56
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poi-plugin-leveling-plan",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Leveling plan plugin for poi",
5
5
  "main": "index.js",
6
6
  "keywords": [
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  exports.__esModule = true;
4
- exports.searchOptions = exports.catMap = exports.expClass = exports.bonusExpScaleNonFlagship = exports.bonusExpScaleFlagship = exports.expPercent = exports.expLevel = exports.shipCat = exports.frequentMaps = exports.EXP_BY_POI_DB = exports.MAX_LEVEL = exports.exp = void 0;
4
+ exports.searchOptions = exports.catMap = exports.expClass = exports.bonusExpScaleNonFlagship = exports.bonusExpScaleFlagship = exports.expPercent = exports.expLevel = exports.shipCat = exports.frequentMaps = exports.WORLD_NAMES = exports.EXP_BY_POI_DB = exports.MAX_LEVEL = exports.exp = void 0;
5
5
  // 等级经验表(1-185级)
6
6
  // 数据来源:poi-plugin-exp-calc
7
7
  const exp = {
@@ -229,10 +229,21 @@ const EXP_BY_POI_DB = {
229
229
  64: 236,
230
230
  65: 242,
231
231
  71: 220,
232
- 72: 213 // 常用练级海图
232
+ 72: 213 // 世界名称映射
233
233
 
234
234
  };
235
235
  exports.EXP_BY_POI_DB = EXP_BY_POI_DB;
236
+ const WORLD_NAMES = {
237
+ 1: '鎮守府海域',
238
+ 2: '南西諸島海域',
239
+ 3: '北方海域',
240
+ 4: '西方海域',
241
+ 5: '南方海域',
242
+ 6: '中部海域',
243
+ 7: '南西海域' // 常用练级海图
244
+
245
+ };
246
+ exports.WORLD_NAMES = WORLD_NAMES;
236
247
  const frequentMaps = [15, 21, 22, 42, 52, 53, 71]; // 舰种分类
237
248
 
238
249
  exports.frequentMaps = frequentMaps;
@@ -28,8 +28,9 @@ exports.generatePlanId = generatePlanId;
28
28
 
29
29
  const validatePlan = (plan, ship = null) => {
30
30
  const errors = [];
31
+ const isFarming = plan.type === 'farming';
31
32
 
32
- if (!plan.shipId) {
33
+ if (!isFarming && !plan.shipId) {
33
34
  errors.push('shipId is required');
34
35
  }
35
36
 
@@ -41,7 +42,7 @@ const validatePlan = (plan, ship = null) => {
41
42
  errors.push('targetLevel must be between 1 and 185');
42
43
  }
43
44
 
44
- if (ship && plan.targetLevel <= ship.api_lv) {
45
+ if (!isFarming && ship && plan.targetLevel <= ship.api_lv) {
45
46
  errors.push('targetLevel must be greater than current level');
46
47
  }
47
48
 
@@ -171,12 +172,7 @@ const shouldAutoComplete = (plan, ship) => {
171
172
  exports.shouldAutoComplete = shouldAutoComplete;
172
173
 
173
174
  const formatMapName = mapId => {
174
- if (!mapId || typeof mapId !== 'string') return ''; // '53' -> '5-3'
175
-
176
- if (mapId.length === 2) {
177
- return `${mapId[0]}-${mapId[1]}`;
178
- } // '11' -> '1-1'
179
-
175
+ if (!mapId || typeof mapId !== 'string') return '';
180
176
 
181
177
  if (mapId.length === 2) {
182
178
  return `${mapId[0]}-${mapId[1]}`;
@@ -197,21 +193,29 @@ const formatMapName = mapId => {
197
193
 
198
194
  exports.formatMapName = formatMapName;
199
195
 
200
- const createPlan = (shipId, shipMasterId, startLevel, targetLevel, maps, notes = '') => {
196
+ const createPlan = (shipId, shipMasterId, startLevel, targetLevel, maps, notes = '', type = 'normal') => {
201
197
  const now = Date.now();
202
- return {
198
+ const base = {
203
199
  id: generatePlanId(),
204
- shipId,
205
200
  shipMasterId,
206
- startLevel,
207
201
  targetLevel,
208
202
  maps,
209
203
  notes,
210
- completed: false,
211
- completedAt: null,
204
+ type,
212
205
  createdAt: now,
213
206
  updatedAt: now
214
207
  };
208
+
209
+ if (type === 'farming') {
210
+ return base;
211
+ }
212
+
213
+ return _extends({}, base, {
214
+ shipId,
215
+ startLevel,
216
+ completed: false,
217
+ completedAt: null
218
+ });
215
219
  };
216
220
  /**
217
221
  * 更新计划对象
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  exports.__esModule = true;
4
- exports.shipMenuDataSelector = exports.completedPlanDetailsSelector = exports.activePlanDetailsSelector = exports.allPlanDetailsSelector = exports.planByShipIdSelectorFactory = exports.completedPlansSelector = exports.activePlansSelector = exports.plansArraySelector = exports.planSettingsSelector = exports.plansSelector = exports.personalStatsSelector = exports.calcSortieCount = exports.calcExpToLevel = exports.shipsByTypeSelector = exports.shipsByIdSelector = exports.shipsWithDetailsSelector = exports.mapsWithExpSelector = exports.mapExpSelector = exports.expTableSelector = exports.remodelLevelSelector = exports.adjustedRemodelChainsSelector = exports.shipUniqueMapSelector = exports.uniqueShipIdsSelector = exports.materialsSelector = exports.resourcesSelector = exports.ourShipsSelector = exports.$mapsSelector = exports.$shipsSelector = void 0;
4
+ exports.farmingPlanDetailsSelector = exports.farmingPlansSelector = exports.farmingShipLookupSelector = exports.masterShipMenuDataSelector = exports.shipMenuDataSelector = exports.completedPlanDetailsSelector = exports.activePlanDetailsSelector = exports.allPlanDetailsSelector = exports.planByShipIdSelectorFactory = exports.completedPlansSelector = exports.activePlansSelector = exports.normalPlansArraySelector = exports.plansArraySelector = exports.planSettingsSelector = exports.plansSelector = exports.personalStatsSelector = exports.calcSortieCount = exports.calcExpToLevel = exports.shipsByTypeSelector = exports.shipsByIdSelector = exports.shipsWithDetailsSelector = exports.mapsWithExpSelector = exports.mapExpSelector = exports.expTableSelector = exports.remodelLevelSelector = exports.adjustedRemodelChainsSelector = exports.shipUniqueMapSelector = exports.uniqueShipIdsSelector = exports.materialsSelector = exports.resourcesSelector = exports.ourShipsSelector = exports.$mapsSelector = exports.$shipsSelector = void 0;
5
5
 
6
6
  var _reselect = require("reselect");
7
7
 
@@ -219,13 +219,16 @@ const planSettingsSelector = (0, _reselect.createSelector)([state => state.confi
219
219
  })); // 所有计划(数组,按创建时间倒序)
220
220
 
221
221
  exports.planSettingsSelector = planSettingsSelector;
222
- const plansArraySelector = (0, _reselect.createSelector)([plansSelector], plans => (0, _lodash.default)(plans).values().orderBy(['createdAt'], ['desc']).value()); // 未完成的计划
222
+ const plansArraySelector = (0, _reselect.createSelector)([plansSelector], plans => (0, _lodash.default)(plans).values().orderBy(['createdAt'], ['desc']).value()); // 所有正常计划(非养殖)
223
223
 
224
224
  exports.plansArraySelector = plansArraySelector;
225
- const activePlansSelector = (0, _reselect.createSelector)([plansArraySelector], plans => plans.filter(plan => !plan.completed)); // 已完成的计划
225
+ const normalPlansArraySelector = (0, _reselect.createSelector)([plansArraySelector], plans => plans.filter(plan => plan.type !== 'farming')); // 未完成的计划
226
+
227
+ exports.normalPlansArraySelector = normalPlansArraySelector;
228
+ const activePlansSelector = (0, _reselect.createSelector)([normalPlansArraySelector], plans => plans.filter(plan => !plan.completed)); // 已完成的计划
226
229
 
227
230
  exports.activePlansSelector = activePlansSelector;
228
- const completedPlansSelector = (0, _reselect.createSelector)([plansArraySelector], plans => plans.filter(plan => plan.completed)); // // 根据计划ID获取计划详情(带计算数据)
231
+ const completedPlansSelector = (0, _reselect.createSelector)([normalPlansArraySelector], plans => plans.filter(plan => plan.completed)); // // 根据计划ID获取计划详情(带计算数据)
229
232
  // export const planDetailSelectorFactory = planId => createSelector(
230
233
  // [plansSelector, ourShipsSelector, $shipsSelector, personalStatsSelector, planSettingsSelector],
231
234
  // (plans, ships, $ships, personalStats, settings) => {
@@ -248,7 +251,7 @@ const planByShipIdSelectorFactory = shipId => (0, _reselect.createSelector)([pla
248
251
 
249
252
 
250
253
  exports.planByShipIdSelectorFactory = planByShipIdSelectorFactory;
251
- const allPlanDetailsSelector = (0, _reselect.createSelector)([plansArraySelector, ourShipsSelector, $shipsSelector, personalStatsSelector, planSettingsSelector], (plans, ships, $ships, personalStats, settings) => {
254
+ const allPlanDetailsSelector = (0, _reselect.createSelector)([normalPlansArraySelector, ourShipsSelector, $shipsSelector, personalStatsSelector, planSettingsSelector], (plans, ships, $ships, personalStats, settings) => {
252
255
  return plans.map(plan => {
253
256
  try {
254
257
  const ship = _lodash.default.find(ships, s => s.api_id === plan.shipId);
@@ -297,5 +300,101 @@ const shipMenuDataSelector = (0, _reselect.createSelector)([ourShipsSelector, $s
297
300
  api_stype: $ship.api_stype
298
301
  };
299
302
  }).filter(Boolean).value();
303
+ }); // ============ 8. 养殖计划 Selectors ============
304
+ // 船型选择器数据(用于养殖模式的舰船选择器)
305
+
306
+ exports.shipMenuDataSelector = shipMenuDataSelector;
307
+ const masterShipMenuDataSelector = (0, _reselect.createSelector)([uniqueShipIdsSelector, $shipsSelector], (uniqueIds, $ships) => {
308
+ return (0, _lodash.default)(uniqueIds).map(id => {
309
+ const $ship = $ships[id];
310
+ if (!$ship) return null;
311
+ const stype = Number($ship.api_stype);
312
+ if (!stype || stype > 22) return null;
313
+ return {
314
+ api_id: id,
315
+ api_name: $ship.api_name,
316
+ api_yomi: $ship.api_yomi || $ship.api_name,
317
+ api_stype: stype
318
+ };
319
+ }).filter(Boolean).sortBy(['api_name']).value();
320
+ }); // 舰船 masterId 到改造链起点的映射缓存
321
+
322
+ exports.masterShipMenuDataSelector = masterShipMenuDataSelector;
323
+ const farmingShipLookupSelector = (0, _reselect.createSelector)([shipUniqueMapSelector], shipUniqueMap => shipUniqueMap); // 养殖计划(type === 'farming')
324
+
325
+ exports.farmingShipLookupSelector = farmingShipLookupSelector;
326
+ const farmingPlansSelector = (0, _reselect.createSelector)([plansSelector], plans => (0, _lodash.default)(plans).filter(plan => plan.type === 'farming').values().orderBy(['createdAt'], ['desc']).value()); // 养殖计划详情
327
+
328
+ exports.farmingPlansSelector = farmingPlansSelector;
329
+ const farmingPlanDetailsSelector = (0, _reselect.createSelector)([farmingPlansSelector, ourShipsSelector, $shipsSelector, personalStatsSelector, planSettingsSelector, shipUniqueMapSelector], (plans, ourShips, $ships, personalStats, settings, shipUniqueMap) => {
330
+ const {
331
+ defaultRank = 0,
332
+ defaultIsFlagship = true,
333
+ defaultIsMVP = false
334
+ } = settings;
335
+ return plans.map(plan => {
336
+ try {
337
+ const targetLv = plan.targetLevel;
338
+ const shipUniqueId = plan.shipMasterId;
339
+ const $baseShip = $ships[shipUniqueId];
340
+ if (!$baseShip) return null; // 筛选该改造链下所有低于目标等级的实例
341
+
342
+ const instances = (0, _lodash.default)(ourShips).map(ship => {
343
+ var _ship$api_exp2;
344
+
345
+ const instanceUniqueId = shipUniqueMap[ship.api_ship_id];
346
+ if (instanceUniqueId !== shipUniqueId) return null;
347
+ if (ship.api_lv >= targetLv) return null;
348
+ const currentLv = ship.api_lv;
349
+ const currentExp = ((_ship$api_exp2 = ship.api_exp) === null || _ship$api_exp2 === void 0 ? void 0 : _ship$api_exp2[0]) || 0;
350
+ const targetTotalExp = _constants.exp[targetLv] || 0;
351
+ const startExp = 0;
352
+ const requiredExp = targetTotalExp - currentExp;
353
+ const progress = targetTotalExp > 0 ? Math.min(100.0, 100.0 * (currentExp / targetTotalExp)) : 0;
354
+ const mapDetails = (plan.maps || []).map(mapId => {
355
+ const mapExpData = (0, _expCalculator.getMapExp)(mapId, personalStats, 30);
356
+ const expPerSortie = (0, _expCalculator.calcBattleExp)(mapExpData.exp, defaultRank, defaultIsFlagship, defaultIsMVP);
357
+ const sortiesNeeded = (0, _expCalculator.calcSortiesNeeded)(requiredExp, mapExpData.exp, defaultRank, defaultIsFlagship, defaultIsMVP);
358
+ return {
359
+ mapId,
360
+ mapName: (0, _planHelpers.formatMapName)(mapId),
361
+ mapExp: mapExpData.exp,
362
+ mapExpSource: mapExpData.source,
363
+ mapExpCount: mapExpData.count,
364
+ expPerSortie,
365
+ sortiesNeeded
366
+ };
367
+ });
368
+ return {
369
+ shipId: ship.api_id,
370
+ currentLv,
371
+ currentExp,
372
+ requiredExp,
373
+ progress,
374
+ mapDetails
375
+ };
376
+ }).filter(Boolean).orderBy(['currentLv'], ['desc']).value();
377
+ const totalInstancesOwned = (0, _lodash.default)(ourShips).filter(s => shipUniqueMap[s.api_ship_id] === shipUniqueId).size();
378
+ const totalInstancesBelowTarget = instances.length;
379
+ return {
380
+ id: plan.id,
381
+ type: 'farming',
382
+ shipMasterId: shipUniqueId,
383
+ shipName: $baseShip.api_name,
384
+ $baseShip,
385
+ targetLv,
386
+ maps: plan.maps,
387
+ notes: plan.notes || '',
388
+ instances,
389
+ totalInstancesOwned,
390
+ totalInstancesBelowTarget,
391
+ createdAt: plan.createdAt,
392
+ updatedAt: plan.updatedAt
393
+ };
394
+ } catch (error) {
395
+ console.error('[LevelingPlan] Error calculating farming plan detail:', plan.id, error);
396
+ return null;
397
+ }
398
+ }).filter(Boolean);
300
399
  });
301
- exports.shipMenuDataSelector = shipMenuDataSelector;
400
+ exports.farmingPlanDetailsSelector = farmingPlanDetailsSelector;
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.default = void 0;
5
+
6
+ var _react = _interopRequireWildcard(require("react"));
7
+
8
+ var _reactRedux = require("react-redux");
9
+
10
+ var _planItem = require("./plan-item");
11
+
12
+ var _selectors = require("../../utils/selectors");
13
+
14
+ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
15
+
16
+ const {
17
+ __
18
+ } = window.i18n['poi-plugin-leveling-plan'];
19
+
20
+ class FarmingPlanList extends _react.Component {
21
+ render() {
22
+ const {
23
+ plans,
24
+ onEdit,
25
+ onDelete
26
+ } = this.props;
27
+ return _react.default.createElement("div", {
28
+ className: "plan-list"
29
+ }, _react.default.createElement("div", {
30
+ className: "plan-list-content"
31
+ }, plans.length === 0 ? _react.default.createElement("div", {
32
+ className: "empty-message"
33
+ }, __('No farming plans yet')) : plans.map(plan => _react.default.createElement(_planItem.FarmingPlanItem, {
34
+ key: plan.id,
35
+ planDetail: plan,
36
+ onEdit: () => onEdit(plan.id),
37
+ onDelete: () => onDelete(plan.id)
38
+ }))));
39
+ }
40
+
41
+ }
42
+
43
+ const mapStateToProps = state => ({
44
+ plans: (0, _selectors.farmingPlanDetailsSelector)(state)
45
+ });
46
+
47
+ var _default = (0, _reactRedux.connect)(mapStateToProps)(FarmingPlanList);
48
+
49
+ exports.default = _default;
50
+ module.exports = exports["default"];