kt-template-online-api/src/qqbot/plugins/bangDream/tsugu/models/player.ts

476 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { bangDreamBestdoriProvider } from '@/qqbot/plugins/bangDream/tsugu/data-clients/bestdori-provider';
import { Server } from '@/qqbot/plugins/bangDream/tsugu/models/server';
import {
Card,
addStat,
Stat,
} from '@/qqbot/plugins/bangDream/tsugu/models/card';
import { AreaItem } from '@/qqbot/plugins/bangDream/tsugu/models/area-item';
import { Event } from '@/qqbot/plugins/bangDream/tsugu/models/event';
import { difficultyNameList } from '@/qqbot/plugins/bangDream/tsugu/models/song';
import { logger } from '@/qqbot/plugins/bangDream/tsugu/runtime/logger';
/*
- mode=0 只从缓存取,无需等待队列立即返回缓存数据
- mode=1 同上立即返回缓存数据,但同时再放入队列请求服务器后台更新
- mode=2 放入队列等待新鲜数据,如果耗时过长就返回缓存数据
- mode=3 放入队列持续等待新鲜数据
*/
export class Player {
playerId: number;
isExist: boolean = false;
initError: boolean = false;
cache: boolean;
time: number;
profile: {
userId: string;
userName: string;
rank: number;
degree: number;
introduction: string;
publishTotalDeckPowerFlg: boolean;
publishBandRankFlg: boolean;
publishMusicClearedFlg: boolean;
publishMusicFullComboFlg: boolean;
publishHighScoreRatingFlg: boolean;
publishUserIdFlg: boolean;
searchableFlg: boolean;
publishUpdatedAtFlg: boolean;
friendApplicableFlg: boolean;
publishMusicAllPerfectFlg: boolean;
publishDeckRankFlg: boolean;
publishStageChallengeAchievementConditionsFlg: boolean;
publishStageChallengeFriendRankingFlg: boolean;
publishCharacterRankFlg: boolean;
mainDeckUserSituations: {
entries: Array<{
userId: string;
situationId: number;
level: number;
exp: number;
createdAt: string;
addExp: number;
trainingStatus: 'not_doing' | 'done';
duplicateCount: number;
illust: 'after_training' | 'normal';
skillExp: number;
skillLevel: number;
userAppendParameter: {
userId: string;
situationId: number;
performance: number;
technique: number;
visual: number;
characterPotentialPerformance: number;
characterPotentialTechnique: number;
characterPotentialVisual: number;
characterBonusPerformance?: number;
characterBonusTechnique?: number;
characterBonusVisual?: number;
};
limitBreakRank: number;
}>;
};
enabledUserAreaItems: {
entries: Array<{
userId: string;
areaItemId: number;
areaItemCategory: number;
level: number;
}>;
};
bandRankMap: {
entries: {
[bandId: number]: number;
};
};
userHighScoreRating: {
[bandHighScoreRatingName: string]: {
entries: Array<{
musicId: number;
difficulty: string;
rating: number;
}>;
};
};
mainUserDeck: {
deckId: number;
deckName: string;
leader: number;
member1: number;
member2: number;
member3: number;
member4: number;
deckType: string;
};
userProfileSituation: {
userId: string;
situationId: number;
illust: 'after_training' | 'normal';
viewProfileSituationStatus: 'deck_leader' | 'profile_situation';
};
userProfileDegreeMap: {
entries: {
first: {
userId: string;
profileDegreeType: string;
degreeId: number;
};
second: {
userId: string;
profileDegreeType: string;
degreeId: number;
};
};
};
userTwitter?: {
twitterId: string;
twitterName: string;
screenName: string;
url: string;
profileImageUrl: string;
};
userDeckTotalRatingMap: {
entries: {
[bandId: number]: {
rank: string;
score: number;
level: number;
lowerRating: number;
upperRating: number;
};
};
};
stageChallengeAchievementConditionsMap: {
entries: {
[bandId: number]: number;
};
};
userMusicClearInfoMap: {
entries: {
[difficultyName: string]: {
clearedMusicCount: number;
fullComboMusicCount: number;
allPerfectMusicCount: number;
};
};
};
userCharacterRankMap: {
entries: {
[characterId: number]: {
rank: number;
exp: number;
addExp: number;
nextExp: number;
totalExp: number;
releasedPotentialLevel: number;
};
};
};
//其他
//卡牌列表
cardList: Card[];
//插画
userIllustration: { cardId: number; trainingStatus: boolean };
};
server: Server;
isInitfull: boolean = false;
/**
* 构造 Player 实例,并初始化该模型的本地基础字段。
*
* @param playerId - 玩家ID参数。
* @param server - 目标服务器。
*/
constructor(playerId: number, server: Server) {
this.playerId = playerId;
this.server = server;
}
/**
* 在 Player 模型中加载远端完整详情并标记初始化状态。
*
* @param useCache - use缓存参数未传入时使用默认值。
* @param mode - mode参数未传入时使用默认值。
*/
async initFull(useCache: boolean = false, mode: 0 | 1 | 2 | 3 = 2) {
if (this.isInitfull) {
return;
}
const cacheTime = useCache ? 1 / 0 : 0;
let playerData;
try {
playerData = await bangDreamBestdoriProvider.getJson(
`/api/player/${Server[this.server]}/${this.playerId}?mode=${mode}`,
{ cacheTime, retryCount: 1 },
);
if (mode == 1 && !isFinite(cacheTime)) {
// 后台更新玩家信息,确保下一次用最新的缓存去显示
bangDreamBestdoriProvider
.getJson(
`/api/player/${Server[this.server]}/${this.playerId}?mode=${mode}`,
{ cacheTime: 300, retryCount: 1 },
)
.catch((err) => {
logger('InitPlayer', err);
});
}
} catch {
this.isExist = false;
this.initError = true;
return;
}
if (!playerData['result'] || playerData['data']['profile'] == null) {
this.isExist = false;
this.initError = true;
return;
}
this.isExist = true;
this.cache = playerData['data']['cache'];
this.time = playerData['data']['time'];
this.profile = playerData['data']['profile'];
//卡牌列表
this.profile.cardList = [];
for (
let i = 0;
i < this.profile.mainDeckUserSituations.entries.length;
i++
) {
const cardData = this.profile.mainDeckUserSituations.entries[i];
const card = new Card(cardData.situationId);
this.profile.cardList.push(card);
}
//插画
this.profile.userIllustration = this.getUserIllustration();
//修复新旧API难度信息不兼容问题
if (this.profile.userMusicClearInfoMap == undefined) {
this.profile.userMusicClearInfoMap = { entries: {} };
for (let i = 0; i < difficultyNameList.length; i++) {
const difficultyName = difficultyNameList[i];
this.profile.userMusicClearInfoMap.entries[difficultyName] = {
clearedMusicCount: 0,
fullComboMusicCount: 0,
allPerfectMusicCount: 0,
};
}
if (this.profile['clearedMusicCountMap']?.['entries'] != undefined) {
for (let i = 0; i < difficultyNameList.length; i++) {
const difficultyName = difficultyNameList[i];
const number =
this.profile['clearedMusicCountMap']['entries'][difficultyName] ||
0;
this.profile.userMusicClearInfoMap.entries[difficultyName][
'clearedMusicCount'
] = number;
}
}
if (this.profile['fullComboMusicCountMap']?.['entries'] != undefined) {
for (let i = 0; i < difficultyNameList.length; i++) {
const difficultyName = difficultyNameList[i];
const number =
this.profile['fullComboMusicCountMap']['entries'][difficultyName] ||
0;
this.profile.userMusicClearInfoMap.entries[difficultyName][
'fullComboMusicCount'
] = number;
}
}
if (this.profile['allPerfectMusicCountMap']?.['entries'] != undefined) {
for (let i = 0; i < difficultyNameList.length; i++) {
const difficultyName = difficultyNameList[i];
const number =
this.profile['allPerfectMusicCountMap']['entries'][
difficultyName
] || 0;
this.profile.userMusicClearInfoMap.entries[difficultyName][
'allPerfectMusicCount'
] = number;
}
}
}
this.isInitfull = true;
}
/**
* 在 Player 模型中计算数值。
*
* @param event - 活动参数,未传入时使用默认值。
* @returns 异步处理结果。
*/
async calcStat(event?: Event): Promise<Stat> {
if (this.profile.publishTotalDeckPowerFlg == false) {
return {
performance: 0,
technique: 0,
visual: 0,
};
}
//计算卡牌本身属性
const cardDataList = this.profile.mainDeckUserSituations.entries;
const cardStatList = [];
const cardStat: Stat = {
//所有卡牌的属性总和
performance: 0,
technique: 0,
visual: 0,
};
for (let i = 0; i < cardDataList.length; i++) {
const cardData = cardDataList[i];
const card = new Card(cardData.situationId);
const tempStat = await card.calcStat(cardData);
addStat(cardStat, tempStat);
cardStatList.push(tempStat);
}
//计算区域道具属性
const extraStat: Stat = {
//所有卡牌的额外属性总和
performance: 0,
technique: 0,
visual: 0,
};
const areaItemList = this.profile.enabledUserAreaItems.entries;
for (let i = 0; i < areaItemList.length; i++) {
const element = areaItemList[i];
const areaItem = new AreaItem(element.areaItemCategory);
const areaItemLevel = element.level;
for (let j = 0; j < cardStatList.length; j++) {
const cardStat = cardStatList[j];
const card = this.profile.cardList[j];
const tempStat = areaItem.calcStat(
card,
areaItemLevel,
cardStat,
this.server,
);
addStat(extraStat, tempStat);
}
}
const eventStat: Stat = {
//所有卡牌的额外属性总和
performance: 0,
technique: 0,
visual: 0,
};
if (event != undefined) {
for (let i = 0; i < cardStatList.length; i++) {
const cardStat = cardStatList[i];
const card = this.profile.cardList[i];
let isCharacter = false;
let isAttribute = false;
for (let j = 0; i < event.characters.length; j++) {
const characterPercent = event.characters[j];
if (card.characterId == characterPercent.characterId) {
const tempStat = {
performance:
(cardStat.performance * characterPercent.percent) / 100,
technique: (cardStat.technique * characterPercent.percent) / 100,
visual: (cardStat.visual * characterPercent.percent) / 100,
};
addStat(eventStat, tempStat);
isCharacter = true;
}
}
for (let j = 0; j < event.attributes.length; j++) {
const attributePercent = event.attributes[j];
if (card.attribute == attributePercent.attribute) {
const tempStat = {
performance:
(cardStat.performance * attributePercent.percent) / 100,
technique: (cardStat.technique * attributePercent.percent) / 100,
visual: (cardStat.visual * attributePercent.percent) / 100,
};
addStat(eventStat, tempStat);
isAttribute = true;
}
}
if (
isCharacter &&
isAttribute &&
event.eventAttributeAndCharacterBonus != undefined
) {
if (event.eventAttributeAndCharacterBonus.parameterPercent != 0) {
const tempStat = {
performance:
(cardStat.performance *
event.eventAttributeAndCharacterBonus.parameterPercent) /
100,
technique:
(cardStat.technique *
event.eventAttributeAndCharacterBonus.parameterPercent) /
100,
visual:
(cardStat.visual *
event.eventAttributeAndCharacterBonus.parameterPercent) /
100,
};
addStat(eventStat, tempStat);
}
}
}
addStat(extraStat, eventStat);
}
//相加
addStat(cardStat, extraStat);
//将cardStat所有参数向下取整
cardStat.performance = cardStat.performance;
cardStat.technique = cardStat.technique;
cardStat.visual = cardStat.visual;
return cardStat;
}
/**
* 在 Player 模型中计算HSR。
*
* @returns 计算后的数值。
*/
calcHSR(): number {
let hsr = 0;
const userHighScoreRating = this.profile.userHighScoreRating;
for (const i in userHighScoreRating) {
if (Object.prototype.hasOwnProperty.call(userHighScoreRating, i)) {
const userBandHighScoreRating = userHighScoreRating[i].entries;
for (let j = 0; j < userBandHighScoreRating.length; j++) {
const element = userBandHighScoreRating[j];
hsr += element.rating;
}
}
}
return hsr;
}
/**
* 在 Player 模型中获取UserIllustration。
*
* @returns 判断结果。
*/
getUserIllustration(): { cardId: number; trainingStatus: boolean } {
let illustrationCardId: number;
let trainingStatus: boolean;
let viewProfileSituationStatus: string;
if (Object.keys(this.profile.userProfileSituation).length != 0) {
viewProfileSituationStatus =
this.profile.userProfileSituation.viewProfileSituationStatus;
} else {
viewProfileSituationStatus = 'deck_leader';
}
if (viewProfileSituationStatus == 'deck_leader') {
illustrationCardId =
this.profile.mainDeckUserSituations.entries[0].situationId;
trainingStatus =
this.profile.mainDeckUserSituations.entries[0].illust ===
'after_training'
? true
: false;
} else {
illustrationCardId = this.profile.userProfileSituation.situationId;
trainingStatus =
this.profile.userProfileSituation.illust === 'after_training'
? true
: false;
}
return { cardId: illustrationCardId, trainingStatus: trainingStatus };
}
}