360 lines
14 KiB
TypeScript
360 lines
14 KiB
TypeScript
import BaseModel from './BaseModel';
|
||
import { index, getModelForClass, prop, Ref, mongoose, DocumentType } from '@typegoose/typegoose';
|
||
// import Equip, { } from './Equip';
|
||
import { DEFAULT_HERO_LV } from '../consts';
|
||
import Skin, { SkinUpdate } from './Skin';
|
||
import { SearchHeroParam } from '../domain/backEndField/search';
|
||
import { Reward } from '../domain/battleField/pvp';
|
||
import { gameData, getHeroExpByLv, getHeroInitTalent } from '../pubUtils/data';
|
||
|
||
/**
|
||
* 英雄表
|
||
*/
|
||
|
||
export class Connect {
|
||
@prop({ required: true })
|
||
shipId: number;
|
||
@prop({ required: true })
|
||
level: number;
|
||
@prop({ required: true })
|
||
exp: number;
|
||
}
|
||
|
||
export class Talent {
|
||
@prop({ required: true })
|
||
id: number; // 天赋表id
|
||
@prop({ required: true })
|
||
level: number; // 激活等级
|
||
|
||
constructor(id: number, level = 1) {
|
||
this.id = id;
|
||
this.level = level;
|
||
}
|
||
}
|
||
|
||
export class HeroSkin {
|
||
@prop({ required: true })
|
||
id: number;
|
||
@prop({ required: true })
|
||
skinId: number; // fashions表的heroId字段
|
||
@prop({ ref: 'Skin', type: mongoose.Schema.Types.ObjectId })
|
||
skin: Ref<Skin>;
|
||
@prop({ required: true })
|
||
enable: boolean;
|
||
@prop({ required: true, type: Talent, _id: false })
|
||
talent: Talent[]; // 天赋树
|
||
@prop({ required: true })
|
||
usedTalentPoint: number; // 已使用的天赋点数
|
||
|
||
constructor(skin: SkinUpdate, enable = true) {
|
||
this.id = skin.id;
|
||
this.skinId = skin.skinId;
|
||
this.skin = skin._id;
|
||
this.enable = enable;
|
||
this.talent = getHeroInitTalent(skin.skinId);
|
||
this.usedTalentPoint = 0;
|
||
}
|
||
}
|
||
|
||
export class Stone {
|
||
@prop({ required: true })
|
||
id: number;
|
||
@prop({ required: true })
|
||
stone: number;
|
||
}
|
||
|
||
export class EPlace {
|
||
@prop({ required: true })
|
||
id: number;
|
||
@prop({ required: true })
|
||
equipId: number;
|
||
@prop({ required: true })
|
||
lv: number = 0;
|
||
@prop({ required: true })
|
||
quality: number = 1;
|
||
@prop({ required: true })
|
||
qualityStage: number = 0;
|
||
@prop({ required: true })
|
||
star: number = 0;
|
||
@prop({ required: true })
|
||
starStage: number = 0;
|
||
@prop({ required: true, type: Stone, _id: false })
|
||
stones: Stone[];
|
||
@prop({ required: true })
|
||
jewel: number = 0;
|
||
|
||
getInitialStone? () {
|
||
let result: Stone[] = [];
|
||
for(let id = 1; id <= 3; id++) {
|
||
result.push({ id, stone: 0 });
|
||
}
|
||
return result;
|
||
}
|
||
|
||
constructor(id: number, equipId: number) {
|
||
this.id = id;
|
||
this.equipId = equipId;
|
||
this.stones = this.getInitialStone();
|
||
}
|
||
}
|
||
|
||
@index({ seqId: 1 })
|
||
@index({ roleId: 1, hid: 1 })
|
||
export default class Hero extends BaseModel {
|
||
|
||
@prop({ required: true })
|
||
roleId: string; // 角色 id
|
||
@prop({ required: true })
|
||
roleName: string; // 角色名称
|
||
@prop({ required: true })
|
||
serverId: number; // 区服 id
|
||
|
||
@prop({ required: true })
|
||
hid: number; // 武将 id
|
||
@prop({ required: true })
|
||
hName: string; // 武将名
|
||
@prop({ required: true })
|
||
seqId: number; // 武将表自增 id
|
||
|
||
@prop({ required: true, default: 0 })
|
||
exp: number; // 经验值
|
||
@prop({ required: true, default: 1 })
|
||
lv: number; // 武将等级
|
||
@prop({ required: true, default: 0 })
|
||
ce: number; // 武将战力
|
||
|
||
@prop({ required: true, default: 1 })
|
||
star: number; // 星级
|
||
@prop({ required: true, default: 0 })
|
||
starStage: number; // 星级六维阶段
|
||
@prop({ required: true, default: 0 })
|
||
colorStar: number; // 觉醒, 彩星
|
||
@prop({ required: true, default: 0 })
|
||
colorStarStage: number; // 觉醒六维阶段
|
||
|
||
@prop({ required: true, default: 0 })
|
||
quality: number; // 品质
|
||
|
||
@prop({ required: true, default: false })
|
||
scrollActive: boolean; // 是否在名将谱中激活
|
||
@prop({ required: true, default: 0 })
|
||
scrollId: number; // 名将谱id
|
||
@prop({ required: true, default: 0 })
|
||
scrollStar: number; // 名将谱中保存的星级
|
||
@prop({ required: true, default: 0 })
|
||
scrollColorStar: number; // 名将谱中保存的觉醒等级
|
||
@prop({ required: true, default: 0 })
|
||
scrollQuality: number; // 名将谱中保存的品质
|
||
|
||
@prop({ required: true, default: 0 })
|
||
job: number; // 职业
|
||
@prop({ required: true, default: 0 })
|
||
jobStage: number; // 职阶
|
||
@prop({ required: true, default: 0 })
|
||
skinId: number; // 当前皮肤id,fashions表的heroId字段
|
||
|
||
@prop({ required: true, type: Connect, default: [], _id: false })
|
||
connections: Connect[]; // 羁绊
|
||
@prop({ required: true, type: HeroSkin, default: [], _id: false })
|
||
skins: HeroSkin[]; // 皮肤
|
||
|
||
@prop({ required: true, type: EPlace, default: [], _id: false })
|
||
ePlace: EPlace[]; // 武将装备引用数组
|
||
|
||
@prop({ required: true, type: Reward, default: [], _id: false })
|
||
consumes: Reward[]; // 消耗
|
||
|
||
@prop({ required: true, default: 0 })
|
||
artifact: number|string; // 宝物
|
||
|
||
@prop({ required: true, default: 0 })
|
||
subHid: number; // 副将
|
||
|
||
@prop({ required: true, default: 0 })
|
||
subActorId: number; // 副将
|
||
|
||
public static async findByRole(roleId: string, sort: { field: string, sortBy: number }[] = [], select?: string, getters = false) {
|
||
let sortParam = {};
|
||
for (let { field, sortBy } of sort) {
|
||
sortParam[field] = sortBy;
|
||
}
|
||
const heros: HeroType[] = await HeroModel.find({ roleId }).sort(sortParam).select(select).lean({ getters });
|
||
return heros;
|
||
}
|
||
|
||
public static async findBySeqIdRange(seqIds: Array<number>, roleId: string, lean = true) {
|
||
const hero: HeroType[] = await HeroModel.find({ seqId: { $in: seqIds }, roleId }).lean(lean);
|
||
return hero;
|
||
}
|
||
|
||
public static async findByHidRange(hids: Array<number>, roleId: string, select?: string, getters = false) {
|
||
const hero: HeroType[] = await HeroModel.find({ hid: { $in: hids }, roleId }).select(select).lean({ getters });
|
||
return hero;
|
||
}
|
||
|
||
public static async checkEquipByQuality(roleId: string, quality: number) {
|
||
const result = await HeroModel.exists({ roleId, 'ePlace.quality': { $gte: quality } });
|
||
return result;
|
||
}
|
||
|
||
public static async findMapByHidRange(hids: Array<number>, roleId: string, select?: string, getters = false) {
|
||
const hero = await HeroModel.findByHidRange(hids, roleId, select, getters);
|
||
let map = new Map<number, HeroType>();
|
||
for (let h of hero) {
|
||
map.set(h.hid, h);
|
||
}
|
||
return map;
|
||
}
|
||
|
||
public static async findBySeqIdAndRole(seqId: number, roleId: string, lean = true) {
|
||
const hero: HeroType = await HeroModel.findOne({ seqId, roleId }).lean(lean);
|
||
return hero;
|
||
}
|
||
public static async findByHidAndRole(hid: number, roleId: string, select?: string, getters = false) {
|
||
const hero: HeroType = await HeroModel.findOne({ hid, roleId }).select(select).lean({ getters });
|
||
return hero;
|
||
}
|
||
|
||
public static async findSubHero(roleId: string, subHid: number) {
|
||
const hero: HeroType = await HeroModel.findOne({ roleId, subHid }).lean();
|
||
return hero;
|
||
}
|
||
|
||
// 设置副将
|
||
public static async setSubHero(roleId: string, hid: number, subSkinId: number, subActorId: number) {
|
||
const preHero = subActorId > 0? await HeroModel.findOneAndUpdate({ roleId, subActorId }, { $set: { subActorId: 0, subHid: 0 } }, { new: true }).lean(): null;
|
||
const curHero: HeroType = await HeroModel.findOneAndUpdate({ roleId, hid }, { $set: { subHid: subSkinId, subActorId } }, { new: true }).lean();
|
||
return { preHid: preHero?.hid||0, curHero };
|
||
}
|
||
|
||
public static async addEquip(roleId: string, hid: number, ePlaceId: number, equipId: string) {
|
||
const hero: HeroType = await HeroModel.findOneAndUpdate(
|
||
{ roleId, hid, 'ePlace.id': ePlaceId },
|
||
{ $set: { 'ePlace.$.equip': equipId } },
|
||
{ new: true }).populate('ePlace.equip').lean();
|
||
return hero;
|
||
}
|
||
|
||
public static async removeEquip(roleId: string, hid: number, ePlaceId: number, lean = true) {
|
||
const hero: HeroType = await HeroModel.findOneAndUpdate(
|
||
{ roleId, hid, 'ePlace.id': ePlaceId },
|
||
{ $set: { 'ePlace.$.equip': null } },
|
||
{ new: true }).populate('ePlace.equip').lean(lean);
|
||
return hero;
|
||
}
|
||
|
||
public static getInitInfo(hid: number, heroInfo: HeroUpdate = {}): HeroUpdate {
|
||
let dicHero = gameData.hero.get(hid)
|
||
let { quality, initialStar: star, initialColorStar: colorStar, jobid: job, name: hName } = dicHero;
|
||
const doc = new HeroModel();
|
||
const update = { ...doc.toJSON(), hid, skinId: hid, hName, star, colorStar, quality, job, lv: DEFAULT_HERO_LV, exp: getHeroExpByLv(DEFAULT_HERO_LV - 1) || 0, ...heroInfo};
|
||
delete update._id;
|
||
return update
|
||
}
|
||
|
||
// public static async createHero(heroInfo: HeroUpdate, lean = true) {
|
||
// const seqId = await CounterModel.getNewCounter(COUNTER.HID) || -1;
|
||
// const update = this.getInitInfo(heroInfo.hid, { ...heroInfo, seqId });
|
||
// const hero: HeroType = await HeroModel.findOneAndUpdate({ roleId: heroInfo.roleId, hid: heroInfo.hid }, update, { upsert: true, new: true }).lean(lean);
|
||
// return hero;
|
||
// }
|
||
|
||
// public static async insertHeroes(roleId: string, roleName: string, serverId: number, heroInfos: HeroUpdate[]) {
|
||
// let insertInfos: HeroUpdate[] = [];
|
||
// for(let hero of heroInfos) {
|
||
// const seqId = await CounterModel.getNewCounter(COUNTER.HID) || -1;
|
||
// insertInfos.push({ ...hero, seqId, roleId, roleName, serverId })
|
||
// }
|
||
// await HeroModel.insertMany(insertInfos);
|
||
// return <HeroType[]>insertInfos;
|
||
// }
|
||
|
||
public static async sumHeroCe(roleId: string) {
|
||
let ce: Array<{ ce: number }> = await HeroModel.aggregate([
|
||
{ $match: { roleId } },
|
||
{ $group: { _id: null, ce: { $sum: '$ce' } } }
|
||
]);
|
||
return ce.length > 0 ? ce[0].ce : 0;
|
||
}
|
||
|
||
public static async getTopHero(roleId: string, num: number, lean = true) {
|
||
const heroes: HeroType[] = await HeroModel.find({ roleId }).limit(num).sort({ ce: -1 }).lean(lean);
|
||
return heroes;
|
||
}
|
||
|
||
public static async deleteAccount(roleId: string) {
|
||
let result = await HeroModel.deleteMany({ roleId });
|
||
return result;
|
||
}
|
||
|
||
public static async deleteHero(roleId: string, hid: number) {
|
||
let result = await HeroModel.deleteMany({ roleId, hid });
|
||
return result;
|
||
}
|
||
|
||
public static async updateHeroInfo(roleId: string, hid: number, heroUpdate: HeroUpdate, select?: string, getters = false) {
|
||
delete heroUpdate._id;
|
||
let result: HeroType = await HeroModel.findOneAndUpdate({ roleId, hid }, { $set: heroUpdate }, { new: true, upsert: true }).select(select).lean({ getters });
|
||
return result;
|
||
}
|
||
|
||
public static async unloadHeroAndEquip(roleId: string, hid: number, id: number, lean = true) {
|
||
const hero: HeroType = await HeroModel.findOneAndUpdate(
|
||
{ roleId, hid, 'ePlace.id': id },
|
||
{ $set: { 'ePlace.$.equip': null } },
|
||
{ new: true }).lean(lean);
|
||
return hero;
|
||
}
|
||
|
||
public static async getAllRank(serverId: number, select?: string, limit = 200) {
|
||
let result: HeroType[] = await HeroModel.find({ serverId }, { _id: false }).select(select).limit(limit).sort({ ce: -1, updatedAt: 1 }).lean({ getters: true });
|
||
return result;
|
||
}
|
||
|
||
public static async getMyTopHero(roleId: string, select?: string) {
|
||
let result: HeroType = await HeroModel.findOne({ roleId }, { _id: false }).select(select).sort({ ce: -1, updatedAt: 1 }).lean({ getters: true });
|
||
return result;
|
||
}
|
||
|
||
public static async getRank(hid: number, serverId: number, select?: string, limit = 200) {
|
||
let result: HeroType[] = await HeroModel.find({ serverId, hid }, { _id: false }).select(select).limit(limit).lean({ getters: true });
|
||
return result;
|
||
}
|
||
|
||
private static getSearchObj(form: SearchHeroParam) {
|
||
let searchObj = {};
|
||
if(form.roleId) searchObj['roleId'] = form.roleId;
|
||
if(form.roleName) searchObj['roleName'] = { $regex: new RegExp(form.roleName.toString(), 'i') };
|
||
if(form.hid) searchObj['hid'] = form.hid;
|
||
return searchObj
|
||
}
|
||
|
||
public static async findByCondition(page: number, pageSize: number, sortField: string = 'updatedAt', sortOrder: string = 'descend', form: SearchHeroParam = {}) {
|
||
|
||
let searchObj = this.getSearchObj(form);
|
||
let sort = {};
|
||
if(sortField && sortOrder) {
|
||
if(sortOrder == 'ascend') {
|
||
sort[sortField] = 1;
|
||
} else if (sortOrder == 'descend') {
|
||
sort[sortField] = -1;
|
||
}
|
||
}
|
||
const result: HeroType[] = await HeroModel.find(searchObj).limit(pageSize).skip((page - 1) * pageSize).sort(sort).lean({ getters: true, virtuals: true });
|
||
return result;
|
||
|
||
}
|
||
|
||
public static async countByCondition(form: SearchHeroParam = {}) {
|
||
|
||
let searchObj = this.getSearchObj(form);
|
||
const result = await HeroModel.count(searchObj);
|
||
return result;
|
||
}
|
||
}
|
||
|
||
export const HeroModel = getModelForClass(Hero);
|
||
|
||
export interface HeroType extends Pick<DocumentType<Hero>, keyof Hero> { };
|
||
export type HeroUpdate = Partial<HeroType>; // 将所有字段变成可选项
|