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 +172 -43
- package/dist/index.d.ts +36 -17
- package/dist/index.mjs +171 -44
- package/package.json +1 -1
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
|
|
155
|
-
|
|
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
|
|
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,
|
|
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[],
|
|
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
|
-
|
|
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,
|
|
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
|
|
153
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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 };
|