import BaseModel from './BaseModel'; import { index, getModelForClass, prop, DocumentType, mongoose, Ref } from '@typegoose/typegoose'; import { LadderDefense, LadderOppPlayerInDB } from '../domain/battleField/ladder'; import Role from './Role'; @index({ roleId: 1 }) @index({ serverId: 1, rank: 1 }) export default class LadderMatch extends BaseModel { @prop({ required: true, default: 0 }) serverId: number; // 角色 id @prop({ required: true, default: '' }) roleId: string; // 角色 id @prop({ ref: 'Role', type: mongoose.Schema.Types.ObjectId }) role: Ref; @prop({ required: true, default: 0 }) rank: number; // 排名 @prop({ required: true, default: 0 }) oldRank: number; // 交换前排名 @prop({ required: true, default: 0 }) historyRank: number; // 历史最高排名 @prop({ required: true, default: 0 }) locked: number; // 是否被挑战 @prop({ required: true, default: null, _id: false }) defense: LadderDefense; @prop({ required: true, default: false }) hasDefense: boolean; // 购买次数 @prop({ required: true, type: LadderOppPlayerInDB, default: [], _id: false }) oppPlayers: LadderOppPlayerInDB[]; @prop({ required: true, default: () => { return new Date() } }) refDaily: Date; // 每日刷新,控制refOppCnt和setAttackCnt @prop({ required: true, default: 0 }) challengeCnt: number; // 挑战次数 @prop({ required: true, default: 0 }) buyCnt: number; // 购买次数 @prop({ required: true, default: 0 }) refOppCnt: number; // 刷新对手次数 public static async findByRoleId(roleId: string) { const result: LadderMatchType = await LadderMatchModel.findOne({ roleId }).lean(); return result; } public static async findByRank(serverId: number, rank: number) { const result: LadderMatchType = await LadderMatchModel.findOne({ serverId, rank }).lean(); return result; } public static async isRankExist(serverId: number, rank: number) { const result = await LadderMatchModel.exists({ serverId, rank }); return result; } public static async findByRoleIdAndInclude(roleId: string) { const result: LadderMatchType = await LadderMatchModel.findOne({ roleId }) .populate('role', 'roleId roleName head frame spine heads frames spines title lv updatedAt') .populate('defense.heroes.hero', 'hid skinId quality star colorStar lv skins job artifact subHid subActorId') .lean(); return result; } public static async createLadder(params: LadderUpdateInter) { const doc = new LadderMatchModel(); const update = Object.assign(doc.toJSON(), params); const defense: LadderMatchType = await LadderMatchModel.findOneAndUpdate({ roleId: params.roleId }, { $set: update}, { upsert: true, new: true }).lean(); return defense; } public static async updateByRoleId(roleId: string, params: LadderUpdateInter) { const defense: LadderMatchType = await LadderMatchModel.findOneAndUpdate({ roleId }, { $set: params}, { new: true }).lean(); return defense; } public static async updateByRoleIdAndInclude(roleId: string, params: LadderUpdateInter) { const defense: LadderMatchType = await LadderMatchModel.findOneAndUpdate({ roleId }, { $set: params}, { new: true }) .populate('role', 'roleId roleName head frame spine heads frames spines title lv updatedAt') .populate('defense.heroes.hero', 'hid skinId quality star colorStar lv skins job artifact subHid subActorId') .lean(); return defense; } public static async findAll(serverId: number) { let result: LadderMatchType[] = [], latestTime = new Date(); while(latestTime) { let ladderMatch = await LadderMatchModel.find({ serverId, rank: { $gt: 0 }, createdAt: { $lt: latestTime } }) .sort({ createdAt: -1 }) .populate('role', 'roleId roleName head frame spine heads frames spines title lv updatedAt') .limit(1000); result.push(...ladderMatch); latestTime = ladderMatch[ladderMatch.length - 1]?.createdAt; } return result } public static async lock(serverId: number, roleId: string, rank: number) { const defense: LadderMatchType = await LadderMatchModel.findOneAndUpdate({ serverId, roleId, rank, locked: 0 }, { $set: { locked: 1 }}, { new: true }) .populate('role', 'roleId roleName head frame spine heads frames spines title lv updatedAt') .populate('defense.heroes.hero', 'hid skinId quality star colorStar lv skins job artifact subHid subActorId') .lean(); return defense; } public static async unlock(serverId: number, roleId: string) { const defense: LadderMatchType = await LadderMatchModel.findOneAndUpdate({ serverId, roleId }, { $set: { locked: 0 }}, { new: true }).lean(); return defense; } public static async changeRank(isSuccess: boolean, attackInfo: { roleId: string }, defenseInfo: { isRobot: boolean, roleId: string, oldRank: number }, times = 0) { if(times > 10) return { isChange: false } const session = await mongoose.connection.startSession(); session.startTransaction() try { let newAtkLadderMatch: LadderMatchType, newDefLadderMatch: LadderMatchType, isChange = false, endTime = Date.now(); if(defenseInfo.isRobot) { let atkLadderMatch = await LadderMatchModel.findOne({ roleId: attackInfo.roleId }).lean(); if(isSuccess && atkLadderMatch && (atkLadderMatch.rank == 0 || atkLadderMatch.rank > defenseInfo.oldRank)) { newAtkLadderMatch = await LadderMatchModel.findOneAndUpdate({ roleId: attackInfo.roleId }, { $set: { rank: defenseInfo.oldRank, oldRank: atkLadderMatch.rank } }, { new: true, session }).lean(); isChange = true; endTime = Date.now(); } else { newAtkLadderMatch = atkLadderMatch; } } else { let atkLadderMatch = await LadderMatchModel.findOne({ roleId: attackInfo.roleId }).lean(); let defLadderMatch = await LadderMatchModel.findOne({ roleId: defenseInfo.roleId }).lean(); if(isSuccess && atkLadderMatch && defLadderMatch && (atkLadderMatch.rank == 0 || atkLadderMatch.rank > defLadderMatch.rank)) { newAtkLadderMatch = await LadderMatchModel.findOneAndUpdate({ roleId: attackInfo.roleId }, { $set: { rank: defLadderMatch.rank, oldRank: atkLadderMatch.rank } }, { new: true, session }).lean(); newDefLadderMatch = await LadderMatchModel.findOneAndUpdate({ roleId: defenseInfo.roleId }, { $set: { rank: atkLadderMatch.rank, oldRank: defLadderMatch.rank } }, { new: true, session }).lean(); isChange = true; endTime = Date.now(); } else { newAtkLadderMatch = atkLadderMatch; newDefLadderMatch = defLadderMatch; } } await session.commitTransaction() return { isChange, atkLadderMatch: newAtkLadderMatch, defLadderMatch: newDefLadderMatch, endTime } } catch(e) { return await this.changeRank(isSuccess, attackInfo, defenseInfo, times + 1); } finally { session.endSession() } } public static async updateCe(roleId: string, hid: number, ce: number) { const rec: LadderMatchType = await LadderMatchModel.findOneAndUpdate( { roleId, hasDefense: true }, { $set: { 'defense.heroes.$[t].ce': ce } }, { arrayFilters: [{"t.actorId": hid}] }).lean(); return rec } public static async removeBySub(roleId: string, subHid: number) { await LadderMatchModel.updateMany({ roleId, hasDefense: true }, { $pull: { 'defense.heroes': { actorId: subHid } } }); } } export const LadderMatchModel = getModelForClass(LadderMatch); export interface LadderMatchType extends Pick, keyof LadderMatch> { }; export type LadderUpdateInter = Partial;