sekai-calculator 0.8.0 → 0.8.3

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/dist/index.cjs CHANGED
@@ -77,6 +77,37 @@ class CachedDataProvider {
77
77
  async getUserDataAll() {
78
78
  return await this.getData(this.instanceCache, 'userData', async () => await this.dataProvider.getUserDataAll());
79
79
  }
80
+ async preloadMasterData(keys) {
81
+ return await Promise.all(keys.map(async (it) => await this.getMasterData(it)));
82
+ }
83
+ }
84
+
85
+ class AreaItemService {
86
+ dataProvider;
87
+ constructor(dataProvider) {
88
+ this.dataProvider = dataProvider;
89
+ }
90
+ async getAreaItemLevels() {
91
+ const userAreas = await this.dataProvider.getUserData('userAreas');
92
+ return await Promise.all(userAreas.flatMap(it => it.areaItems)
93
+ .map(async (it) => await this.getAreaItemLevel(it.areaItemId, it.level)));
94
+ }
95
+ async getAreaItemLevel(areaItemId, level) {
96
+ const areaItemLevels = await this.dataProvider.getMasterData('areaItemLevels');
97
+ return findOrThrow(areaItemLevels, it => it.areaItemId === areaItemId && it.level === level);
98
+ }
99
+ async getAreaItemNextLevel(areaItem, areaItemLevel) {
100
+ const level = areaItemLevel === undefined ? 1 : (areaItemLevel.level === 15 ? 15 : areaItemLevel.level + 1);
101
+ return await this.getAreaItemLevel(areaItem.id, level);
102
+ }
103
+ async getShopItem(areaItemLevel) {
104
+ const shopItems = await this.dataProvider.getMasterData('shopItems');
105
+ const idOffset = areaItemLevel.level <= 10
106
+ ? (1000 + (areaItemLevel.areaItemId - 1) * 10)
107
+ : (1550 - 10 + (areaItemLevel.areaItemId - 1) * 5);
108
+ const id = idOffset + areaItemLevel.level;
109
+ return findOrThrow(shopItems, it => it.id === id);
110
+ }
80
111
  }
81
112
 
82
113
  class DeckService {
@@ -189,8 +220,12 @@ class CardPowerCalculator {
189
220
  return basePower.reduce((v, it) => v + it, 0) + characterBonus + await this.getAreaItemBonusPower(userAreaItemLevels, basePower, card.characterId, unit, sameUnit, card.attr, sameAttr);
190
221
  }
191
222
  async getCardBasePowers(userCard, card) {
192
- const cardEpisodes = await this.dataProvider.getMasterData('cardEpisodes');
193
- const masterLessons = await this.dataProvider.getMasterData('masterLessons');
223
+ const p = await Promise.all([
224
+ await this.dataProvider.getMasterData('cardEpisodes'),
225
+ await this.dataProvider.getMasterData('masterLessons')
226
+ ]);
227
+ const cardEpisodes = p[0];
228
+ const masterLessons = p[1];
194
229
  const ret = [0, 0, 0];
195
230
  const cardParameters = card.cardParameters.filter(it => it.cardLevel === userCard.level);
196
231
  const params = ['param1', 'param2', 'param3'];
@@ -330,11 +365,13 @@ class CardCalculator {
330
365
  powerCalculator;
331
366
  skillCalculator;
332
367
  eventCalculator;
368
+ areaItemService;
333
369
  constructor(dataProvider) {
334
370
  this.dataProvider = dataProvider;
335
371
  this.powerCalculator = new CardPowerCalculator(dataProvider);
336
372
  this.skillCalculator = new CardSkillCalculator(dataProvider);
337
373
  this.eventCalculator = new CardEventCalculator(dataProvider);
374
+ this.areaItemService = new AreaItemService(dataProvider);
338
375
  }
339
376
  async getCardUnits(card) {
340
377
  const gameCharacters = await this.dataProvider.getMasterData('gameCharacters');
@@ -360,7 +397,9 @@ class CardCalculator {
360
397
  }
361
398
  }
362
399
  if (episodeRead && ret.episodes !== undefined) {
363
- ret.episodes.forEach(it => { it.scenarioStatus = 'already_read'; });
400
+ ret.episodes.forEach(it => {
401
+ it.scenarioStatus = 'already_read';
402
+ });
364
403
  }
365
404
  if (masterMax) {
366
405
  ret.masterRank = 5;
@@ -393,11 +432,11 @@ class CardCalculator {
393
432
  eventBonus
394
433
  };
395
434
  }
396
- async batchGetCardDetail(userCards, config = {}, eventId = 0) {
397
- const areaItemLevels = await this.dataProvider.getMasterData('areaItemLevels');
398
- const userAreas = await this.dataProvider.getUserData('userAreas');
399
- const userItemLevels = userAreas.flatMap(it => it.areaItems).map(areaItem => findOrThrow(areaItemLevels, it => it.areaItemId === areaItem.areaItemId && it.level === areaItem.level));
400
- return await Promise.all(userCards.map(async (it) => await this.getCardDetail(it, userItemLevels, config, eventId)));
435
+ async batchGetCardDetail(userCards, config = {}, eventId = 0, areaItemLevels) {
436
+ const areaItemLevels0 = areaItemLevels === undefined
437
+ ? await this.areaItemService.getAreaItemLevels()
438
+ : areaItemLevels;
439
+ return await Promise.all(userCards.map(async (it) => await this.getCardDetail(it, areaItemLevels0, config, eventId)));
401
440
  }
402
441
  static isCertainlyLessThan(cardDetail0, cardDetail1) {
403
442
  return cardDetail0.power.isCertainlyLessThen(cardDetail1.power) &&
@@ -442,21 +481,47 @@ class LiveCalculator {
442
481
  return musicMeta.skill_score_auto;
443
482
  }
444
483
  }
484
+ static getSortedSkillDetails(deckDetail, liveType, skillDetails = undefined) {
485
+ if (skillDetails !== undefined && skillDetails.length === 6 && skillDetails[5].scoreUp > 0) {
486
+ return {
487
+ details: skillDetails,
488
+ sorted: false
489
+ };
490
+ }
491
+ if (liveType === exports.LiveType.MULTI) {
492
+ return {
493
+ details: duplicateObj(LiveCalculator.getMultiLiveSkill(deckDetail), 6),
494
+ sorted: false
495
+ };
496
+ }
497
+ const sortedSkill = [...deckDetail.cards].map(it => it.skill)
498
+ .sort((a, b) => a.scoreUp - b.scoreUp);
499
+ const emptySkill = duplicateObj({
500
+ scoreUp: 0,
501
+ lifeRecovery: 0
502
+ }, 5 - sortedSkill.length);
503
+ return {
504
+ details: [...sortedSkill, ...emptySkill, deckDetail.cards[0].skill],
505
+ sorted: true
506
+ };
507
+ }
508
+ static getSortedSkillRate(sorted, cardLength, skillScores) {
509
+ if (!sorted) {
510
+ return skillScores;
511
+ }
512
+ return [
513
+ ...skillScores.slice(0, cardLength).sort((a, b) => a - b),
514
+ ...skillScores.slice(cardLength)
515
+ ];
516
+ }
445
517
  static getLiveDetailByDeck(deckDetail, musicMeta, liveType, skillDetails = undefined, multiPowerSum = 0) {
446
- const skills = skillDetails !== undefined
447
- ? skillDetails
448
- : (liveType === exports.LiveType.MULTI
449
- ? duplicateObj(LiveCalculator.getMultiLiveSkill(deckDetail), 6)
450
- : [...[...deckDetail.cards].map(it => it.skill)
451
- .sort((a, b) => a.scoreUp - b.scoreUp),
452
- deckDetail.cards[0].skill]);
518
+ const skills = this.getSortedSkillDetails(deckDetail, liveType, skillDetails);
453
519
  const baseRate = LiveCalculator.getBaseScore(musicMeta, liveType);
454
520
  const skillScores = [...LiveCalculator.getSkillScore(musicMeta, liveType)];
455
- const skillRate = skillDetails === undefined
456
- ? [...skillScores.slice(0, 5).sort((a, b) => a - b), skillScores[5]]
457
- : skillScores;
458
- const rate = baseRate + skills.reduce((v, it, i) => v + it.scoreUp * skillRate[i] / 100, 0);
459
- const life = skills.reduce((v, it) => v + it.lifeRecovery, 0);
521
+ const skillRate = LiveCalculator.getSortedSkillRate(skills.sorted, deckDetail.cards.length, skillScores);
522
+ const rate = baseRate + skills.details
523
+ .reduce((v, it, i) => v + it.scoreUp * skillRate[i] / 100, 0);
524
+ const life = skills.details.reduce((v, it) => v + it.lifeRecovery, 0);
460
525
  const powerSum = multiPowerSum === 0 ? 5 * deckDetail.power : multiPowerSum;
461
526
  const activeBonus = liveType === exports.LiveType.MULTI ? 5 * 0.015 * powerSum : 0;
462
527
  return {
@@ -612,8 +677,56 @@ class DeckCalculator {
612
677
  cards
613
678
  };
614
679
  }
615
- async getDeckDetail(deckCards) {
616
- return DeckCalculator.getDeckDetailByCards(await this.cardCalculator.batchGetCardDetail(deckCards), await this.getHonorBonusPower());
680
+ async getDeckDetail(deckCards, eventId = 0, areaItemLevels) {
681
+ return DeckCalculator.getDeckDetailByCards(await this.cardCalculator.batchGetCardDetail(deckCards, {}, eventId, areaItemLevels), await this.getHonorBonusPower());
682
+ }
683
+ }
684
+
685
+ class AreaItemRecommend {
686
+ dataProvider;
687
+ areaItemService;
688
+ deckCalculator;
689
+ constructor(dataProvider) {
690
+ this.dataProvider = dataProvider;
691
+ this.areaItemService = new AreaItemService(dataProvider);
692
+ this.deckCalculator = new DeckCalculator(dataProvider);
693
+ }
694
+ static findCost(shopItem, resourceType, resourceId) {
695
+ const cost = shopItem.costs.map(it => it.cost)
696
+ .find(it => it.resourceType === resourceType && it.resourceId === resourceId);
697
+ return cost === undefined ? 0 : cost.quantity;
698
+ }
699
+ async getRecommendAreaItem(areaItem, areaItemLevel, power) {
700
+ const areas = await this.dataProvider.getMasterData('areas');
701
+ const area = findOrThrow(areas, it => it.id === areaItem.areaId);
702
+ const shopItem = await this.areaItemService.getShopItem(areaItemLevel);
703
+ return {
704
+ area,
705
+ areaItem,
706
+ areaItemLevel,
707
+ shopItem,
708
+ cost: {
709
+ coin: AreaItemRecommend.findCost(shopItem, 'coin', 0),
710
+ seed: AreaItemRecommend.findCost(shopItem, 'material', 17),
711
+ szk: AreaItemRecommend.findCost(shopItem, 'material', 57)
712
+ },
713
+ power
714
+ };
715
+ }
716
+ async recommendAreaItem(userCards) {
717
+ const areaItems = await this.dataProvider.getMasterData('areaItems');
718
+ const currentAreaItemLevels = await this.areaItemService.getAreaItemLevels();
719
+ const { power: currentPower } = await this.deckCalculator.getDeckDetail(userCards, 0, currentAreaItemLevels);
720
+ const recommend = await Promise.all(areaItems.map(async (areaItem) => {
721
+ const newAreaItemLevel = await this.areaItemService.getAreaItemNextLevel(areaItem, currentAreaItemLevels.find(it => it.areaItemId === areaItem.id));
722
+ const newAreaItemLevels = [
723
+ ...currentAreaItemLevels.filter(it => it.areaItemId !== areaItem.id), newAreaItemLevel
724
+ ];
725
+ const { power: newPower } = await this.deckCalculator.getDeckDetail(userCards, 0, newAreaItemLevels);
726
+ return await this.getRecommendAreaItem(areaItem, newAreaItemLevel, newPower - currentPower);
727
+ }));
728
+ return recommend.filter(it => it.power > 0)
729
+ .sort((a, b) => b.power / b.cost.coin - a.power / a.cost.coin);
617
730
  }
618
731
  }
619
732
 
@@ -763,12 +876,17 @@ class BaseDeckRecommend {
763
876
  dataProvider;
764
877
  cardCalculator;
765
878
  deckCalculator;
879
+ areaItemService;
766
880
  constructor(dataProvider) {
767
881
  this.dataProvider = dataProvider;
768
882
  this.cardCalculator = new CardCalculator(dataProvider);
769
883
  this.deckCalculator = new DeckCalculator(dataProvider);
884
+ this.areaItemService = new AreaItemService(dataProvider);
770
885
  }
771
886
  static findBestCards(cardDetails, scoreFunc, limit = 1, isChallengeLive = false, member = 5, honorBonus = 0, deckCards = [], deckCharacters = []) {
887
+ if (isChallengeLive) {
888
+ member = Math.min(member, cardDetails.length);
889
+ }
772
890
  if (deckCards.length === member) {
773
891
  const score = scoreFunc(deckCards);
774
892
  const deckDetail = DeckCalculator.getDeckDetailByCards(deckCards, honorBonus);
@@ -823,7 +941,8 @@ class BaseDeckRecommend {
823
941
  return ans;
824
942
  }
825
943
  async recommendHighScoreDeck(userCards, scoreFunc, { musicMeta, limit = 1, member = 5, cardConfig = {} }, eventId = 0, isChallengeLive = false) {
826
- const cards = await this.cardCalculator.batchGetCardDetail(userCards, cardConfig, eventId);
944
+ const areaItemLevels = await this.areaItemService.getAreaItemLevels();
945
+ const cards = await this.cardCalculator.batchGetCardDetail(userCards, cardConfig, eventId, areaItemLevels);
827
946
  const honorBonus = await this.deckCalculator.getHonorBonusPower();
828
947
  let priority = (isChallengeLive || eventId === 0) ? 10 : 0;
829
948
  while (priority < 10) {
@@ -909,6 +1028,8 @@ class MusicRecommend {
909
1028
  }
910
1029
  }
911
1030
 
1031
+ exports.AreaItemRecommend = AreaItemRecommend;
1032
+ exports.AreaItemService = AreaItemService;
912
1033
  exports.BaseDeckRecommend = BaseDeckRecommend;
913
1034
  exports.CachedDataProvider = CachedDataProvider;
914
1035
  exports.CardCalculator = CardCalculator;
package/dist/index.d.ts CHANGED
@@ -1,8 +1,23 @@
1
+ interface MusicMeta {
2
+ music_id: number;
3
+ difficulty: string;
4
+ music_time: number;
5
+ event_rate: number;
6
+ base_score: number;
7
+ base_score_auto: number;
8
+ skill_score_solo: number[];
9
+ skill_score_auto: number[];
10
+ skill_score_multi: number[];
11
+ fever_score: number;
12
+ fever_end_time: number;
13
+ tap_count: number;
14
+ }
15
+
1
16
  interface DataProvider {
2
- getMasterData: (key: string) => Promise<any>;
3
- getUserDataAll: () => Promise<any>;
4
- getUserData: (key: string) => Promise<any>;
5
- getMusicMeta: () => Promise<any>;
17
+ getMasterData: <T>(key: string) => Promise<T[]>;
18
+ getUserDataAll: () => Promise<Record<string, any>>;
19
+ getUserData: <T>(key: string) => Promise<T>;
20
+ getMusicMeta: () => Promise<MusicMeta[]>;
6
21
  }
7
22
 
8
23
  declare class CachedDataProvider implements DataProvider {
@@ -12,10 +27,64 @@ declare class CachedDataProvider implements DataProvider {
12
27
  private readonly instanceCache;
13
28
  private static readonly runningPromise;
14
29
  private getData;
15
- getMasterData(key: string): Promise<any>;
16
- getMusicMeta(): Promise<any>;
17
- getUserData(key: string): Promise<any>;
18
- getUserDataAll(): Promise<any>;
30
+ getMasterData<T>(key: string): Promise<T[]>;
31
+ getMusicMeta(): Promise<MusicMeta[]>;
32
+ getUserData<T>(key: string): Promise<T>;
33
+ getUserDataAll(): Promise<Record<string, any>>;
34
+ preloadMasterData(keys: string[]): Promise<any[]>;
35
+ }
36
+
37
+ interface AreaItemLevel {
38
+ areaItemId: number;
39
+ level: number;
40
+ targetUnit: string;
41
+ targetCardAttr: string;
42
+ targetGameCharacterId?: number;
43
+ power1BonusRate: number;
44
+ power1AllMatchBonusRate: number;
45
+ power2BonusRate: number;
46
+ power2AllMatchBonusRate: number;
47
+ power3BonusRate: number;
48
+ power3AllMatchBonusRate: number;
49
+ sentence: string;
50
+ }
51
+
52
+ interface AreaItem {
53
+ id: number;
54
+ areaId: number;
55
+ name: string;
56
+ flavorText: string;
57
+ spawnPoint: string;
58
+ assetbundleName: string;
59
+ }
60
+
61
+ interface CommonResource {
62
+ resourceId?: number;
63
+ resourceType: string;
64
+ resourceLevel?: number;
65
+ quantity: number;
66
+ }
67
+
68
+ interface ShopItem {
69
+ id: number;
70
+ shopId: number;
71
+ seq: number;
72
+ releaseConditionId: number;
73
+ resourceBoxId: number;
74
+ costs: Array<{
75
+ shopItemId: number;
76
+ seq: number;
77
+ cost: CommonResource;
78
+ }>;
79
+ }
80
+
81
+ declare class AreaItemService {
82
+ private readonly dataProvider;
83
+ constructor(dataProvider: DataProvider);
84
+ getAreaItemLevels(): Promise<AreaItemLevel[]>;
85
+ getAreaItemLevel(areaItemId: number, level: number): Promise<AreaItemLevel>;
86
+ getAreaItemNextLevel(areaItem: AreaItem, areaItemLevel?: AreaItemLevel): Promise<AreaItemLevel>;
87
+ getShopItem(areaItemLevel: AreaItemLevel): Promise<ShopItem>;
19
88
  }
20
89
 
21
90
  interface UserDeck {
@@ -62,21 +131,6 @@ interface UserChallengeLiveSoloDeck {
62
131
  support4: number | null;
63
132
  }
64
133
 
65
- interface AreaItemLevel {
66
- areaItemId: number;
67
- level: number;
68
- targetUnit: string;
69
- targetCardAttr: string;
70
- targetGameCharacterId?: number;
71
- power1BonusRate: number;
72
- power1AllMatchBonusRate: number;
73
- power2BonusRate: number;
74
- power2AllMatchBonusRate: number;
75
- power3BonusRate: number;
76
- power3AllMatchBonusRate: number;
77
- sentence: string;
78
- }
79
-
80
134
  declare class CardDetailMap {
81
135
  min: number;
82
136
  max: number;
@@ -93,11 +147,12 @@ declare class CardCalculator {
93
147
  private readonly powerCalculator;
94
148
  private readonly skillCalculator;
95
149
  private readonly eventCalculator;
150
+ private readonly areaItemService;
96
151
  constructor(dataProvider: DataProvider);
97
152
  private getCardUnits;
98
153
  private applyCardConfig;
99
154
  getCardDetail(userCard: UserCard, userAreaItemLevels: AreaItemLevel[], config?: Record<string, CardConfig>, eventId?: number): Promise<CardDetail>;
100
- batchGetCardDetail(userCards: UserCard[], config?: Record<string, CardConfig>, eventId?: number): Promise<CardDetail[]>;
155
+ batchGetCardDetail(userCards: UserCard[], config?: Record<string, CardConfig>, eventId?: number, areaItemLevels?: AreaItemLevel[]): Promise<CardDetail[]>;
101
156
  static isCertainlyLessThan(cardDetail0: CardDetail, cardDetail1: CardDetail): boolean;
102
157
  }
103
158
  interface CardDetail {
@@ -127,7 +182,7 @@ declare class DeckCalculator {
127
182
  constructor(dataProvider: DataProvider);
128
183
  getHonorBonusPower(): Promise<number>;
129
184
  static getDeckDetailByCards(cardDetails: CardDetail[], honorBonus: number): DeckDetail;
130
- getDeckDetail(deckCards: UserCard[]): Promise<DeckDetail>;
185
+ getDeckDetail(deckCards: UserCard[], eventId?: number, areaItemLevels?: AreaItemLevel[]): Promise<DeckDetail>;
131
186
  }
132
187
  interface DeckDetail {
133
188
  power: number;
@@ -160,13 +215,6 @@ declare class DeckService {
160
215
  static toUserChallengeLiveSoloDeck(userCards: DeckCardDetail[], characterId: number): UserChallengeLiveSoloDeck;
161
216
  }
162
217
 
163
- interface CommonResource {
164
- resourceId?: number;
165
- resourceType: string;
166
- resourceLevel?: number;
167
- quantity: number;
168
- }
169
-
170
218
  interface Card {
171
219
  id: number;
172
220
  seq: number;
@@ -227,21 +275,6 @@ declare class CardEventCalculator {
227
275
  getCardEventBonus(userCard: UserCard, eventId: number): Promise<number>;
228
276
  }
229
277
 
230
- interface MusicMeta {
231
- music_id: number;
232
- difficulty: string;
233
- music_time: number;
234
- event_rate: number;
235
- base_score: number;
236
- base_score_auto: number;
237
- skill_score_solo: number[];
238
- skill_score_auto: number[];
239
- skill_score_multi: number[];
240
- fever_score: number;
241
- fever_end_time: number;
242
- tap_count: number;
243
- }
244
-
245
278
  declare class LiveCalculator {
246
279
  private readonly dataProvider;
247
280
  private readonly deckCalculator;
@@ -249,6 +282,8 @@ declare class LiveCalculator {
249
282
  getMusicMeta(musicId: number, musicDiff: string): Promise<MusicMeta>;
250
283
  private static getBaseScore;
251
284
  private static getSkillScore;
285
+ private static getSortedSkillDetails;
286
+ private static getSortedSkillRate;
252
287
  static getLiveDetailByDeck(deckDetail: DeckDetail, musicMeta: MusicMeta, liveType: LiveType, skillDetails?: DeckCardSkillDetail[] | undefined, multiPowerSum?: number): LiveDetail;
253
288
  private static getMultiLiveSkill;
254
289
  private static getSoloLiveSkill;
@@ -283,10 +318,42 @@ declare class EventCalculator {
283
318
  static getDeckEventPoint(deckCards: CardDetail[], honorBonus: number, musicMeta: MusicMeta, liveType: LiveType): number;
284
319
  }
285
320
 
321
+ interface Area {
322
+ id: number;
323
+ assetbundleName: string;
324
+ areaType: string;
325
+ viewType: string;
326
+ name: string;
327
+ releaseConditionId: number;
328
+ }
329
+
330
+ declare class AreaItemRecommend {
331
+ private readonly dataProvider;
332
+ private readonly areaItemService;
333
+ private readonly deckCalculator;
334
+ constructor(dataProvider: DataProvider);
335
+ private static findCost;
336
+ private getRecommendAreaItem;
337
+ recommendAreaItem(userCards: UserCard[]): Promise<RecommendAreaItem[]>;
338
+ }
339
+ interface RecommendAreaItem {
340
+ area: Area;
341
+ areaItem: AreaItem;
342
+ areaItemLevel: AreaItemLevel;
343
+ shopItem: ShopItem;
344
+ cost: {
345
+ coin: number;
346
+ seed: number;
347
+ szk: number;
348
+ };
349
+ power: number;
350
+ }
351
+
286
352
  declare class BaseDeckRecommend {
287
353
  private readonly dataProvider;
288
354
  private readonly cardCalculator;
289
355
  private readonly deckCalculator;
356
+ private readonly areaItemService;
290
357
  constructor(dataProvider: DataProvider);
291
358
  private static findBestCards;
292
359
  recommendHighScoreDeck(userCards: UserCard[], scoreFunc: ScoreFunction, { musicMeta, limit, member, cardConfig }: DeckRecommendConfig, eventId?: number, isChallengeLive?: boolean): Promise<RecommendDeck[]>;
@@ -635,4 +702,4 @@ interface UserHonor {
635
702
  level: number;
636
703
  }
637
704
 
638
- export { AreaItemLevel, BaseDeckRecommend, CachedDataProvider, Card, CardCalculator, CardConfig, CardDetail, CardEpisode, CardEventCalculator, CardPowerCalculator, CardRarity, CardSkillCalculator, ChallengeLiveDeckRecommend, CharacterRank, DataProvider, DeckCalculator, DeckCardDetail, DeckCardSkillDetail, DeckDetail, DeckRecommendConfig, DeckService, Event, EventCalculator, EventCard, EventDeckBonus, EventDeckRecommend, EventExchange, EventRarityBonusRate, GameCharacter, GameCharacterUnit, Honor, LiveCalculator, LiveDetail, LiveSkill, LiveType, MasterLesson, Music, MusicDifficulty, MusicMeta, MusicRecommend, MusicVocal, RecommendDeck, ScoreFunction, Skill, User, UserArea, UserCard, UserChallengeLiveSoloDeck, UserCharacter, UserDeck, UserHonor };
705
+ export { Area, AreaItem, AreaItemLevel, AreaItemRecommend, AreaItemService, BaseDeckRecommend, CachedDataProvider, Card, CardCalculator, CardConfig, CardDetail, CardEpisode, CardEventCalculator, CardPowerCalculator, CardRarity, CardSkillCalculator, ChallengeLiveDeckRecommend, CharacterRank, DataProvider, DeckCalculator, DeckCardDetail, DeckCardSkillDetail, DeckDetail, DeckRecommendConfig, DeckService, Event, EventCalculator, EventCard, EventDeckBonus, EventDeckRecommend, EventExchange, EventRarityBonusRate, GameCharacter, GameCharacterUnit, Honor, LiveCalculator, LiveDetail, LiveSkill, LiveType, MasterLesson, Music, MusicDifficulty, MusicMeta, MusicRecommend, MusicVocal, RecommendAreaItem, RecommendDeck, ScoreFunction, ShopItem, Skill, User, UserArea, UserCard, UserChallengeLiveSoloDeck, UserCharacter, UserDeck, UserHonor };
package/dist/index.mjs CHANGED
@@ -75,6 +75,37 @@ class CachedDataProvider {
75
75
  async getUserDataAll() {
76
76
  return await this.getData(this.instanceCache, 'userData', async () => await this.dataProvider.getUserDataAll());
77
77
  }
78
+ async preloadMasterData(keys) {
79
+ return await Promise.all(keys.map(async (it) => await this.getMasterData(it)));
80
+ }
81
+ }
82
+
83
+ class AreaItemService {
84
+ dataProvider;
85
+ constructor(dataProvider) {
86
+ this.dataProvider = dataProvider;
87
+ }
88
+ async getAreaItemLevels() {
89
+ const userAreas = await this.dataProvider.getUserData('userAreas');
90
+ return await Promise.all(userAreas.flatMap(it => it.areaItems)
91
+ .map(async (it) => await this.getAreaItemLevel(it.areaItemId, it.level)));
92
+ }
93
+ async getAreaItemLevel(areaItemId, level) {
94
+ const areaItemLevels = await this.dataProvider.getMasterData('areaItemLevels');
95
+ return findOrThrow(areaItemLevels, it => it.areaItemId === areaItemId && it.level === level);
96
+ }
97
+ async getAreaItemNextLevel(areaItem, areaItemLevel) {
98
+ const level = areaItemLevel === undefined ? 1 : (areaItemLevel.level === 15 ? 15 : areaItemLevel.level + 1);
99
+ return await this.getAreaItemLevel(areaItem.id, level);
100
+ }
101
+ async getShopItem(areaItemLevel) {
102
+ const shopItems = await this.dataProvider.getMasterData('shopItems');
103
+ const idOffset = areaItemLevel.level <= 10
104
+ ? (1000 + (areaItemLevel.areaItemId - 1) * 10)
105
+ : (1550 - 10 + (areaItemLevel.areaItemId - 1) * 5);
106
+ const id = idOffset + areaItemLevel.level;
107
+ return findOrThrow(shopItems, it => it.id === id);
108
+ }
78
109
  }
79
110
 
80
111
  class DeckService {
@@ -187,8 +218,12 @@ class CardPowerCalculator {
187
218
  return basePower.reduce((v, it) => v + it, 0) + characterBonus + await this.getAreaItemBonusPower(userAreaItemLevels, basePower, card.characterId, unit, sameUnit, card.attr, sameAttr);
188
219
  }
189
220
  async getCardBasePowers(userCard, card) {
190
- const cardEpisodes = await this.dataProvider.getMasterData('cardEpisodes');
191
- const masterLessons = await this.dataProvider.getMasterData('masterLessons');
221
+ const p = await Promise.all([
222
+ await this.dataProvider.getMasterData('cardEpisodes'),
223
+ await this.dataProvider.getMasterData('masterLessons')
224
+ ]);
225
+ const cardEpisodes = p[0];
226
+ const masterLessons = p[1];
192
227
  const ret = [0, 0, 0];
193
228
  const cardParameters = card.cardParameters.filter(it => it.cardLevel === userCard.level);
194
229
  const params = ['param1', 'param2', 'param3'];
@@ -328,11 +363,13 @@ class CardCalculator {
328
363
  powerCalculator;
329
364
  skillCalculator;
330
365
  eventCalculator;
366
+ areaItemService;
331
367
  constructor(dataProvider) {
332
368
  this.dataProvider = dataProvider;
333
369
  this.powerCalculator = new CardPowerCalculator(dataProvider);
334
370
  this.skillCalculator = new CardSkillCalculator(dataProvider);
335
371
  this.eventCalculator = new CardEventCalculator(dataProvider);
372
+ this.areaItemService = new AreaItemService(dataProvider);
336
373
  }
337
374
  async getCardUnits(card) {
338
375
  const gameCharacters = await this.dataProvider.getMasterData('gameCharacters');
@@ -358,7 +395,9 @@ class CardCalculator {
358
395
  }
359
396
  }
360
397
  if (episodeRead && ret.episodes !== undefined) {
361
- ret.episodes.forEach(it => { it.scenarioStatus = 'already_read'; });
398
+ ret.episodes.forEach(it => {
399
+ it.scenarioStatus = 'already_read';
400
+ });
362
401
  }
363
402
  if (masterMax) {
364
403
  ret.masterRank = 5;
@@ -391,11 +430,11 @@ class CardCalculator {
391
430
  eventBonus
392
431
  };
393
432
  }
394
- async batchGetCardDetail(userCards, config = {}, eventId = 0) {
395
- const areaItemLevels = await this.dataProvider.getMasterData('areaItemLevels');
396
- const userAreas = await this.dataProvider.getUserData('userAreas');
397
- const userItemLevels = userAreas.flatMap(it => it.areaItems).map(areaItem => findOrThrow(areaItemLevels, it => it.areaItemId === areaItem.areaItemId && it.level === areaItem.level));
398
- return await Promise.all(userCards.map(async (it) => await this.getCardDetail(it, userItemLevels, config, eventId)));
433
+ async batchGetCardDetail(userCards, config = {}, eventId = 0, areaItemLevels) {
434
+ const areaItemLevels0 = areaItemLevels === undefined
435
+ ? await this.areaItemService.getAreaItemLevels()
436
+ : areaItemLevels;
437
+ return await Promise.all(userCards.map(async (it) => await this.getCardDetail(it, areaItemLevels0, config, eventId)));
399
438
  }
400
439
  static isCertainlyLessThan(cardDetail0, cardDetail1) {
401
440
  return cardDetail0.power.isCertainlyLessThen(cardDetail1.power) &&
@@ -440,21 +479,47 @@ class LiveCalculator {
440
479
  return musicMeta.skill_score_auto;
441
480
  }
442
481
  }
482
+ static getSortedSkillDetails(deckDetail, liveType, skillDetails = undefined) {
483
+ if (skillDetails !== undefined && skillDetails.length === 6 && skillDetails[5].scoreUp > 0) {
484
+ return {
485
+ details: skillDetails,
486
+ sorted: false
487
+ };
488
+ }
489
+ if (liveType === LiveType.MULTI) {
490
+ return {
491
+ details: duplicateObj(LiveCalculator.getMultiLiveSkill(deckDetail), 6),
492
+ sorted: false
493
+ };
494
+ }
495
+ const sortedSkill = [...deckDetail.cards].map(it => it.skill)
496
+ .sort((a, b) => a.scoreUp - b.scoreUp);
497
+ const emptySkill = duplicateObj({
498
+ scoreUp: 0,
499
+ lifeRecovery: 0
500
+ }, 5 - sortedSkill.length);
501
+ return {
502
+ details: [...sortedSkill, ...emptySkill, deckDetail.cards[0].skill],
503
+ sorted: true
504
+ };
505
+ }
506
+ static getSortedSkillRate(sorted, cardLength, skillScores) {
507
+ if (!sorted) {
508
+ return skillScores;
509
+ }
510
+ return [
511
+ ...skillScores.slice(0, cardLength).sort((a, b) => a - b),
512
+ ...skillScores.slice(cardLength)
513
+ ];
514
+ }
443
515
  static getLiveDetailByDeck(deckDetail, musicMeta, liveType, skillDetails = undefined, multiPowerSum = 0) {
444
- const skills = skillDetails !== undefined
445
- ? skillDetails
446
- : (liveType === LiveType.MULTI
447
- ? duplicateObj(LiveCalculator.getMultiLiveSkill(deckDetail), 6)
448
- : [...[...deckDetail.cards].map(it => it.skill)
449
- .sort((a, b) => a.scoreUp - b.scoreUp),
450
- deckDetail.cards[0].skill]);
516
+ const skills = this.getSortedSkillDetails(deckDetail, liveType, skillDetails);
451
517
  const baseRate = LiveCalculator.getBaseScore(musicMeta, liveType);
452
518
  const skillScores = [...LiveCalculator.getSkillScore(musicMeta, liveType)];
453
- const skillRate = skillDetails === undefined
454
- ? [...skillScores.slice(0, 5).sort((a, b) => a - b), skillScores[5]]
455
- : skillScores;
456
- const rate = baseRate + skills.reduce((v, it, i) => v + it.scoreUp * skillRate[i] / 100, 0);
457
- const life = skills.reduce((v, it) => v + it.lifeRecovery, 0);
519
+ const skillRate = LiveCalculator.getSortedSkillRate(skills.sorted, deckDetail.cards.length, skillScores);
520
+ const rate = baseRate + skills.details
521
+ .reduce((v, it, i) => v + it.scoreUp * skillRate[i] / 100, 0);
522
+ const life = skills.details.reduce((v, it) => v + it.lifeRecovery, 0);
458
523
  const powerSum = multiPowerSum === 0 ? 5 * deckDetail.power : multiPowerSum;
459
524
  const activeBonus = liveType === LiveType.MULTI ? 5 * 0.015 * powerSum : 0;
460
525
  return {
@@ -610,8 +675,56 @@ class DeckCalculator {
610
675
  cards
611
676
  };
612
677
  }
613
- async getDeckDetail(deckCards) {
614
- return DeckCalculator.getDeckDetailByCards(await this.cardCalculator.batchGetCardDetail(deckCards), await this.getHonorBonusPower());
678
+ async getDeckDetail(deckCards, eventId = 0, areaItemLevels) {
679
+ return DeckCalculator.getDeckDetailByCards(await this.cardCalculator.batchGetCardDetail(deckCards, {}, eventId, areaItemLevels), await this.getHonorBonusPower());
680
+ }
681
+ }
682
+
683
+ class AreaItemRecommend {
684
+ dataProvider;
685
+ areaItemService;
686
+ deckCalculator;
687
+ constructor(dataProvider) {
688
+ this.dataProvider = dataProvider;
689
+ this.areaItemService = new AreaItemService(dataProvider);
690
+ this.deckCalculator = new DeckCalculator(dataProvider);
691
+ }
692
+ static findCost(shopItem, resourceType, resourceId) {
693
+ const cost = shopItem.costs.map(it => it.cost)
694
+ .find(it => it.resourceType === resourceType && it.resourceId === resourceId);
695
+ return cost === undefined ? 0 : cost.quantity;
696
+ }
697
+ async getRecommendAreaItem(areaItem, areaItemLevel, power) {
698
+ const areas = await this.dataProvider.getMasterData('areas');
699
+ const area = findOrThrow(areas, it => it.id === areaItem.areaId);
700
+ const shopItem = await this.areaItemService.getShopItem(areaItemLevel);
701
+ return {
702
+ area,
703
+ areaItem,
704
+ areaItemLevel,
705
+ shopItem,
706
+ cost: {
707
+ coin: AreaItemRecommend.findCost(shopItem, 'coin', 0),
708
+ seed: AreaItemRecommend.findCost(shopItem, 'material', 17),
709
+ szk: AreaItemRecommend.findCost(shopItem, 'material', 57)
710
+ },
711
+ power
712
+ };
713
+ }
714
+ async recommendAreaItem(userCards) {
715
+ const areaItems = await this.dataProvider.getMasterData('areaItems');
716
+ const currentAreaItemLevels = await this.areaItemService.getAreaItemLevels();
717
+ const { power: currentPower } = await this.deckCalculator.getDeckDetail(userCards, 0, currentAreaItemLevels);
718
+ const recommend = await Promise.all(areaItems.map(async (areaItem) => {
719
+ const newAreaItemLevel = await this.areaItemService.getAreaItemNextLevel(areaItem, currentAreaItemLevels.find(it => it.areaItemId === areaItem.id));
720
+ const newAreaItemLevels = [
721
+ ...currentAreaItemLevels.filter(it => it.areaItemId !== areaItem.id), newAreaItemLevel
722
+ ];
723
+ const { power: newPower } = await this.deckCalculator.getDeckDetail(userCards, 0, newAreaItemLevels);
724
+ return await this.getRecommendAreaItem(areaItem, newAreaItemLevel, newPower - currentPower);
725
+ }));
726
+ return recommend.filter(it => it.power > 0)
727
+ .sort((a, b) => b.power / b.cost.coin - a.power / a.cost.coin);
615
728
  }
616
729
  }
617
730
 
@@ -761,12 +874,17 @@ class BaseDeckRecommend {
761
874
  dataProvider;
762
875
  cardCalculator;
763
876
  deckCalculator;
877
+ areaItemService;
764
878
  constructor(dataProvider) {
765
879
  this.dataProvider = dataProvider;
766
880
  this.cardCalculator = new CardCalculator(dataProvider);
767
881
  this.deckCalculator = new DeckCalculator(dataProvider);
882
+ this.areaItemService = new AreaItemService(dataProvider);
768
883
  }
769
884
  static findBestCards(cardDetails, scoreFunc, limit = 1, isChallengeLive = false, member = 5, honorBonus = 0, deckCards = [], deckCharacters = []) {
885
+ if (isChallengeLive) {
886
+ member = Math.min(member, cardDetails.length);
887
+ }
770
888
  if (deckCards.length === member) {
771
889
  const score = scoreFunc(deckCards);
772
890
  const deckDetail = DeckCalculator.getDeckDetailByCards(deckCards, honorBonus);
@@ -821,7 +939,8 @@ class BaseDeckRecommend {
821
939
  return ans;
822
940
  }
823
941
  async recommendHighScoreDeck(userCards, scoreFunc, { musicMeta, limit = 1, member = 5, cardConfig = {} }, eventId = 0, isChallengeLive = false) {
824
- const cards = await this.cardCalculator.batchGetCardDetail(userCards, cardConfig, eventId);
942
+ const areaItemLevels = await this.areaItemService.getAreaItemLevels();
943
+ const cards = await this.cardCalculator.batchGetCardDetail(userCards, cardConfig, eventId, areaItemLevels);
825
944
  const honorBonus = await this.deckCalculator.getHonorBonusPower();
826
945
  let priority = (isChallengeLive || eventId === 0) ? 10 : 0;
827
946
  while (priority < 10) {
@@ -907,4 +1026,4 @@ class MusicRecommend {
907
1026
  }
908
1027
  }
909
1028
 
910
- export { BaseDeckRecommend, CachedDataProvider, CardCalculator, CardEventCalculator, CardPowerCalculator, CardSkillCalculator, ChallengeLiveDeckRecommend, DeckCalculator, DeckService, EventCalculator, EventDeckRecommend, LiveCalculator, LiveType, MusicRecommend };
1029
+ export { AreaItemRecommend, AreaItemService, BaseDeckRecommend, CachedDataProvider, CardCalculator, CardEventCalculator, CardPowerCalculator, CardSkillCalculator, ChallengeLiveDeckRecommend, DeckCalculator, DeckService, EventCalculator, EventDeckRecommend, LiveCalculator, LiveType, MusicRecommend };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sekai-calculator",
3
- "version": "0.8.0",
3
+ "version": "0.8.3",
4
4
  "description": "Project SEKAI Calculator for deck power, live score, event point and more.",
5
5
  "type": "module",
6
6
  "types": "dist/index.d.ts",