sekai-calculator 0.0.7 → 0.1.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/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # sekai-calculator
2
2
  Project SEKAI Calculator for deck power, live score, event point and more.
3
3
 
4
- ## Requirement
5
- - Node.js >= 18
4
+ This project is fully made up in TypeScript, while reducing `any` as possible.
5
+
6
+ Both ECMAScript Module `index.mjs` and CommonJS `index.cjs` are provided with types `index.d.ts` for TypeScript.
6
7
 
7
8
  ## Quick Start
8
9
  W.I.P.
package/dist/index.cjs ADDED
@@ -0,0 +1,409 @@
1
+ 'use strict';
2
+
3
+ function findOrThrow(arr, p) {
4
+ const result = arr.find(p);
5
+ if (result === undefined)
6
+ throw new Error('object not found');
7
+ return result;
8
+ }
9
+ function getOrThrow(map, key) {
10
+ const value = map.get(key);
11
+ if (value === undefined)
12
+ throw new Error('key not found');
13
+ return value;
14
+ }
15
+ function getOrDefault(map, key, defaultValue) {
16
+ const value = map.get(key);
17
+ if (value === undefined)
18
+ return defaultValue;
19
+ return value;
20
+ }
21
+ function computeWithDefault(map, key, defaultValue, action) {
22
+ map.set(key, action(getOrDefault(map, key, defaultValue)));
23
+ }
24
+ function duplicateObj(obj, times) {
25
+ const ret = [];
26
+ for (let i = 0; i < times; ++i)
27
+ ret.push(obj);
28
+ return ret;
29
+ }
30
+ function mapOrUndefined(arr, fun) {
31
+ if (arr === undefined)
32
+ return undefined;
33
+ return arr.map(fun);
34
+ }
35
+
36
+ class CardPowerCalculator {
37
+ dataProvider;
38
+ constructor(dataProvider) {
39
+ this.dataProvider = dataProvider;
40
+ }
41
+ async getCardPower(userCard, card, cardUnits, userAreaItemLevels) {
42
+ const ret = new CardDetailMap();
43
+ const basePower = await this.getCardBasePowers(userCard, card);
44
+ const characterBonus = await this.getCharacterBonusPower(basePower, card.characterId);
45
+ for (const unit of cardUnits) {
46
+ ret.set(unit, 5, 5, await this.getPower(card, basePower, characterBonus, userAreaItemLevels, unit, true, true));
47
+ ret.set(unit, 5, 1, await this.getPower(card, basePower, characterBonus, userAreaItemLevels, unit, true, false));
48
+ ret.set(unit, 1, 5, await this.getPower(card, basePower, characterBonus, userAreaItemLevels, unit, false, true));
49
+ ret.set(unit, 1, 1, await this.getPower(card, basePower, characterBonus, userAreaItemLevels, unit, false, false));
50
+ }
51
+ return ret;
52
+ }
53
+ async getPower(card, basePower, characterBonus, userAreaItemLevels, unit, sameUnit, sameAttr) {
54
+ return basePower.reduce((v, it) => v + it, 0) + characterBonus + await this.getAreaItemBonusPower(userAreaItemLevels, basePower, card.characterId, unit, sameUnit, card.attr, sameAttr);
55
+ }
56
+ async getCardBasePowers(userCard, card) {
57
+ const cardEpisodes = await this.dataProvider.getMasterData('cardEpisodes');
58
+ const masterLessons = await this.dataProvider.getMasterData('masterLessons');
59
+ const ret = [0, 0, 0];
60
+ const cardParameters = card.cardParameters.filter(it => it.cardLevel === userCard.level);
61
+ const params = ['param1', 'param2', 'param3'];
62
+ params.forEach((param, i) => {
63
+ ret[i] = findOrThrow(cardParameters, it => it.cardParameterType === param).power;
64
+ });
65
+ if (userCard.specialTrainingStatus === 'done') {
66
+ ret[0] += card.specialTrainingPower1BonusFixed;
67
+ ret[1] += card.specialTrainingPower2BonusFixed;
68
+ ret[2] += card.specialTrainingPower3BonusFixed;
69
+ }
70
+ const episodes = userCard.episodes.filter(it => it.scenarioStatus === 'already_read')
71
+ .map(it => findOrThrow(cardEpisodes, e => e.id === it.cardEpisodeId));
72
+ for (const episode of episodes) {
73
+ ret[0] += episode.power1BonusFixed;
74
+ ret[1] += episode.power2BonusFixed;
75
+ ret[2] += episode.power3BonusFixed;
76
+ }
77
+ const usedMasterLessons = masterLessons
78
+ .filter((it) => it.cardRarityType === card.cardRarityType && it.masterRank <= userCard.masterRank);
79
+ for (const masterLesson of usedMasterLessons) {
80
+ ret[0] += masterLesson.power1BonusFixed;
81
+ ret[1] += masterLesson.power2BonusFixed;
82
+ ret[2] += masterLesson.power3BonusFixed;
83
+ }
84
+ return ret;
85
+ }
86
+ async getAreaItemBonusPower(userAreaItemLevels, basePower, characterId, unit, sameUnit, attr, sameAttr) {
87
+ const usedAreaItems = userAreaItemLevels.filter(it => (it.targetUnit === 'any' || it.targetUnit === unit) &&
88
+ (it.targetCardAttr === 'any' || it.targetCardAttr === attr) &&
89
+ (it.targetGameCharacterId === undefined || it.targetGameCharacterId === characterId));
90
+ const areaItemBonus = [0, 0, 0];
91
+ for (const areaItem of usedAreaItems) {
92
+ const allMatch = (areaItem.targetUnit !== 'any' && sameUnit) || (areaItem.targetCardAttr !== 'any' && sameAttr);
93
+ areaItemBonus[0] += allMatch ? areaItem.power1AllMatchBonusRate : areaItem.power1BonusRate;
94
+ areaItemBonus[1] += allMatch ? areaItem.power2AllMatchBonusRate : areaItem.power2BonusRate;
95
+ areaItemBonus[2] += allMatch ? areaItem.power3AllMatchBonusRate : areaItem.power3BonusRate;
96
+ }
97
+ return basePower.reduce((v, it, i) => v + Math.floor(it * areaItemBonus[i] / 100), 0);
98
+ }
99
+ async getCharacterBonusPower(basePower, characterId) {
100
+ const characterRanks = await this.dataProvider.getMasterData('characterRanks');
101
+ const userCharacters = await this.dataProvider.getUserData('userCharacters');
102
+ const userCharacter = findOrThrow(userCharacters, it => it.characterId === characterId);
103
+ const characterRank = findOrThrow(characterRanks, it => it.characterId === userCharacter.characterId && it.characterRank === userCharacter.characterRank);
104
+ const rates = [characterRank.power1BonusRate, characterRank.power2BonusRate, characterRank.power3BonusRate];
105
+ return rates.reduce((v, it, i) => v + Math.floor(basePower[i] * it / 100), 0);
106
+ }
107
+ }
108
+
109
+ class CardSkillCalculator {
110
+ dataProvider;
111
+ constructor(dataProvider) {
112
+ this.dataProvider = dataProvider;
113
+ }
114
+ async getCardSkill(userCard, card) {
115
+ const scoreUpMap = new CardDetailMap();
116
+ const detail = await this.getSkillDetail(userCard, card);
117
+ if (detail.scoreUpEnhance !== undefined) {
118
+ for (let i = 1; i <= 5; ++i) {
119
+ const scoreUp = detail.scoreUp + (i === 5 ? 5 : (i - 1)) * detail.scoreUpEnhance.value;
120
+ scoreUpMap.set(detail.scoreUpEnhance.unit, i, 1, scoreUp);
121
+ }
122
+ }
123
+ scoreUpMap.set('any', 1, 1, detail.scoreUp);
124
+ return { scoreUp: scoreUpMap, lifeRecovery: detail.lifeRecovery };
125
+ }
126
+ async getSkillDetail(userCard, card) {
127
+ const skills = await this.dataProvider.getMasterData('skills');
128
+ const skill = findOrThrow(skills, it => it.id === card.skillId);
129
+ const ret = { scoreUp: 0, lifeRecovery: 0 };
130
+ for (const skillEffect of skill.skillEffects) {
131
+ const skillEffectDetail = findOrThrow(skillEffect.skillEffectDetails, it => it.level === userCard.skillLevel);
132
+ if (skillEffect.skillEffectType === 'score_up' ||
133
+ skillEffect.skillEffectType === 'score_up_condition_life' ||
134
+ skillEffect.skillEffectType === 'score_up_keep') {
135
+ const current = skillEffectDetail.activateEffectValue;
136
+ if (skillEffect.skillEnhance !== undefined) {
137
+ ret.scoreUpEnhance = {
138
+ unit: skillEffect.skillEnhance.skillEnhanceCondition.unit,
139
+ value: skillEffect.skillEnhance.activateEffectValue
140
+ };
141
+ }
142
+ ret.scoreUp = Math.max(ret.scoreUp, current);
143
+ }
144
+ else if (skillEffect.skillEffectType === 'life_recovery') {
145
+ ret.lifeRecovery += skillEffectDetail.activateEffectValue;
146
+ }
147
+ }
148
+ return ret;
149
+ }
150
+ }
151
+
152
+ class CardCalculator {
153
+ dataProvider;
154
+ powerCalculator;
155
+ skillCalculator;
156
+ constructor(dataProvider) {
157
+ this.dataProvider = dataProvider;
158
+ this.powerCalculator = new CardPowerCalculator(dataProvider);
159
+ this.skillCalculator = new CardSkillCalculator(dataProvider);
160
+ }
161
+ async getCardUnits(card) {
162
+ const gameCharacters = await this.dataProvider.getMasterData('gameCharacters');
163
+ const units = [];
164
+ if (card.supportUnit !== 'none')
165
+ units.push(card.supportUnit);
166
+ units.push(findOrThrow(gameCharacters, it => it.id === card.characterId).unit);
167
+ return units;
168
+ }
169
+ async getCardDetail(userCard, userAreaItemLevels) {
170
+ const cards = await this.dataProvider.getMasterData('cards');
171
+ const card = findOrThrow(cards, it => it.id === userCard.cardId);
172
+ const units = await this.getCardUnits(card);
173
+ const skill = await this.skillCalculator.getCardSkill(userCard, card);
174
+ const power = await this.powerCalculator.getCardPower(userCard, card, units, userAreaItemLevels);
175
+ return {
176
+ cardId: card.id,
177
+ units,
178
+ attr: card.attr,
179
+ power,
180
+ scoreSkill: skill.scoreUp,
181
+ lifeSkill: skill.lifeRecovery
182
+ };
183
+ }
184
+ }
185
+ class CardDetailMap {
186
+ min = Number.MAX_SAFE_INTEGER;
187
+ max = Number.MIN_SAFE_INTEGER;
188
+ values = new Map();
189
+ set(unit, unitMember, attrMember, value) {
190
+ this.min = Math.min(this.min, value);
191
+ this.max = Math.max(this.max, value);
192
+ this.values.set(CardDetailMap.getKey(unit, unitMember, attrMember), value);
193
+ }
194
+ get(unit, unitMember, attrMember) {
195
+ const attrMember0 = attrMember === 5 ? 5 : 1;
196
+ let best = this.getInternal(unit, unitMember, attrMember0);
197
+ if (best !== undefined)
198
+ return best;
199
+ best = this.getInternal(unit, unitMember === 5 ? 5 : 1, attrMember0);
200
+ if (best !== undefined)
201
+ return best;
202
+ best = this.getInternal('any', 1, 1);
203
+ if (best !== undefined)
204
+ return best;
205
+ throw new Error('case not found');
206
+ }
207
+ getInternal(unit, unitMember, attrMember) {
208
+ return this.values.get(CardDetailMap.getKey(unit, unitMember, attrMember));
209
+ }
210
+ static getKey(unit, unitMember, attrMember) {
211
+ return `${unit}-${unitMember}-${attrMember}`;
212
+ }
213
+ isCertainlyLessThen(another) {
214
+ return this.max < another.min;
215
+ }
216
+ }
217
+
218
+ class DeckCalculator {
219
+ dataProvider;
220
+ cardCalculator;
221
+ constructor(dataProvider) {
222
+ this.dataProvider = dataProvider;
223
+ this.cardCalculator = new CardCalculator(dataProvider);
224
+ }
225
+ async getDeck(deckId) {
226
+ const userDecks = await this.dataProvider.getUserData('userDecks');
227
+ return findOrThrow(userDecks, it => it.deckId === deckId);
228
+ }
229
+ async getDeckCards(userDeck) {
230
+ const userCards = await this.dataProvider.getUserData('userCards');
231
+ const cardIds = [userDeck.member1, userDeck.member2, userDeck.member3, userDeck.member4, userDeck.member5];
232
+ return cardIds.map(id => findOrThrow(userCards, it => it.cardId === id));
233
+ }
234
+ async getDeckCardsById(deckId) {
235
+ return await this.getDeckCards(await this.getDeck(deckId));
236
+ }
237
+ async getHonorBonusPower() {
238
+ const honors = await this.dataProvider.getMasterData('honors');
239
+ const userHonors = await this.dataProvider.getUserData('userHonors');
240
+ return userHonors
241
+ .map(userHonor => {
242
+ const honor = findOrThrow(honors, it => it.id === userHonor.honorId);
243
+ return findOrThrow(honor.levels, it => it.level === userHonor.level);
244
+ })
245
+ .reduce((v, it) => v + it.bonus, 0);
246
+ }
247
+ async getDeckDetailByCards(cardDetails, honorBonus) {
248
+ const map = new Map();
249
+ for (const cardDetail of cardDetails) {
250
+ computeWithDefault(map, cardDetail.attr, 0, it => it + 1);
251
+ cardDetail.units.forEach(key => { computeWithDefault(map, key, 0, it => it + 1); });
252
+ }
253
+ const power = cardDetails.reduce((v, cardDetail) => v + cardDetail.units.reduce((vv, unit) => Math.max(vv, cardDetail.power.get(unit, getOrThrow(map, unit), getOrThrow(map, cardDetail.attr))), 0), 0) + honorBonus;
254
+ const skill = cardDetails.map(cardDetail => {
255
+ const scoreUp = cardDetail.units.reduce((vv, unit) => Math.max(vv, cardDetail.scoreSkill.get(unit, getOrThrow(map, unit), 1)), 0);
256
+ return {
257
+ cardId: cardDetail.cardId,
258
+ scoreUp,
259
+ lifeRecovery: cardDetail.lifeSkill
260
+ };
261
+ });
262
+ return { power, skill };
263
+ }
264
+ async getDeckDetail(deckCards) {
265
+ const areaItemLevels = await this.dataProvider.getMasterData('areaItemLevels');
266
+ const userAreas = await this.dataProvider.getUserData('userAreas');
267
+ const userItemLevels = userAreas.flatMap(it => it.areaItems).map(areaItem => findOrThrow(areaItemLevels, it => it.areaItemId === areaItem.areaItemId && it.level === areaItem.level));
268
+ const cardDetails = await Promise.all(deckCards.map(async (it) => await this.cardCalculator.getCardDetail(it, userItemLevels)));
269
+ return await this.getDeckDetailByCards(cardDetails, await this.getHonorBonusPower());
270
+ }
271
+ async getDeckDetailById(deckId) {
272
+ return await this.getDeckDetail(await this.getDeckCardsById(deckId));
273
+ }
274
+ }
275
+
276
+ class CardEventCalculator {
277
+ dataProvider;
278
+ constructor(dataProvider) {
279
+ this.dataProvider = dataProvider;
280
+ }
281
+ async getEventDeckBonus(eventId, card) {
282
+ const eventDeckBonuses = await this.dataProvider.getMasterData('eventDeckBonuses');
283
+ const gameCharacterUnits = await this.dataProvider.getMasterData('gameCharacterUnits');
284
+ return eventDeckBonuses.filter(it => it.eventId === eventId &&
285
+ (it.cardAttr === undefined || it.cardAttr === card.attr))
286
+ .reduce((v, eventDeckBonus) => {
287
+ if (eventDeckBonus.gameCharacterUnitId === undefined)
288
+ return Math.max(v, eventDeckBonus.bonusRate);
289
+ const gameCharacterUnit = findOrThrow(gameCharacterUnits, unit => unit.id === eventDeckBonus.gameCharacterUnitId);
290
+ if (gameCharacterUnit.gameCharacterId !== card.characterId)
291
+ return v;
292
+ if (card.characterId < 21 || card.supportUnit === gameCharacterUnit.unit) {
293
+ return Math.max(v, eventDeckBonus.bonusRate);
294
+ }
295
+ return Math.max(v, card.supportUnit === 'none' ? eventDeckBonus.bonusRate - 10 : 0);
296
+ }, 0);
297
+ }
298
+ async getCardEventBonus(userCard, eventId) {
299
+ const cards = await this.dataProvider.getMasterData('cards');
300
+ const eventCards = await this.dataProvider.getMasterData('eventCards');
301
+ const eventRarityBonusRates = await this.dataProvider.getMasterData('eventRarityBonusRates');
302
+ let eventBonus = 0;
303
+ const card = findOrThrow(cards, it => it.id === userCard.cardId);
304
+ eventBonus += await this.getEventDeckBonus(eventId, card);
305
+ const cardBonus = eventCards.find((it) => it.eventId === eventId && it.cardId === card.id);
306
+ if (cardBonus != null) {
307
+ eventBonus += cardBonus.bonusRate;
308
+ }
309
+ const masterRankBonus = findOrThrow(eventRarityBonusRates, it => it.cardRarityType === card.cardRarityType && it.masterRank === userCard.masterRank);
310
+ eventBonus += masterRankBonus.bonusRate;
311
+ return eventBonus;
312
+ }
313
+ }
314
+
315
+ class EventCalculator {
316
+ dataProvider;
317
+ deckCalculator;
318
+ cardEventCalculator;
319
+ constructor(dataProvider) {
320
+ this.dataProvider = dataProvider;
321
+ this.deckCalculator = new DeckCalculator(dataProvider);
322
+ this.cardEventCalculator = new CardEventCalculator(dataProvider);
323
+ }
324
+ async getDeckEventBonus(userDeck, eventId) {
325
+ const deckCards = await this.deckCalculator.getDeckCards(userDeck);
326
+ return await deckCards.reduce(async (v, it) => await v + await this.cardEventCalculator.getCardEventBonus(it, eventId), Promise.resolve(0));
327
+ }
328
+ async getDeckEventBonusById(deckId, eventId) {
329
+ return await this.getDeckEventBonus(await this.deckCalculator.getDeck(deckId), eventId);
330
+ }
331
+ }
332
+
333
+ class LiveCalculator {
334
+ dataProvider;
335
+ deckCalculator;
336
+ constructor(dataProvider) {
337
+ this.dataProvider = dataProvider;
338
+ this.deckCalculator = new DeckCalculator(dataProvider);
339
+ }
340
+ getBaseScore(musicMeta, liveType) {
341
+ switch (liveType) {
342
+ case exports.LiveType.SOLO:
343
+ return musicMeta.base_score;
344
+ case exports.LiveType.MULTI:
345
+ return musicMeta.base_score + musicMeta.fever_score;
346
+ case exports.LiveType.AUTO:
347
+ return musicMeta.base_score_auto;
348
+ }
349
+ }
350
+ getSkillScore(musicMeta, liveType) {
351
+ switch (liveType) {
352
+ case exports.LiveType.SOLO:
353
+ return musicMeta.skill_score_solo;
354
+ case exports.LiveType.MULTI:
355
+ return musicMeta.skill_score_multi;
356
+ case exports.LiveType.AUTO:
357
+ return musicMeta.skill_score_auto;
358
+ }
359
+ }
360
+ async getLiveDetailByDeck(deckDetail, musicMeta, liveType, skillDetails = undefined, multiPowerSum = 0) {
361
+ const bestSkill = skillDetails === undefined;
362
+ const skills = bestSkill
363
+ ? [...deckDetail.skill, deckDetail.skill[0]].sort((a, b) => a.scoreUp - b.scoreUp)
364
+ : skillDetails;
365
+ const baseRate = this.getBaseScore(musicMeta, liveType);
366
+ const skillRate = bestSkill
367
+ ? [...this.getSkillScore(musicMeta, liveType)].sort((a, b) => a - b)
368
+ : this.getSkillScore(musicMeta, liveType);
369
+ const rate = baseRate + skills.reduce((v, it, i) => v + it.scoreUp * skillRate[i] / 100, 0);
370
+ const life = skills.reduce((v, it) => v + it.lifeRecovery, 0);
371
+ const powerSum = multiPowerSum === 0 ? 5 * deckDetail.power : multiPowerSum;
372
+ const activeBonus = liveType === exports.LiveType.MULTI ? 5 * 0.015 * powerSum : 0;
373
+ return {
374
+ score: Math.floor(rate * deckDetail.power * 4 + activeBonus),
375
+ time: musicMeta.music_time,
376
+ life: Math.min(2000, life + 1000),
377
+ tap: musicMeta.tap_count
378
+ };
379
+ }
380
+ getMultiLiveSkill(deckDetail) {
381
+ const scoreUp = deckDetail.skill.reduce((v, it, i) => v + (i === 0 ? it.scoreUp : (it.scoreUp / 5)), 0);
382
+ const lifeRecovery = deckDetail.skill[0].lifeRecovery;
383
+ return { scoreUp, lifeRecovery };
384
+ }
385
+ async getLiveDetail(deckCards, musicId, musicDiff, liveType, liveSkills = undefined) {
386
+ const musicMetas = await this.dataProvider.getMusicMeta();
387
+ const musicMeta = findOrThrow(musicMetas, it => it.music_id === musicId && it.difficulty === musicDiff);
388
+ const deckDetail = await this.deckCalculator.getDeckDetail(deckCards);
389
+ const skills = liveType === exports.LiveType.MULTI
390
+ ? duplicateObj(this.getMultiLiveSkill(deckDetail), 6)
391
+ : (mapOrUndefined(liveSkills, liveSkill => findOrThrow(deckDetail.skill, it => it.cardId === liveSkill.cardId)));
392
+ return await this.getLiveDetailByDeck(deckDetail, musicMeta, liveType, skills);
393
+ }
394
+ async getLiveDetailById(deckId, musicId, musicDiff, liveType, liveSkills = undefined) {
395
+ return await this.getLiveDetail(await this.deckCalculator.getDeckCardsById(deckId), musicId, musicDiff, liveType, liveSkills);
396
+ }
397
+ }
398
+ exports.LiveType = void 0;
399
+ (function (LiveType) {
400
+ LiveType["SOLO"] = "solo";
401
+ LiveType["MULTI"] = "multi";
402
+ LiveType["AUTO"] = "auto";
403
+ })(exports.LiveType || (exports.LiveType = {}));
404
+
405
+ exports.CardCalculator = CardCalculator;
406
+ exports.CardDetailMap = CardDetailMap;
407
+ exports.DeckCalculator = DeckCalculator;
408
+ exports.EventCalculator = EventCalculator;
409
+ exports.LiveCalculator = LiveCalculator;
package/dist/index.d.ts CHANGED
@@ -4,18 +4,6 @@ interface DataProvider {
4
4
  getMusicMeta: () => Promise<any>;
5
5
  }
6
6
 
7
- interface UserCard {
8
- cardId: number;
9
- level?: number;
10
- skillLevel?: number;
11
- masterRank: number;
12
- specialTrainingStatus?: string;
13
- episodes?: Array<{
14
- cardEpisodeId: number;
15
- scenarioStatus: string;
16
- }>;
17
- }
18
-
19
7
  interface UserDeck {
20
8
  deckId: number;
21
9
  member1: number;
@@ -28,19 +16,30 @@ interface UserDeck {
28
16
  declare class EventCalculator {
29
17
  private readonly dataProvider;
30
18
  private readonly deckCalculator;
19
+ private readonly cardEventCalculator;
31
20
  constructor(dataProvider: DataProvider);
32
- private getEventDeckBonus;
33
- getCardEventBonus(userCard: UserCard, eventId: number): Promise<number>;
34
21
  getDeckEventBonus(userDeck: UserDeck, eventId: number): Promise<number>;
35
22
  getDeckEventBonusById(deckId: number, eventId: number): Promise<number>;
36
23
  }
37
24
 
25
+ interface UserCard {
26
+ cardId: number;
27
+ level: number;
28
+ skillLevel: number;
29
+ masterRank: number;
30
+ specialTrainingStatus?: string;
31
+ episodes: Array<{
32
+ cardEpisodeId: number;
33
+ scenarioStatus: string;
34
+ }>;
35
+ }
36
+
38
37
  interface AreaItemLevel {
39
38
  areaItemId: number;
40
39
  level: number;
41
40
  targetUnit: string;
42
41
  targetCardAttr: string;
43
- targetGameCharacterId: number;
42
+ targetGameCharacterId?: number;
44
43
  power1BonusRate: number;
45
44
  power1AllMatchBonusRate: number;
46
45
  power2BonusRate: number;
@@ -50,95 +49,96 @@ interface AreaItemLevel {
50
49
  sentence: string;
51
50
  }
52
51
 
53
- interface CommonResource {
54
- resourceId?: number;
55
- resourceType: string;
56
- resourceLevel?: number;
57
- quantity: number;
52
+ declare class CardCalculator {
53
+ private readonly dataProvider;
54
+ private readonly powerCalculator;
55
+ private readonly skillCalculator;
56
+ constructor(dataProvider: DataProvider);
57
+ private getCardUnits;
58
+ getCardDetail(userCard: UserCard, userAreaItemLevels: AreaItemLevel[]): Promise<CardDetail>;
58
59
  }
59
-
60
- interface Card {
61
- id: number;
62
- seq: number;
63
- characterId: number;
64
- cardRarityType: string;
65
- specialTrainingPower1BonusFixed: number;
66
- specialTrainingPower2BonusFixed: number;
67
- specialTrainingPower3BonusFixed: number;
60
+ interface CardDetail {
61
+ cardId: number;
62
+ units: string[];
68
63
  attr: string;
69
- supportUnit: string;
70
- skillId: number;
71
- cardSkillName: string;
72
- prefix: string;
73
- assetbundleName: string;
74
- gachaPhrase: string;
75
- flavorText: string;
76
- releaseAt: number;
77
- cardParameters: Array<{
78
- id: number;
79
- cardId: number;
80
- cardLevel: number;
81
- cardParameterType: string;
82
- power: number;
83
- }>;
84
- specialTrainingCosts: CommonResource[];
85
- masterLessonAchieveResources: {
86
- releaseConditionId: number;
87
- cardId: number;
88
- masterRank: number;
89
- resources: CommonResource[];
90
- };
64
+ power: CardDetailMap;
65
+ scoreSkill: CardDetailMap;
66
+ lifeSkill: number;
67
+ eventBonus?: number;
91
68
  }
92
-
93
- interface EventCard {
94
- id: number;
95
- cardId: number;
96
- eventId: number;
97
- bonusRate: number;
69
+ declare class CardDetailMap {
70
+ min: number;
71
+ max: number;
72
+ values: Map<string, number>;
73
+ set(unit: string, unitMember: number, attrMember: number, value: number): void;
74
+ get(unit: string, unitMember: number, attrMember: number): number;
75
+ private getInternal;
76
+ static getKey(unit: string, unitMember: number, attrMember: number): string;
77
+ isCertainlyLessThen(another: CardDetailMap): boolean;
98
78
  }
99
79
 
100
- interface EventDeckBonus {
101
- id: number;
102
- eventId: number;
103
- gameCharacterUnitId?: number;
104
- cardAttr?: string;
105
- bonusRate: number;
80
+ declare class DeckCalculator {
81
+ private readonly dataProvider;
82
+ private readonly cardCalculator;
83
+ constructor(dataProvider: DataProvider);
84
+ getDeck(deckId: number): Promise<UserDeck>;
85
+ getDeckCards(userDeck: UserDeck): Promise<UserCard[]>;
86
+ getDeckCardsById(deckId: number): Promise<UserCard[]>;
87
+ private getHonorBonusPower;
88
+ getDeckDetailByCards(cardDetails: CardDetail[], honorBonus: number): Promise<DeckDetail>;
89
+ getDeckDetail(deckCards: UserCard[]): Promise<DeckDetail>;
90
+ getDeckDetailById(deckId: number): Promise<DeckDetail>;
106
91
  }
107
-
108
- interface EventRarityBonusRate {
109
- id: number;
110
- cardRarityType: string;
111
- masterRank: number;
112
- bonusRate: number;
92
+ interface DeckDetail {
93
+ power: number;
94
+ skill: SkillDetail[];
113
95
  }
114
-
115
- interface GameCharacterUnit {
116
- id: number;
117
- gameCharacterId: number;
118
- unit: string;
119
- colorCode: string;
120
- skinColorCode: string;
121
- skinShadowColorCode1: string;
122
- skinShadowColorCode2: string;
96
+ interface SkillDetail {
97
+ cardId?: number;
98
+ scoreUp: number;
99
+ lifeRecovery: number;
123
100
  }
124
101
 
125
- interface UserAreaItem {
126
- areaItemId: number;
127
- level: number;
102
+ interface MusicMeta {
103
+ music_id: number;
104
+ difficulty: string;
105
+ music_time: number;
106
+ event_rate: number;
107
+ base_score: number;
108
+ base_score_auto: number;
109
+ skill_score_solo: number[];
110
+ skill_score_auto: number[];
111
+ skill_score_multi: number[];
112
+ fever_score: number;
113
+ fever_end_time: number;
114
+ tap_count: number;
128
115
  }
129
116
 
130
- interface UserChallengeLiveSoloDeck {
131
- characterId: number;
132
- leader: number;
133
- support1: number;
134
- support2: number | null;
135
- support3: number | null;
136
- support4: number | null;
117
+ declare class LiveCalculator {
118
+ private readonly dataProvider;
119
+ private readonly deckCalculator;
120
+ constructor(dataProvider: DataProvider);
121
+ private getBaseScore;
122
+ private getSkillScore;
123
+ getLiveDetailByDeck(deckDetail: DeckDetail, musicMeta: MusicMeta, liveType: LiveType, skillDetails?: SkillDetail[] | undefined, multiPowerSum?: number): Promise<LiveDetail>;
124
+ private getMultiLiveSkill;
125
+ getLiveDetail(deckCards: UserCard[], musicId: number, musicDiff: string, liveType: LiveType, liveSkills?: LiveSkill[] | undefined): Promise<LiveDetail>;
126
+ getLiveDetailById(deckId: number, musicId: number, musicDiff: string, liveType: LiveType, liveSkills?: LiveSkill[] | undefined): Promise<LiveDetail>;
137
127
  }
138
-
139
- interface UserCharacter {
140
- characterId: number;
141
- characterRank: number;
128
+ interface LiveDetail {
129
+ score: number;
130
+ time: number;
131
+ life: number;
132
+ tap: number;
133
+ }
134
+ interface LiveSkill {
135
+ seq?: number;
136
+ cardId: number;
137
+ }
138
+ declare enum LiveType {
139
+ SOLO = "solo",
140
+ MULTI = "multi",
141
+ AUTO = "auto"
142
142
  }
143
143
 
144
- export { AreaItemLevel, Card, DataProvider, EventCalculator, EventCard, EventDeckBonus, EventRarityBonusRate, GameCharacterUnit, UserAreaItem, UserCard, UserChallengeLiveSoloDeck, UserCharacter, UserDeck };
144
+ export { CardCalculator, CardDetail, CardDetailMap, DataProvider, DeckCalculator, DeckDetail, EventCalculator, LiveCalculator, LiveDetail, LiveSkill, LiveType, SkillDetail };
package/dist/index.mjs ADDED
@@ -0,0 +1,403 @@
1
+ function findOrThrow(arr, p) {
2
+ const result = arr.find(p);
3
+ if (result === undefined)
4
+ throw new Error('object not found');
5
+ return result;
6
+ }
7
+ function getOrThrow(map, key) {
8
+ const value = map.get(key);
9
+ if (value === undefined)
10
+ throw new Error('key not found');
11
+ return value;
12
+ }
13
+ function getOrDefault(map, key, defaultValue) {
14
+ const value = map.get(key);
15
+ if (value === undefined)
16
+ return defaultValue;
17
+ return value;
18
+ }
19
+ function computeWithDefault(map, key, defaultValue, action) {
20
+ map.set(key, action(getOrDefault(map, key, defaultValue)));
21
+ }
22
+ function duplicateObj(obj, times) {
23
+ const ret = [];
24
+ for (let i = 0; i < times; ++i)
25
+ ret.push(obj);
26
+ return ret;
27
+ }
28
+ function mapOrUndefined(arr, fun) {
29
+ if (arr === undefined)
30
+ return undefined;
31
+ return arr.map(fun);
32
+ }
33
+
34
+ class CardPowerCalculator {
35
+ dataProvider;
36
+ constructor(dataProvider) {
37
+ this.dataProvider = dataProvider;
38
+ }
39
+ async getCardPower(userCard, card, cardUnits, userAreaItemLevels) {
40
+ const ret = new CardDetailMap();
41
+ const basePower = await this.getCardBasePowers(userCard, card);
42
+ const characterBonus = await this.getCharacterBonusPower(basePower, card.characterId);
43
+ for (const unit of cardUnits) {
44
+ ret.set(unit, 5, 5, await this.getPower(card, basePower, characterBonus, userAreaItemLevels, unit, true, true));
45
+ ret.set(unit, 5, 1, await this.getPower(card, basePower, characterBonus, userAreaItemLevels, unit, true, false));
46
+ ret.set(unit, 1, 5, await this.getPower(card, basePower, characterBonus, userAreaItemLevels, unit, false, true));
47
+ ret.set(unit, 1, 1, await this.getPower(card, basePower, characterBonus, userAreaItemLevels, unit, false, false));
48
+ }
49
+ return ret;
50
+ }
51
+ async getPower(card, basePower, characterBonus, userAreaItemLevels, unit, sameUnit, sameAttr) {
52
+ return basePower.reduce((v, it) => v + it, 0) + characterBonus + await this.getAreaItemBonusPower(userAreaItemLevels, basePower, card.characterId, unit, sameUnit, card.attr, sameAttr);
53
+ }
54
+ async getCardBasePowers(userCard, card) {
55
+ const cardEpisodes = await this.dataProvider.getMasterData('cardEpisodes');
56
+ const masterLessons = await this.dataProvider.getMasterData('masterLessons');
57
+ const ret = [0, 0, 0];
58
+ const cardParameters = card.cardParameters.filter(it => it.cardLevel === userCard.level);
59
+ const params = ['param1', 'param2', 'param3'];
60
+ params.forEach((param, i) => {
61
+ ret[i] = findOrThrow(cardParameters, it => it.cardParameterType === param).power;
62
+ });
63
+ if (userCard.specialTrainingStatus === 'done') {
64
+ ret[0] += card.specialTrainingPower1BonusFixed;
65
+ ret[1] += card.specialTrainingPower2BonusFixed;
66
+ ret[2] += card.specialTrainingPower3BonusFixed;
67
+ }
68
+ const episodes = userCard.episodes.filter(it => it.scenarioStatus === 'already_read')
69
+ .map(it => findOrThrow(cardEpisodes, e => e.id === it.cardEpisodeId));
70
+ for (const episode of episodes) {
71
+ ret[0] += episode.power1BonusFixed;
72
+ ret[1] += episode.power2BonusFixed;
73
+ ret[2] += episode.power3BonusFixed;
74
+ }
75
+ const usedMasterLessons = masterLessons
76
+ .filter((it) => it.cardRarityType === card.cardRarityType && it.masterRank <= userCard.masterRank);
77
+ for (const masterLesson of usedMasterLessons) {
78
+ ret[0] += masterLesson.power1BonusFixed;
79
+ ret[1] += masterLesson.power2BonusFixed;
80
+ ret[2] += masterLesson.power3BonusFixed;
81
+ }
82
+ return ret;
83
+ }
84
+ async getAreaItemBonusPower(userAreaItemLevels, basePower, characterId, unit, sameUnit, attr, sameAttr) {
85
+ const usedAreaItems = userAreaItemLevels.filter(it => (it.targetUnit === 'any' || it.targetUnit === unit) &&
86
+ (it.targetCardAttr === 'any' || it.targetCardAttr === attr) &&
87
+ (it.targetGameCharacterId === undefined || it.targetGameCharacterId === characterId));
88
+ const areaItemBonus = [0, 0, 0];
89
+ for (const areaItem of usedAreaItems) {
90
+ const allMatch = (areaItem.targetUnit !== 'any' && sameUnit) || (areaItem.targetCardAttr !== 'any' && sameAttr);
91
+ areaItemBonus[0] += allMatch ? areaItem.power1AllMatchBonusRate : areaItem.power1BonusRate;
92
+ areaItemBonus[1] += allMatch ? areaItem.power2AllMatchBonusRate : areaItem.power2BonusRate;
93
+ areaItemBonus[2] += allMatch ? areaItem.power3AllMatchBonusRate : areaItem.power3BonusRate;
94
+ }
95
+ return basePower.reduce((v, it, i) => v + Math.floor(it * areaItemBonus[i] / 100), 0);
96
+ }
97
+ async getCharacterBonusPower(basePower, characterId) {
98
+ const characterRanks = await this.dataProvider.getMasterData('characterRanks');
99
+ const userCharacters = await this.dataProvider.getUserData('userCharacters');
100
+ const userCharacter = findOrThrow(userCharacters, it => it.characterId === characterId);
101
+ const characterRank = findOrThrow(characterRanks, it => it.characterId === userCharacter.characterId && it.characterRank === userCharacter.characterRank);
102
+ const rates = [characterRank.power1BonusRate, characterRank.power2BonusRate, characterRank.power3BonusRate];
103
+ return rates.reduce((v, it, i) => v + Math.floor(basePower[i] * it / 100), 0);
104
+ }
105
+ }
106
+
107
+ class CardSkillCalculator {
108
+ dataProvider;
109
+ constructor(dataProvider) {
110
+ this.dataProvider = dataProvider;
111
+ }
112
+ async getCardSkill(userCard, card) {
113
+ const scoreUpMap = new CardDetailMap();
114
+ const detail = await this.getSkillDetail(userCard, card);
115
+ if (detail.scoreUpEnhance !== undefined) {
116
+ for (let i = 1; i <= 5; ++i) {
117
+ const scoreUp = detail.scoreUp + (i === 5 ? 5 : (i - 1)) * detail.scoreUpEnhance.value;
118
+ scoreUpMap.set(detail.scoreUpEnhance.unit, i, 1, scoreUp);
119
+ }
120
+ }
121
+ scoreUpMap.set('any', 1, 1, detail.scoreUp);
122
+ return { scoreUp: scoreUpMap, lifeRecovery: detail.lifeRecovery };
123
+ }
124
+ async getSkillDetail(userCard, card) {
125
+ const skills = await this.dataProvider.getMasterData('skills');
126
+ const skill = findOrThrow(skills, it => it.id === card.skillId);
127
+ const ret = { scoreUp: 0, lifeRecovery: 0 };
128
+ for (const skillEffect of skill.skillEffects) {
129
+ const skillEffectDetail = findOrThrow(skillEffect.skillEffectDetails, it => it.level === userCard.skillLevel);
130
+ if (skillEffect.skillEffectType === 'score_up' ||
131
+ skillEffect.skillEffectType === 'score_up_condition_life' ||
132
+ skillEffect.skillEffectType === 'score_up_keep') {
133
+ const current = skillEffectDetail.activateEffectValue;
134
+ if (skillEffect.skillEnhance !== undefined) {
135
+ ret.scoreUpEnhance = {
136
+ unit: skillEffect.skillEnhance.skillEnhanceCondition.unit,
137
+ value: skillEffect.skillEnhance.activateEffectValue
138
+ };
139
+ }
140
+ ret.scoreUp = Math.max(ret.scoreUp, current);
141
+ }
142
+ else if (skillEffect.skillEffectType === 'life_recovery') {
143
+ ret.lifeRecovery += skillEffectDetail.activateEffectValue;
144
+ }
145
+ }
146
+ return ret;
147
+ }
148
+ }
149
+
150
+ class CardCalculator {
151
+ dataProvider;
152
+ powerCalculator;
153
+ skillCalculator;
154
+ constructor(dataProvider) {
155
+ this.dataProvider = dataProvider;
156
+ this.powerCalculator = new CardPowerCalculator(dataProvider);
157
+ this.skillCalculator = new CardSkillCalculator(dataProvider);
158
+ }
159
+ async getCardUnits(card) {
160
+ const gameCharacters = await this.dataProvider.getMasterData('gameCharacters');
161
+ const units = [];
162
+ if (card.supportUnit !== 'none')
163
+ units.push(card.supportUnit);
164
+ units.push(findOrThrow(gameCharacters, it => it.id === card.characterId).unit);
165
+ return units;
166
+ }
167
+ async getCardDetail(userCard, userAreaItemLevels) {
168
+ const cards = await this.dataProvider.getMasterData('cards');
169
+ const card = findOrThrow(cards, it => it.id === userCard.cardId);
170
+ const units = await this.getCardUnits(card);
171
+ const skill = await this.skillCalculator.getCardSkill(userCard, card);
172
+ const power = await this.powerCalculator.getCardPower(userCard, card, units, userAreaItemLevels);
173
+ return {
174
+ cardId: card.id,
175
+ units,
176
+ attr: card.attr,
177
+ power,
178
+ scoreSkill: skill.scoreUp,
179
+ lifeSkill: skill.lifeRecovery
180
+ };
181
+ }
182
+ }
183
+ class CardDetailMap {
184
+ min = Number.MAX_SAFE_INTEGER;
185
+ max = Number.MIN_SAFE_INTEGER;
186
+ values = new Map();
187
+ set(unit, unitMember, attrMember, value) {
188
+ this.min = Math.min(this.min, value);
189
+ this.max = Math.max(this.max, value);
190
+ this.values.set(CardDetailMap.getKey(unit, unitMember, attrMember), value);
191
+ }
192
+ get(unit, unitMember, attrMember) {
193
+ const attrMember0 = attrMember === 5 ? 5 : 1;
194
+ let best = this.getInternal(unit, unitMember, attrMember0);
195
+ if (best !== undefined)
196
+ return best;
197
+ best = this.getInternal(unit, unitMember === 5 ? 5 : 1, attrMember0);
198
+ if (best !== undefined)
199
+ return best;
200
+ best = this.getInternal('any', 1, 1);
201
+ if (best !== undefined)
202
+ return best;
203
+ throw new Error('case not found');
204
+ }
205
+ getInternal(unit, unitMember, attrMember) {
206
+ return this.values.get(CardDetailMap.getKey(unit, unitMember, attrMember));
207
+ }
208
+ static getKey(unit, unitMember, attrMember) {
209
+ return `${unit}-${unitMember}-${attrMember}`;
210
+ }
211
+ isCertainlyLessThen(another) {
212
+ return this.max < another.min;
213
+ }
214
+ }
215
+
216
+ class DeckCalculator {
217
+ dataProvider;
218
+ cardCalculator;
219
+ constructor(dataProvider) {
220
+ this.dataProvider = dataProvider;
221
+ this.cardCalculator = new CardCalculator(dataProvider);
222
+ }
223
+ async getDeck(deckId) {
224
+ const userDecks = await this.dataProvider.getUserData('userDecks');
225
+ return findOrThrow(userDecks, it => it.deckId === deckId);
226
+ }
227
+ async getDeckCards(userDeck) {
228
+ const userCards = await this.dataProvider.getUserData('userCards');
229
+ const cardIds = [userDeck.member1, userDeck.member2, userDeck.member3, userDeck.member4, userDeck.member5];
230
+ return cardIds.map(id => findOrThrow(userCards, it => it.cardId === id));
231
+ }
232
+ async getDeckCardsById(deckId) {
233
+ return await this.getDeckCards(await this.getDeck(deckId));
234
+ }
235
+ async getHonorBonusPower() {
236
+ const honors = await this.dataProvider.getMasterData('honors');
237
+ const userHonors = await this.dataProvider.getUserData('userHonors');
238
+ return userHonors
239
+ .map(userHonor => {
240
+ const honor = findOrThrow(honors, it => it.id === userHonor.honorId);
241
+ return findOrThrow(honor.levels, it => it.level === userHonor.level);
242
+ })
243
+ .reduce((v, it) => v + it.bonus, 0);
244
+ }
245
+ async getDeckDetailByCards(cardDetails, honorBonus) {
246
+ const map = new Map();
247
+ for (const cardDetail of cardDetails) {
248
+ computeWithDefault(map, cardDetail.attr, 0, it => it + 1);
249
+ cardDetail.units.forEach(key => { computeWithDefault(map, key, 0, it => it + 1); });
250
+ }
251
+ const power = cardDetails.reduce((v, cardDetail) => v + cardDetail.units.reduce((vv, unit) => Math.max(vv, cardDetail.power.get(unit, getOrThrow(map, unit), getOrThrow(map, cardDetail.attr))), 0), 0) + honorBonus;
252
+ const skill = cardDetails.map(cardDetail => {
253
+ const scoreUp = cardDetail.units.reduce((vv, unit) => Math.max(vv, cardDetail.scoreSkill.get(unit, getOrThrow(map, unit), 1)), 0);
254
+ return {
255
+ cardId: cardDetail.cardId,
256
+ scoreUp,
257
+ lifeRecovery: cardDetail.lifeSkill
258
+ };
259
+ });
260
+ return { power, skill };
261
+ }
262
+ async getDeckDetail(deckCards) {
263
+ const areaItemLevels = await this.dataProvider.getMasterData('areaItemLevels');
264
+ const userAreas = await this.dataProvider.getUserData('userAreas');
265
+ const userItemLevels = userAreas.flatMap(it => it.areaItems).map(areaItem => findOrThrow(areaItemLevels, it => it.areaItemId === areaItem.areaItemId && it.level === areaItem.level));
266
+ const cardDetails = await Promise.all(deckCards.map(async (it) => await this.cardCalculator.getCardDetail(it, userItemLevels)));
267
+ return await this.getDeckDetailByCards(cardDetails, await this.getHonorBonusPower());
268
+ }
269
+ async getDeckDetailById(deckId) {
270
+ return await this.getDeckDetail(await this.getDeckCardsById(deckId));
271
+ }
272
+ }
273
+
274
+ class CardEventCalculator {
275
+ dataProvider;
276
+ constructor(dataProvider) {
277
+ this.dataProvider = dataProvider;
278
+ }
279
+ async getEventDeckBonus(eventId, card) {
280
+ const eventDeckBonuses = await this.dataProvider.getMasterData('eventDeckBonuses');
281
+ const gameCharacterUnits = await this.dataProvider.getMasterData('gameCharacterUnits');
282
+ return eventDeckBonuses.filter(it => it.eventId === eventId &&
283
+ (it.cardAttr === undefined || it.cardAttr === card.attr))
284
+ .reduce((v, eventDeckBonus) => {
285
+ if (eventDeckBonus.gameCharacterUnitId === undefined)
286
+ return Math.max(v, eventDeckBonus.bonusRate);
287
+ const gameCharacterUnit = findOrThrow(gameCharacterUnits, unit => unit.id === eventDeckBonus.gameCharacterUnitId);
288
+ if (gameCharacterUnit.gameCharacterId !== card.characterId)
289
+ return v;
290
+ if (card.characterId < 21 || card.supportUnit === gameCharacterUnit.unit) {
291
+ return Math.max(v, eventDeckBonus.bonusRate);
292
+ }
293
+ return Math.max(v, card.supportUnit === 'none' ? eventDeckBonus.bonusRate - 10 : 0);
294
+ }, 0);
295
+ }
296
+ async getCardEventBonus(userCard, eventId) {
297
+ const cards = await this.dataProvider.getMasterData('cards');
298
+ const eventCards = await this.dataProvider.getMasterData('eventCards');
299
+ const eventRarityBonusRates = await this.dataProvider.getMasterData('eventRarityBonusRates');
300
+ let eventBonus = 0;
301
+ const card = findOrThrow(cards, it => it.id === userCard.cardId);
302
+ eventBonus += await this.getEventDeckBonus(eventId, card);
303
+ const cardBonus = eventCards.find((it) => it.eventId === eventId && it.cardId === card.id);
304
+ if (cardBonus != null) {
305
+ eventBonus += cardBonus.bonusRate;
306
+ }
307
+ const masterRankBonus = findOrThrow(eventRarityBonusRates, it => it.cardRarityType === card.cardRarityType && it.masterRank === userCard.masterRank);
308
+ eventBonus += masterRankBonus.bonusRate;
309
+ return eventBonus;
310
+ }
311
+ }
312
+
313
+ class EventCalculator {
314
+ dataProvider;
315
+ deckCalculator;
316
+ cardEventCalculator;
317
+ constructor(dataProvider) {
318
+ this.dataProvider = dataProvider;
319
+ this.deckCalculator = new DeckCalculator(dataProvider);
320
+ this.cardEventCalculator = new CardEventCalculator(dataProvider);
321
+ }
322
+ async getDeckEventBonus(userDeck, eventId) {
323
+ const deckCards = await this.deckCalculator.getDeckCards(userDeck);
324
+ return await deckCards.reduce(async (v, it) => await v + await this.cardEventCalculator.getCardEventBonus(it, eventId), Promise.resolve(0));
325
+ }
326
+ async getDeckEventBonusById(deckId, eventId) {
327
+ return await this.getDeckEventBonus(await this.deckCalculator.getDeck(deckId), eventId);
328
+ }
329
+ }
330
+
331
+ class LiveCalculator {
332
+ dataProvider;
333
+ deckCalculator;
334
+ constructor(dataProvider) {
335
+ this.dataProvider = dataProvider;
336
+ this.deckCalculator = new DeckCalculator(dataProvider);
337
+ }
338
+ getBaseScore(musicMeta, liveType) {
339
+ switch (liveType) {
340
+ case LiveType.SOLO:
341
+ return musicMeta.base_score;
342
+ case LiveType.MULTI:
343
+ return musicMeta.base_score + musicMeta.fever_score;
344
+ case LiveType.AUTO:
345
+ return musicMeta.base_score_auto;
346
+ }
347
+ }
348
+ getSkillScore(musicMeta, liveType) {
349
+ switch (liveType) {
350
+ case LiveType.SOLO:
351
+ return musicMeta.skill_score_solo;
352
+ case LiveType.MULTI:
353
+ return musicMeta.skill_score_multi;
354
+ case LiveType.AUTO:
355
+ return musicMeta.skill_score_auto;
356
+ }
357
+ }
358
+ async getLiveDetailByDeck(deckDetail, musicMeta, liveType, skillDetails = undefined, multiPowerSum = 0) {
359
+ const bestSkill = skillDetails === undefined;
360
+ const skills = bestSkill
361
+ ? [...deckDetail.skill, deckDetail.skill[0]].sort((a, b) => a.scoreUp - b.scoreUp)
362
+ : skillDetails;
363
+ const baseRate = this.getBaseScore(musicMeta, liveType);
364
+ const skillRate = bestSkill
365
+ ? [...this.getSkillScore(musicMeta, liveType)].sort((a, b) => a - b)
366
+ : this.getSkillScore(musicMeta, liveType);
367
+ const rate = baseRate + skills.reduce((v, it, i) => v + it.scoreUp * skillRate[i] / 100, 0);
368
+ const life = skills.reduce((v, it) => v + it.lifeRecovery, 0);
369
+ const powerSum = multiPowerSum === 0 ? 5 * deckDetail.power : multiPowerSum;
370
+ const activeBonus = liveType === LiveType.MULTI ? 5 * 0.015 * powerSum : 0;
371
+ return {
372
+ score: Math.floor(rate * deckDetail.power * 4 + activeBonus),
373
+ time: musicMeta.music_time,
374
+ life: Math.min(2000, life + 1000),
375
+ tap: musicMeta.tap_count
376
+ };
377
+ }
378
+ getMultiLiveSkill(deckDetail) {
379
+ const scoreUp = deckDetail.skill.reduce((v, it, i) => v + (i === 0 ? it.scoreUp : (it.scoreUp / 5)), 0);
380
+ const lifeRecovery = deckDetail.skill[0].lifeRecovery;
381
+ return { scoreUp, lifeRecovery };
382
+ }
383
+ async getLiveDetail(deckCards, musicId, musicDiff, liveType, liveSkills = undefined) {
384
+ const musicMetas = await this.dataProvider.getMusicMeta();
385
+ const musicMeta = findOrThrow(musicMetas, it => it.music_id === musicId && it.difficulty === musicDiff);
386
+ const deckDetail = await this.deckCalculator.getDeckDetail(deckCards);
387
+ const skills = liveType === LiveType.MULTI
388
+ ? duplicateObj(this.getMultiLiveSkill(deckDetail), 6)
389
+ : (mapOrUndefined(liveSkills, liveSkill => findOrThrow(deckDetail.skill, it => it.cardId === liveSkill.cardId)));
390
+ return await this.getLiveDetailByDeck(deckDetail, musicMeta, liveType, skills);
391
+ }
392
+ async getLiveDetailById(deckId, musicId, musicDiff, liveType, liveSkills = undefined) {
393
+ return await this.getLiveDetail(await this.deckCalculator.getDeckCardsById(deckId), musicId, musicDiff, liveType, liveSkills);
394
+ }
395
+ }
396
+ var LiveType;
397
+ (function (LiveType) {
398
+ LiveType["SOLO"] = "solo";
399
+ LiveType["MULTI"] = "multi";
400
+ LiveType["AUTO"] = "auto";
401
+ })(LiveType || (LiveType = {}));
402
+
403
+ export { CardCalculator, CardDetailMap, DeckCalculator, EventCalculator, LiveCalculator, LiveType };
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "sekai-calculator",
3
- "version": "0.0.7",
3
+ "version": "0.1.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",
7
- "main": "dist/index.js",
8
- "module": "dist/index.es.js",
7
+ "main": "dist/index.cjs",
8
+ "module": "dist/index.mjs",
9
9
  "files": [
10
- "dist/index.js",
11
- "dist/index.es.js",
10
+ "dist/index.cjs",
11
+ "dist/index.mjs",
12
12
  "dist/index.d.ts"
13
13
  ],
14
14
  "keywords": [
package/dist/index.es.js DELETED
@@ -1,75 +0,0 @@
1
- function findOrThrow(arr, p) {
2
- const result = arr.find(p);
3
- if (result === undefined)
4
- throw new Error('object not found');
5
- return result;
6
- }
7
-
8
- class DeckCalculator {
9
- dataProvider;
10
- constructor(dataProvider) {
11
- this.dataProvider = dataProvider;
12
- }
13
- async getDeck(deckId) {
14
- const userDecks = await this.dataProvider.getUserData('userDecks');
15
- return findOrThrow(userDecks, it => it.deckId === deckId);
16
- }
17
- async getDeckCards(userDeck) {
18
- const userCards = await this.dataProvider.getUserData('userCards');
19
- const cardIds = [userDeck.member1, userDeck.member2, userDeck.member3, userDeck.member4, userDeck.member5];
20
- return cardIds.map(id => findOrThrow(userCards, it => it.cardId === id));
21
- }
22
- async getDeckCardsById(deckId) {
23
- return await this.getDeckCards(await this.getDeck(deckId));
24
- }
25
- }
26
-
27
- class EventCalculator {
28
- dataProvider;
29
- deckCalculator;
30
- constructor(dataProvider) {
31
- this.dataProvider = dataProvider;
32
- this.deckCalculator = new DeckCalculator(dataProvider);
33
- }
34
- async getEventDeckBonus(eventId, card) {
35
- const eventDeckBonuses = await this.dataProvider.getMasterData('eventDeckBonuses');
36
- const gameCharacterUnits = await this.dataProvider.getMasterData('gameCharacterUnits');
37
- return eventDeckBonuses.filter(it => it.eventId === eventId &&
38
- (it.cardAttr === undefined || it.cardAttr === card.attr))
39
- .reduce((v, eventDeckBonus) => {
40
- if (eventDeckBonus.gameCharacterUnitId === undefined)
41
- return Math.max(v, eventDeckBonus.bonusRate);
42
- const gameCharacterUnit = findOrThrow(gameCharacterUnits, unit => unit.id === eventDeckBonus.gameCharacterUnitId);
43
- if (gameCharacterUnit.gameCharacterId !== card.characterId)
44
- return v;
45
- if (card.characterId < 21 || card.supportUnit === gameCharacterUnit.unit) {
46
- return Math.max(v, eventDeckBonus.bonusRate);
47
- }
48
- return Math.max(v, card.supportUnit === 'none' ? eventDeckBonus.bonusRate - 10 : 0);
49
- }, 0);
50
- }
51
- async getCardEventBonus(userCard, eventId) {
52
- const cards = await this.dataProvider.getMasterData('cards');
53
- const eventCards = await this.dataProvider.getMasterData('eventCards');
54
- const eventRarityBonusRates = await this.dataProvider.getMasterData('eventRarityBonusRates');
55
- let eventBonus = 0;
56
- const card = findOrThrow(cards, it => it.id === userCard.cardId);
57
- eventBonus += await this.getEventDeckBonus(eventId, card);
58
- const cardBonus = eventCards.find((it) => it.eventId === eventId && it.cardId === card.id);
59
- if (cardBonus != null) {
60
- eventBonus += cardBonus.bonusRate;
61
- }
62
- const masterRankBonus = findOrThrow(eventRarityBonusRates, it => it.cardRarityType === card.cardRarityType && it.masterRank === userCard.masterRank);
63
- eventBonus += masterRankBonus.bonusRate;
64
- return eventBonus;
65
- }
66
- async getDeckEventBonus(userDeck, eventId) {
67
- const deckCards = await this.deckCalculator.getDeckCards(userDeck);
68
- return await deckCards.reduce(async (v, it) => await this.getCardEventBonus(it, eventId) + await v, Promise.resolve(0));
69
- }
70
- async getDeckEventBonusById(deckId, eventId) {
71
- return await this.getDeckEventBonus(await this.deckCalculator.getDeck(deckId), eventId);
72
- }
73
- }
74
-
75
- export { EventCalculator };
package/dist/index.js DELETED
@@ -1,77 +0,0 @@
1
- 'use strict';
2
-
3
- function findOrThrow(arr, p) {
4
- const result = arr.find(p);
5
- if (result === undefined)
6
- throw new Error('object not found');
7
- return result;
8
- }
9
-
10
- class DeckCalculator {
11
- dataProvider;
12
- constructor(dataProvider) {
13
- this.dataProvider = dataProvider;
14
- }
15
- async getDeck(deckId) {
16
- const userDecks = await this.dataProvider.getUserData('userDecks');
17
- return findOrThrow(userDecks, it => it.deckId === deckId);
18
- }
19
- async getDeckCards(userDeck) {
20
- const userCards = await this.dataProvider.getUserData('userCards');
21
- const cardIds = [userDeck.member1, userDeck.member2, userDeck.member3, userDeck.member4, userDeck.member5];
22
- return cardIds.map(id => findOrThrow(userCards, it => it.cardId === id));
23
- }
24
- async getDeckCardsById(deckId) {
25
- return await this.getDeckCards(await this.getDeck(deckId));
26
- }
27
- }
28
-
29
- class EventCalculator {
30
- dataProvider;
31
- deckCalculator;
32
- constructor(dataProvider) {
33
- this.dataProvider = dataProvider;
34
- this.deckCalculator = new DeckCalculator(dataProvider);
35
- }
36
- async getEventDeckBonus(eventId, card) {
37
- const eventDeckBonuses = await this.dataProvider.getMasterData('eventDeckBonuses');
38
- const gameCharacterUnits = await this.dataProvider.getMasterData('gameCharacterUnits');
39
- return eventDeckBonuses.filter(it => it.eventId === eventId &&
40
- (it.cardAttr === undefined || it.cardAttr === card.attr))
41
- .reduce((v, eventDeckBonus) => {
42
- if (eventDeckBonus.gameCharacterUnitId === undefined)
43
- return Math.max(v, eventDeckBonus.bonusRate);
44
- const gameCharacterUnit = findOrThrow(gameCharacterUnits, unit => unit.id === eventDeckBonus.gameCharacterUnitId);
45
- if (gameCharacterUnit.gameCharacterId !== card.characterId)
46
- return v;
47
- if (card.characterId < 21 || card.supportUnit === gameCharacterUnit.unit) {
48
- return Math.max(v, eventDeckBonus.bonusRate);
49
- }
50
- return Math.max(v, card.supportUnit === 'none' ? eventDeckBonus.bonusRate - 10 : 0);
51
- }, 0);
52
- }
53
- async getCardEventBonus(userCard, eventId) {
54
- const cards = await this.dataProvider.getMasterData('cards');
55
- const eventCards = await this.dataProvider.getMasterData('eventCards');
56
- const eventRarityBonusRates = await this.dataProvider.getMasterData('eventRarityBonusRates');
57
- let eventBonus = 0;
58
- const card = findOrThrow(cards, it => it.id === userCard.cardId);
59
- eventBonus += await this.getEventDeckBonus(eventId, card);
60
- const cardBonus = eventCards.find((it) => it.eventId === eventId && it.cardId === card.id);
61
- if (cardBonus != null) {
62
- eventBonus += cardBonus.bonusRate;
63
- }
64
- const masterRankBonus = findOrThrow(eventRarityBonusRates, it => it.cardRarityType === card.cardRarityType && it.masterRank === userCard.masterRank);
65
- eventBonus += masterRankBonus.bonusRate;
66
- return eventBonus;
67
- }
68
- async getDeckEventBonus(userDeck, eventId) {
69
- const deckCards = await this.deckCalculator.getDeckCards(userDeck);
70
- return await deckCards.reduce(async (v, it) => await this.getCardEventBonus(it, eventId) + await v, Promise.resolve(0));
71
- }
72
- async getDeckEventBonusById(deckId, eventId) {
73
- return await this.getDeckEventBonus(await this.deckCalculator.getDeck(deckId), eventId);
74
- }
75
- }
76
-
77
- exports.EventCalculator = EventCalculator;