sekai-calculator 0.2.0 → 0.3.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/dist/index.cjs CHANGED
@@ -27,6 +27,13 @@ function duplicateObj(obj, times) {
27
27
  ret.push(obj);
28
28
  return ret;
29
29
  }
30
+ function containsAny(collection, contains) {
31
+ for (const c of contains) {
32
+ if (collection.includes(c))
33
+ return true;
34
+ }
35
+ return false;
36
+ }
30
37
 
31
38
  class DeckService {
32
39
  dataProvider;
@@ -151,8 +158,10 @@ class CardPowerCalculator {
151
158
  ret[1] += card.specialTrainingPower2BonusFixed;
152
159
  ret[2] += card.specialTrainingPower3BonusFixed;
153
160
  }
154
- const episodes = userCard.episodes.filter(it => it.scenarioStatus === 'already_read')
155
- .map(it => findOrThrow(cardEpisodes, e => e.id === it.cardEpisodeId));
161
+ const episodes = userCard.episodes === undefined
162
+ ? []
163
+ : userCard.episodes.filter(it => it.scenarioStatus === 'already_read')
164
+ .map(it => findOrThrow(cardEpisodes, e => e.id === it.cardEpisodeId));
156
165
  for (const episode of episodes) {
157
166
  ret[0] += episode.power1BonusFixed;
158
167
  ret[1] += episode.power2BonusFixed;
@@ -319,7 +328,7 @@ class CardCalculator {
319
328
  return cardDetail0.power.isCertainlyLessThen(cardDetail1.power) &&
320
329
  cardDetail0.scoreSkill.isCertainlyLessThen(cardDetail1.scoreSkill) &&
321
330
  (cardDetail0.eventBonus === undefined || cardDetail1.eventBonus === undefined ||
322
- cardDetail0.eventBonus < cardDetail1.eventBonus);
331
+ cardDetail0.eventBonus <= cardDetail1.eventBonus);
323
332
  }
324
333
  }
325
334
 
@@ -362,42 +371,6 @@ class DeckCalculator {
362
371
  }
363
372
  }
364
373
 
365
- class EventCalculator {
366
- dataProvider;
367
- cardEventCalculator;
368
- constructor(dataProvider) {
369
- this.dataProvider = dataProvider;
370
- this.cardEventCalculator = new CardEventCalculator(dataProvider);
371
- }
372
- async getDeckEventBonus(deckCards, eventId) {
373
- return await deckCards.reduce(async (v, it) => await v + await this.cardEventCalculator.getCardEventBonus(it, eventId), Promise.resolve(0));
374
- }
375
- static getEventPoint(type, selfScore, musicRate = 100, deckBonus = 0, boostRate = 1, otherScore = 0, life = 1000) {
376
- const musicRate0 = musicRate / 100;
377
- const deckRate = deckBonus / 100 + 1;
378
- switch (type) {
379
- case exports.EventLiveType.SOLO:
380
- return Math.floor((100 + Math.floor(selfScore / 20000)) * musicRate0 * deckRate) * boostRate;
381
- case exports.EventLiveType.CHALLENGE:
382
- return (100 + Math.floor(selfScore / 20000)) * 120;
383
- case exports.EventLiveType.MULTI:
384
- return Math.floor((110 + Math.floor(selfScore / 17000) +
385
- Math.floor(Math.min(otherScore, 5200000) / 400000)) * musicRate0 * deckRate) * boostRate;
386
- case exports.EventLiveType.CHEERFUL:
387
- return Math.floor((114 + Math.floor(selfScore / 12500) +
388
- Math.floor(Math.min(otherScore, 4400000) / 400000) + Math.floor(Math.min(life, 1000) / 25)) *
389
- musicRate0 * deckRate) * boostRate;
390
- }
391
- }
392
- }
393
- exports.EventLiveType = void 0;
394
- (function (EventLiveType) {
395
- EventLiveType["SOLO"] = "solo";
396
- EventLiveType["CHALLENGE"] = "challenge";
397
- EventLiveType["MULTI"] = "multi";
398
- EventLiveType["CHEERFUL"] = "cheerful";
399
- })(exports.EventLiveType || (exports.EventLiveType = {}));
400
-
401
374
  class LiveCalculator {
402
375
  dataProvider;
403
376
  deckCalculator;
@@ -405,11 +378,17 @@ class LiveCalculator {
405
378
  this.dataProvider = dataProvider;
406
379
  this.deckCalculator = new DeckCalculator(dataProvider);
407
380
  }
381
+ async getMusicMeta(musicId, musicDiff) {
382
+ const musicMetas = await this.dataProvider.getMusicMeta();
383
+ return findOrThrow(musicMetas, it => it.music_id === musicId && it.difficulty === musicDiff);
384
+ }
408
385
  static getBaseScore(musicMeta, liveType) {
409
386
  switch (liveType) {
410
387
  case exports.LiveType.SOLO:
388
+ case exports.LiveType.CHALLENGE:
411
389
  return musicMeta.base_score;
412
390
  case exports.LiveType.MULTI:
391
+ case exports.LiveType.CHEERFUL:
413
392
  return musicMeta.base_score + musicMeta.fever_score;
414
393
  case exports.LiveType.AUTO:
415
394
  return musicMeta.base_score_auto;
@@ -418,8 +397,10 @@ class LiveCalculator {
418
397
  static getSkillScore(musicMeta, liveType) {
419
398
  switch (liveType) {
420
399
  case exports.LiveType.SOLO:
400
+ case exports.LiveType.CHALLENGE:
421
401
  return musicMeta.skill_score_solo;
422
402
  case exports.LiveType.MULTI:
403
+ case exports.LiveType.CHEERFUL:
423
404
  return musicMeta.skill_score_multi;
424
405
  case exports.LiveType.AUTO:
425
406
  return musicMeta.skill_score_auto;
@@ -473,28 +454,176 @@ class LiveCalculator {
473
454
  ret[5] = skills[skills.length - 1];
474
455
  return ret;
475
456
  }
476
- async getLiveDetail(deckCards, musicId, musicDiff, liveType, liveSkills = undefined) {
477
- const musicMetas = await this.dataProvider.getMusicMeta();
478
- const musicMeta = findOrThrow(musicMetas, it => it.music_id === musicId && it.difficulty === musicDiff);
457
+ async getLiveDetail(deckCards, musicMeta, liveType, liveSkills = undefined) {
479
458
  const deckDetail = await this.deckCalculator.getDeckDetail(deckCards);
480
459
  const skills = liveType === exports.LiveType.MULTI
481
460
  ? undefined
482
461
  : LiveCalculator.getSoloLiveSkill(liveSkills, deckDetail.skill);
483
462
  return LiveCalculator.getLiveDetailByDeck(deckDetail, musicMeta, liveType, skills);
484
463
  }
464
+ static getLiveScoreByDeck(deckCards, honorBonus, musicMeta, liveType) {
465
+ return LiveCalculator.getLiveDetailByDeck(DeckCalculator.getDeckDetailByCards(deckCards, honorBonus), musicMeta, liveType).score;
466
+ }
485
467
  }
486
468
  exports.LiveType = void 0;
487
469
  (function (LiveType) {
488
470
  LiveType["SOLO"] = "solo";
489
- LiveType["MULTI"] = "multi";
490
471
  LiveType["AUTO"] = "auto";
472
+ LiveType["CHALLENGE"] = "challenge";
473
+ LiveType["MULTI"] = "multi";
474
+ LiveType["CHEERFUL"] = "cheerful";
491
475
  })(exports.LiveType || (exports.LiveType = {}));
492
476
 
477
+ class EventCalculator {
478
+ dataProvider;
479
+ cardEventCalculator;
480
+ constructor(dataProvider) {
481
+ this.dataProvider = dataProvider;
482
+ this.cardEventCalculator = new CardEventCalculator(dataProvider);
483
+ }
484
+ async getDeckEventBonus(deckCards, eventId) {
485
+ return await deckCards.reduce(async (v, it) => await v + await this.cardEventCalculator.getCardEventBonus(it, eventId), Promise.resolve(0));
486
+ }
487
+ static getEventPoint(type, selfScore, musicRate = 100, deckBonus = 0, boostRate = 1, otherScore = 0, life = 1000) {
488
+ const musicRate0 = musicRate / 100;
489
+ const deckRate = deckBonus / 100 + 1;
490
+ const otherScore0 = otherScore === 0 ? 4 * selfScore : otherScore;
491
+ switch (type) {
492
+ case exports.LiveType.SOLO:
493
+ case exports.LiveType.AUTO:
494
+ return Math.floor((100 + Math.floor(selfScore / 20000)) * musicRate0 * deckRate) * boostRate;
495
+ case exports.LiveType.CHALLENGE:
496
+ return (100 + Math.floor(selfScore / 20000)) * 120;
497
+ case exports.LiveType.MULTI:
498
+ return Math.floor((110 + Math.floor(selfScore / 17000) +
499
+ Math.floor(Math.min(otherScore0, 5200000) / 400000)) * musicRate0 * deckRate) * boostRate;
500
+ case exports.LiveType.CHEERFUL:
501
+ return Math.floor((114 + Math.floor(selfScore / 12500) +
502
+ Math.floor(Math.min(otherScore0, 4400000) / 400000) + Math.floor(Math.min(life, 1000) / 25)) *
503
+ musicRate0 * deckRate) * boostRate;
504
+ }
505
+ }
506
+ static getDeckEventPoint(deckCards, honorBonus, musicMeta, liveType) {
507
+ const deckBonus = deckCards.reduce((v, it) => v + (it.eventBonus === undefined ? 0 : it.eventBonus), 0);
508
+ const score = LiveCalculator.getLiveScoreByDeck(deckCards, honorBonus, musicMeta, liveType);
509
+ return EventCalculator.getEventPoint(liveType, score, musicMeta.event_rate, deckBonus);
510
+ }
511
+ }
512
+
513
+ class BaseDeckRecommend {
514
+ dataProvider;
515
+ cardCalculator;
516
+ deckCalculator;
517
+ constructor(dataProvider) {
518
+ this.dataProvider = dataProvider;
519
+ this.cardCalculator = new CardCalculator(dataProvider);
520
+ this.deckCalculator = new DeckCalculator(dataProvider);
521
+ }
522
+ static filterCard(cardDetails) {
523
+ let afterFilter = cardDetails;
524
+ for (const minBonus of [65, 50, 40, 25, 15, 0]) {
525
+ const bonusFilter = cardDetails.filter(cardDetail => !(cardDetail.eventBonus !== undefined && cardDetail.eventBonus < minBonus));
526
+ if (bonusFilter.length >= 5) {
527
+ afterFilter = bonusFilter;
528
+ break;
529
+ }
530
+ }
531
+ return afterFilter;
532
+ }
533
+ static findBestCards(cardDetails, scoreFunc, isChallengeLive = false, member = 5, deckCards = [], deckCharacters = []) {
534
+ if (deckCards.length === member) {
535
+ return {
536
+ score: scoreFunc(deckCards),
537
+ deckCards
538
+ };
539
+ }
540
+ let ans = {
541
+ score: 0,
542
+ deckCards: cardDetails
543
+ };
544
+ let preCard = cardDetails[0];
545
+ for (const card of cardDetails) {
546
+ if (deckCards.includes(card))
547
+ continue;
548
+ if (!isChallengeLive && deckCharacters.includes(card.characterId))
549
+ continue;
550
+ if (deckCards.length >= 1 && deckCards[0].scoreSkill.isCertainlyLessThen(card.scoreSkill))
551
+ continue;
552
+ if (deckCards.length >= 1 && card.attr !== deckCards[0].attr && !containsAny(deckCards[0].units, card.units)) {
553
+ continue;
554
+ }
555
+ if (CardCalculator.isCertainlyLessThan(card, preCard))
556
+ continue;
557
+ preCard = card;
558
+ const result = BaseDeckRecommend.findBestCards(cardDetails, scoreFunc, isChallengeLive, member, [...deckCards, card], [...deckCharacters, card.characterId]);
559
+ if (result.score > ans.score)
560
+ ans = result;
561
+ }
562
+ return ans;
563
+ }
564
+ async recommendHighScoreDeck(userCards, musicId, musicDiff, scoreFunc, eventId = 0, isChallengeLive = false, member = 5) {
565
+ const musicMetas = await this.dataProvider.getMusicMeta();
566
+ const musicMeta = findOrThrow(musicMetas, it => it.music_id === musicId && it.difficulty === musicDiff);
567
+ const cardDetails = BaseDeckRecommend.filterCard(await this.cardCalculator.batchGetCardDetail(userCards, eventId));
568
+ const honorBonus = await this.deckCalculator.getHonorBonusPower();
569
+ const best = BaseDeckRecommend.findBestCards(cardDetails, deckCards => scoreFunc(musicMeta, honorBonus, deckCards), isChallengeLive, member);
570
+ return {
571
+ score: best.score,
572
+ deckCards: best.deckCards.map(deckCard => findOrThrow(userCards, it => it.cardId === deckCard.cardId))
573
+ };
574
+ }
575
+ static getLiveScoreFunction(liveType) {
576
+ return (musicMeta, honorBonus, deckCards) => LiveCalculator.getLiveScoreByDeck(deckCards, honorBonus, musicMeta, liveType);
577
+ }
578
+ static getEventPointFunction(liveType) {
579
+ return (musicMeta, honorBonus, deckCards) => EventCalculator.getDeckEventPoint(deckCards, honorBonus, musicMeta, liveType);
580
+ }
581
+ }
582
+
583
+ class ChallengeLiveDeckRecommend {
584
+ dataProvider;
585
+ baseRecommend;
586
+ constructor(dataProvider) {
587
+ this.dataProvider = dataProvider;
588
+ this.baseRecommend = new BaseDeckRecommend(dataProvider);
589
+ }
590
+ async recommendChallengeLiveDeck(characterId, musicId, musicDiff, member = 5) {
591
+ const userCards = await this.dataProvider.getUserData('userCards');
592
+ const cards = await this.dataProvider.getMasterData('cards');
593
+ const characterCards = userCards
594
+ .filter(userCard => findOrThrow(cards, it => it.id === userCard.cardId).characterId === characterId);
595
+ const recommend = await this.baseRecommend.recommendHighScoreDeck(characterCards, musicId, musicDiff, BaseDeckRecommend.getLiveScoreFunction(exports.LiveType.SOLO), 0, true, member);
596
+ return {
597
+ score: recommend.score,
598
+ deck: DeckService.toUserChallengeLiveSoloDeck(recommend.deckCards, characterId)
599
+ };
600
+ }
601
+ }
602
+
603
+ class EventDeckRecommend {
604
+ dataProvider;
605
+ baseRecommend;
606
+ constructor(dataProvider) {
607
+ this.dataProvider = dataProvider;
608
+ this.baseRecommend = new BaseDeckRecommend(dataProvider);
609
+ }
610
+ async recommendEventDeck(eventId, musicId, musicDiff, liveType) {
611
+ const userCards = await this.dataProvider.getUserData('userCards');
612
+ const recommend = await this.baseRecommend.recommendHighScoreDeck(userCards, musicId, musicDiff, BaseDeckRecommend.getEventPointFunction(liveType), eventId);
613
+ return {
614
+ point: recommend.score,
615
+ deck: DeckService.toUserDeck(recommend.deckCards)
616
+ };
617
+ }
618
+ }
619
+
493
620
  exports.CardCalculator = CardCalculator;
494
621
  exports.CardEventCalculator = CardEventCalculator;
495
622
  exports.CardPowerCalculator = CardPowerCalculator;
496
623
  exports.CardSkillCalculator = CardSkillCalculator;
624
+ exports.ChallengeLiveDeckRecommend = ChallengeLiveDeckRecommend;
497
625
  exports.DeckCalculator = DeckCalculator;
498
626
  exports.DeckService = DeckService;
499
627
  exports.EventCalculator = EventCalculator;
628
+ exports.EventDeckRecommend = EventDeckRecommend;
500
629
  exports.LiveCalculator = LiveCalculator;
package/dist/index.d.ts CHANGED
@@ -193,20 +193,6 @@ declare class CardEventCalculator {
193
193
  getCardEventBonus(userCard: UserCard, eventId: number): Promise<number>;
194
194
  }
195
195
 
196
- declare class EventCalculator {
197
- private readonly dataProvider;
198
- private readonly cardEventCalculator;
199
- constructor(dataProvider: DataProvider);
200
- getDeckEventBonus(deckCards: UserCard[], eventId: number): Promise<number>;
201
- static getEventPoint(type: EventLiveType, selfScore: number, musicRate?: number, deckBonus?: number, boostRate?: number, otherScore?: number, life?: number): number;
202
- }
203
- declare enum EventLiveType {
204
- SOLO = "solo",
205
- CHALLENGE = "challenge",
206
- MULTI = "multi",
207
- CHEERFUL = "cheerful"
208
- }
209
-
210
196
  interface MusicMeta {
211
197
  music_id: number;
212
198
  difficulty: string;
@@ -226,12 +212,14 @@ declare class LiveCalculator {
226
212
  private readonly dataProvider;
227
213
  private readonly deckCalculator;
228
214
  constructor(dataProvider: DataProvider);
215
+ getMusicMeta(musicId: number, musicDiff: string): Promise<MusicMeta>;
229
216
  private static getBaseScore;
230
217
  private static getSkillScore;
231
218
  static getLiveDetailByDeck(deckDetail: DeckDetail, musicMeta: MusicMeta, liveType: LiveType, skillDetails?: SkillDetail[] | undefined, multiPowerSum?: number): LiveDetail;
232
219
  private static getMultiLiveSkill;
233
220
  private static getSoloLiveSkill;
234
- getLiveDetail(deckCards: UserCard[], musicId: number, musicDiff: string, liveType: LiveType, liveSkills?: LiveSkill[] | undefined): Promise<LiveDetail>;
221
+ getLiveDetail(deckCards: UserCard[], musicMeta: MusicMeta, liveType: LiveType, liveSkills?: LiveSkill[] | undefined): Promise<LiveDetail>;
222
+ static getLiveScoreByDeck(deckCards: CardDetail[], honorBonus: number, musicMeta: MusicMeta, liveType: LiveType): number;
235
223
  }
236
224
  interface LiveDetail {
237
225
  score: number;
@@ -245,8 +233,39 @@ interface LiveSkill {
245
233
  }
246
234
  declare enum LiveType {
247
235
  SOLO = "solo",
236
+ AUTO = "auto",
237
+ CHALLENGE = "challenge",
248
238
  MULTI = "multi",
249
- AUTO = "auto"
239
+ CHEERFUL = "cheerful"
240
+ }
241
+
242
+ declare class EventCalculator {
243
+ private readonly dataProvider;
244
+ private readonly cardEventCalculator;
245
+ constructor(dataProvider: DataProvider);
246
+ getDeckEventBonus(deckCards: UserCard[], eventId: number): Promise<number>;
247
+ static getEventPoint(type: LiveType, selfScore: number, musicRate?: number, deckBonus?: number, boostRate?: number, otherScore?: number, life?: number): number;
248
+ static getDeckEventPoint(deckCards: CardDetail[], honorBonus: number, musicMeta: MusicMeta, liveType: LiveType): number;
249
+ }
250
+
251
+ declare class ChallengeLiveDeckRecommend {
252
+ private readonly dataProvider;
253
+ private readonly baseRecommend;
254
+ constructor(dataProvider: DataProvider);
255
+ recommendChallengeLiveDeck(characterId: number, musicId: number, musicDiff: string, member?: number): Promise<{
256
+ score: number;
257
+ deck: UserChallengeLiveSoloDeck;
258
+ }>;
259
+ }
260
+
261
+ declare class EventDeckRecommend {
262
+ private readonly dataProvider;
263
+ private readonly baseRecommend;
264
+ constructor(dataProvider: DataProvider);
265
+ recommendEventDeck(eventId: number, musicId: number, musicDiff: string, liveType: LiveType): Promise<{
266
+ point: number;
267
+ deck: UserDeck;
268
+ }>;
250
269
  }
251
270
 
252
- export { CardCalculator, CardDetail, CardEventCalculator, CardPowerCalculator, CardSkillCalculator, DataProvider, DeckCalculator, DeckDetail, DeckService, EventCalculator, EventLiveType, LiveCalculator, LiveDetail, LiveSkill, LiveType, SkillDetail };
271
+ export { CardCalculator, CardDetail, CardEventCalculator, CardPowerCalculator, CardSkillCalculator, ChallengeLiveDeckRecommend, DataProvider, DeckCalculator, DeckDetail, DeckService, EventCalculator, EventDeckRecommend, LiveCalculator, LiveDetail, LiveSkill, LiveType, SkillDetail };
package/dist/index.mjs CHANGED
@@ -25,6 +25,13 @@ function duplicateObj(obj, times) {
25
25
  ret.push(obj);
26
26
  return ret;
27
27
  }
28
+ function containsAny(collection, contains) {
29
+ for (const c of contains) {
30
+ if (collection.includes(c))
31
+ return true;
32
+ }
33
+ return false;
34
+ }
28
35
 
29
36
  class DeckService {
30
37
  dataProvider;
@@ -149,8 +156,10 @@ class CardPowerCalculator {
149
156
  ret[1] += card.specialTrainingPower2BonusFixed;
150
157
  ret[2] += card.specialTrainingPower3BonusFixed;
151
158
  }
152
- const episodes = userCard.episodes.filter(it => it.scenarioStatus === 'already_read')
153
- .map(it => findOrThrow(cardEpisodes, e => e.id === it.cardEpisodeId));
159
+ const episodes = userCard.episodes === undefined
160
+ ? []
161
+ : userCard.episodes.filter(it => it.scenarioStatus === 'already_read')
162
+ .map(it => findOrThrow(cardEpisodes, e => e.id === it.cardEpisodeId));
154
163
  for (const episode of episodes) {
155
164
  ret[0] += episode.power1BonusFixed;
156
165
  ret[1] += episode.power2BonusFixed;
@@ -317,7 +326,7 @@ class CardCalculator {
317
326
  return cardDetail0.power.isCertainlyLessThen(cardDetail1.power) &&
318
327
  cardDetail0.scoreSkill.isCertainlyLessThen(cardDetail1.scoreSkill) &&
319
328
  (cardDetail0.eventBonus === undefined || cardDetail1.eventBonus === undefined ||
320
- cardDetail0.eventBonus < cardDetail1.eventBonus);
329
+ cardDetail0.eventBonus <= cardDetail1.eventBonus);
321
330
  }
322
331
  }
323
332
 
@@ -360,42 +369,6 @@ class DeckCalculator {
360
369
  }
361
370
  }
362
371
 
363
- class EventCalculator {
364
- dataProvider;
365
- cardEventCalculator;
366
- constructor(dataProvider) {
367
- this.dataProvider = dataProvider;
368
- this.cardEventCalculator = new CardEventCalculator(dataProvider);
369
- }
370
- async getDeckEventBonus(deckCards, eventId) {
371
- return await deckCards.reduce(async (v, it) => await v + await this.cardEventCalculator.getCardEventBonus(it, eventId), Promise.resolve(0));
372
- }
373
- static getEventPoint(type, selfScore, musicRate = 100, deckBonus = 0, boostRate = 1, otherScore = 0, life = 1000) {
374
- const musicRate0 = musicRate / 100;
375
- const deckRate = deckBonus / 100 + 1;
376
- switch (type) {
377
- case EventLiveType.SOLO:
378
- return Math.floor((100 + Math.floor(selfScore / 20000)) * musicRate0 * deckRate) * boostRate;
379
- case EventLiveType.CHALLENGE:
380
- return (100 + Math.floor(selfScore / 20000)) * 120;
381
- case EventLiveType.MULTI:
382
- return Math.floor((110 + Math.floor(selfScore / 17000) +
383
- Math.floor(Math.min(otherScore, 5200000) / 400000)) * musicRate0 * deckRate) * boostRate;
384
- case EventLiveType.CHEERFUL:
385
- return Math.floor((114 + Math.floor(selfScore / 12500) +
386
- Math.floor(Math.min(otherScore, 4400000) / 400000) + Math.floor(Math.min(life, 1000) / 25)) *
387
- musicRate0 * deckRate) * boostRate;
388
- }
389
- }
390
- }
391
- var EventLiveType;
392
- (function (EventLiveType) {
393
- EventLiveType["SOLO"] = "solo";
394
- EventLiveType["CHALLENGE"] = "challenge";
395
- EventLiveType["MULTI"] = "multi";
396
- EventLiveType["CHEERFUL"] = "cheerful";
397
- })(EventLiveType || (EventLiveType = {}));
398
-
399
372
  class LiveCalculator {
400
373
  dataProvider;
401
374
  deckCalculator;
@@ -403,11 +376,17 @@ class LiveCalculator {
403
376
  this.dataProvider = dataProvider;
404
377
  this.deckCalculator = new DeckCalculator(dataProvider);
405
378
  }
379
+ async getMusicMeta(musicId, musicDiff) {
380
+ const musicMetas = await this.dataProvider.getMusicMeta();
381
+ return findOrThrow(musicMetas, it => it.music_id === musicId && it.difficulty === musicDiff);
382
+ }
406
383
  static getBaseScore(musicMeta, liveType) {
407
384
  switch (liveType) {
408
385
  case LiveType.SOLO:
386
+ case LiveType.CHALLENGE:
409
387
  return musicMeta.base_score;
410
388
  case LiveType.MULTI:
389
+ case LiveType.CHEERFUL:
411
390
  return musicMeta.base_score + musicMeta.fever_score;
412
391
  case LiveType.AUTO:
413
392
  return musicMeta.base_score_auto;
@@ -416,8 +395,10 @@ class LiveCalculator {
416
395
  static getSkillScore(musicMeta, liveType) {
417
396
  switch (liveType) {
418
397
  case LiveType.SOLO:
398
+ case LiveType.CHALLENGE:
419
399
  return musicMeta.skill_score_solo;
420
400
  case LiveType.MULTI:
401
+ case LiveType.CHEERFUL:
421
402
  return musicMeta.skill_score_multi;
422
403
  case LiveType.AUTO:
423
404
  return musicMeta.skill_score_auto;
@@ -471,21 +452,167 @@ class LiveCalculator {
471
452
  ret[5] = skills[skills.length - 1];
472
453
  return ret;
473
454
  }
474
- async getLiveDetail(deckCards, musicId, musicDiff, liveType, liveSkills = undefined) {
475
- const musicMetas = await this.dataProvider.getMusicMeta();
476
- const musicMeta = findOrThrow(musicMetas, it => it.music_id === musicId && it.difficulty === musicDiff);
455
+ async getLiveDetail(deckCards, musicMeta, liveType, liveSkills = undefined) {
477
456
  const deckDetail = await this.deckCalculator.getDeckDetail(deckCards);
478
457
  const skills = liveType === LiveType.MULTI
479
458
  ? undefined
480
459
  : LiveCalculator.getSoloLiveSkill(liveSkills, deckDetail.skill);
481
460
  return LiveCalculator.getLiveDetailByDeck(deckDetail, musicMeta, liveType, skills);
482
461
  }
462
+ static getLiveScoreByDeck(deckCards, honorBonus, musicMeta, liveType) {
463
+ return LiveCalculator.getLiveDetailByDeck(DeckCalculator.getDeckDetailByCards(deckCards, honorBonus), musicMeta, liveType).score;
464
+ }
483
465
  }
484
466
  var LiveType;
485
467
  (function (LiveType) {
486
468
  LiveType["SOLO"] = "solo";
487
- LiveType["MULTI"] = "multi";
488
469
  LiveType["AUTO"] = "auto";
470
+ LiveType["CHALLENGE"] = "challenge";
471
+ LiveType["MULTI"] = "multi";
472
+ LiveType["CHEERFUL"] = "cheerful";
489
473
  })(LiveType || (LiveType = {}));
490
474
 
491
- export { CardCalculator, CardEventCalculator, CardPowerCalculator, CardSkillCalculator, DeckCalculator, DeckService, EventCalculator, EventLiveType, LiveCalculator, LiveType };
475
+ class EventCalculator {
476
+ dataProvider;
477
+ cardEventCalculator;
478
+ constructor(dataProvider) {
479
+ this.dataProvider = dataProvider;
480
+ this.cardEventCalculator = new CardEventCalculator(dataProvider);
481
+ }
482
+ async getDeckEventBonus(deckCards, eventId) {
483
+ return await deckCards.reduce(async (v, it) => await v + await this.cardEventCalculator.getCardEventBonus(it, eventId), Promise.resolve(0));
484
+ }
485
+ static getEventPoint(type, selfScore, musicRate = 100, deckBonus = 0, boostRate = 1, otherScore = 0, life = 1000) {
486
+ const musicRate0 = musicRate / 100;
487
+ const deckRate = deckBonus / 100 + 1;
488
+ const otherScore0 = otherScore === 0 ? 4 * selfScore : otherScore;
489
+ switch (type) {
490
+ case LiveType.SOLO:
491
+ case LiveType.AUTO:
492
+ return Math.floor((100 + Math.floor(selfScore / 20000)) * musicRate0 * deckRate) * boostRate;
493
+ case LiveType.CHALLENGE:
494
+ return (100 + Math.floor(selfScore / 20000)) * 120;
495
+ case LiveType.MULTI:
496
+ return Math.floor((110 + Math.floor(selfScore / 17000) +
497
+ Math.floor(Math.min(otherScore0, 5200000) / 400000)) * musicRate0 * deckRate) * boostRate;
498
+ case LiveType.CHEERFUL:
499
+ return Math.floor((114 + Math.floor(selfScore / 12500) +
500
+ Math.floor(Math.min(otherScore0, 4400000) / 400000) + Math.floor(Math.min(life, 1000) / 25)) *
501
+ musicRate0 * deckRate) * boostRate;
502
+ }
503
+ }
504
+ static getDeckEventPoint(deckCards, honorBonus, musicMeta, liveType) {
505
+ const deckBonus = deckCards.reduce((v, it) => v + (it.eventBonus === undefined ? 0 : it.eventBonus), 0);
506
+ const score = LiveCalculator.getLiveScoreByDeck(deckCards, honorBonus, musicMeta, liveType);
507
+ return EventCalculator.getEventPoint(liveType, score, musicMeta.event_rate, deckBonus);
508
+ }
509
+ }
510
+
511
+ class BaseDeckRecommend {
512
+ dataProvider;
513
+ cardCalculator;
514
+ deckCalculator;
515
+ constructor(dataProvider) {
516
+ this.dataProvider = dataProvider;
517
+ this.cardCalculator = new CardCalculator(dataProvider);
518
+ this.deckCalculator = new DeckCalculator(dataProvider);
519
+ }
520
+ static filterCard(cardDetails) {
521
+ let afterFilter = cardDetails;
522
+ for (const minBonus of [65, 50, 40, 25, 15, 0]) {
523
+ const bonusFilter = cardDetails.filter(cardDetail => !(cardDetail.eventBonus !== undefined && cardDetail.eventBonus < minBonus));
524
+ if (bonusFilter.length >= 5) {
525
+ afterFilter = bonusFilter;
526
+ break;
527
+ }
528
+ }
529
+ return afterFilter;
530
+ }
531
+ static findBestCards(cardDetails, scoreFunc, isChallengeLive = false, member = 5, deckCards = [], deckCharacters = []) {
532
+ if (deckCards.length === member) {
533
+ return {
534
+ score: scoreFunc(deckCards),
535
+ deckCards
536
+ };
537
+ }
538
+ let ans = {
539
+ score: 0,
540
+ deckCards: cardDetails
541
+ };
542
+ let preCard = cardDetails[0];
543
+ for (const card of cardDetails) {
544
+ if (deckCards.includes(card))
545
+ continue;
546
+ if (!isChallengeLive && deckCharacters.includes(card.characterId))
547
+ continue;
548
+ if (deckCards.length >= 1 && deckCards[0].scoreSkill.isCertainlyLessThen(card.scoreSkill))
549
+ continue;
550
+ if (deckCards.length >= 1 && card.attr !== deckCards[0].attr && !containsAny(deckCards[0].units, card.units)) {
551
+ continue;
552
+ }
553
+ if (CardCalculator.isCertainlyLessThan(card, preCard))
554
+ continue;
555
+ preCard = card;
556
+ const result = BaseDeckRecommend.findBestCards(cardDetails, scoreFunc, isChallengeLive, member, [...deckCards, card], [...deckCharacters, card.characterId]);
557
+ if (result.score > ans.score)
558
+ ans = result;
559
+ }
560
+ return ans;
561
+ }
562
+ async recommendHighScoreDeck(userCards, musicId, musicDiff, scoreFunc, eventId = 0, isChallengeLive = false, member = 5) {
563
+ const musicMetas = await this.dataProvider.getMusicMeta();
564
+ const musicMeta = findOrThrow(musicMetas, it => it.music_id === musicId && it.difficulty === musicDiff);
565
+ const cardDetails = BaseDeckRecommend.filterCard(await this.cardCalculator.batchGetCardDetail(userCards, eventId));
566
+ const honorBonus = await this.deckCalculator.getHonorBonusPower();
567
+ const best = BaseDeckRecommend.findBestCards(cardDetails, deckCards => scoreFunc(musicMeta, honorBonus, deckCards), isChallengeLive, member);
568
+ return {
569
+ score: best.score,
570
+ deckCards: best.deckCards.map(deckCard => findOrThrow(userCards, it => it.cardId === deckCard.cardId))
571
+ };
572
+ }
573
+ static getLiveScoreFunction(liveType) {
574
+ return (musicMeta, honorBonus, deckCards) => LiveCalculator.getLiveScoreByDeck(deckCards, honorBonus, musicMeta, liveType);
575
+ }
576
+ static getEventPointFunction(liveType) {
577
+ return (musicMeta, honorBonus, deckCards) => EventCalculator.getDeckEventPoint(deckCards, honorBonus, musicMeta, liveType);
578
+ }
579
+ }
580
+
581
+ class ChallengeLiveDeckRecommend {
582
+ dataProvider;
583
+ baseRecommend;
584
+ constructor(dataProvider) {
585
+ this.dataProvider = dataProvider;
586
+ this.baseRecommend = new BaseDeckRecommend(dataProvider);
587
+ }
588
+ async recommendChallengeLiveDeck(characterId, musicId, musicDiff, member = 5) {
589
+ const userCards = await this.dataProvider.getUserData('userCards');
590
+ const cards = await this.dataProvider.getMasterData('cards');
591
+ const characterCards = userCards
592
+ .filter(userCard => findOrThrow(cards, it => it.id === userCard.cardId).characterId === characterId);
593
+ const recommend = await this.baseRecommend.recommendHighScoreDeck(characterCards, musicId, musicDiff, BaseDeckRecommend.getLiveScoreFunction(LiveType.SOLO), 0, true, member);
594
+ return {
595
+ score: recommend.score,
596
+ deck: DeckService.toUserChallengeLiveSoloDeck(recommend.deckCards, characterId)
597
+ };
598
+ }
599
+ }
600
+
601
+ class EventDeckRecommend {
602
+ dataProvider;
603
+ baseRecommend;
604
+ constructor(dataProvider) {
605
+ this.dataProvider = dataProvider;
606
+ this.baseRecommend = new BaseDeckRecommend(dataProvider);
607
+ }
608
+ async recommendEventDeck(eventId, musicId, musicDiff, liveType) {
609
+ const userCards = await this.dataProvider.getUserData('userCards');
610
+ const recommend = await this.baseRecommend.recommendHighScoreDeck(userCards, musicId, musicDiff, BaseDeckRecommend.getEventPointFunction(liveType), eventId);
611
+ return {
612
+ point: recommend.score,
613
+ deck: DeckService.toUserDeck(recommend.deckCards)
614
+ };
615
+ }
616
+ }
617
+
618
+ export { CardCalculator, CardEventCalculator, CardPowerCalculator, CardSkillCalculator, ChallengeLiveDeckRecommend, DeckCalculator, DeckService, EventCalculator, EventDeckRecommend, LiveCalculator, LiveType };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sekai-calculator",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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",