import { ITID, CONSUME_TYPE, ITEM_TABLE, REDIS_KEY, TASK_TYPE, CURRENCY, CURRENCY_TYPE, MAIL_TYPE, HANDLE_REWARD_TYPE, HERO_SYSTEM_TYPE, CURRENCY_BY_TYPE, ITEM_CHANGE_REASON, TA_USERSET_TYPE, TA_EVENT } from './../consts'; import { EquipModel, EquipType } from './../db/Equip'; import { getRandSingleEelm, resResult } from '../pubUtils/util'; import { RoleModel, RoleType } from '../db/Role'; import { setAp } from './actionPointService'; import { pushCalPlayerCe, pushCalAllHeroCe, calPlayerCeAndSave } from './playerCeService'; import { ItemModel, ItemType } from '../db/Item'; import { STATUS } from '../consts/statusCode'; import { pinus } from 'pinus'; import { addEquips, addBags, addSkin, addFigure, unlockFigure as pubUnlockFigure, transPiece, getGoldObject, getCoinObject, getApObject } from '../pubUtils/itemUtils'; import { ItemInter, RewardInter, } from '../pubUtils/interface'; import { gameData } from '../pubUtils/data'; import { uniq } from 'underscore'; import { HeroModel, HeroType, HeroUpdate } from '../db/Hero'; import { Figure } from '../domain/dbGeneral'; import { Rank } from './rankService'; import { checkActivityTask, checkTaskWithHero, pushActivityUpdate, pushTaskUpdate } from './taskService'; import { CreateHeroParam, HeroShowParam } from '../domain/roleField/hero'; import { HeroSkin } from '../db/Hero'; import { errlogger } from '../util/logger'; import { BAG } from '../pubUtils/dicParam'; import { sendMailByContent } from './mailService'; import { calEquipSeids } from '../pubUtils/playerCe'; import { CreateHeroes } from '../pubUtils/roleUtil'; import { SkinUpdate } from '../db/Skin'; import { getInitHeroById } from './roleService'; import { getActivities } from './activity/activityService'; import { reportTAEvent, reportTAUserSet } from './sdkService'; export class CheckMeterial { private roleId: string; private itemsIndb: Map = new Map(); // 玩家已有的东西 private consumes: ItemInter[] = []; // 消耗,正向数 private goldId = CURRENCY_BY_TYPE.get(CURRENCY_TYPE.GOLD); private coinId = CURRENCY_BY_TYPE.get(CURRENCY_TYPE.COIN); constructor(roleId: string, role?: RoleType, items?: ItemType[]) { this.roleId = roleId; if(role) { this.itemsIndb.set(this.goldId, role.gold); this.itemsIndb.set(this.coinId, role.coin); } if(items && items.length) { for(let {id, count} of items) { this.itemsIndb.set(id, count); } } } public async decrease(goods: {id: number, count: number}[]) { let { items, gold, coin } = sortItems(goods, HANDLE_REWARD_TYPE.COST); let isEnough = true; for(let { id, count} of items) { if(!this.itemsIndb.has(id)) { let item = await ItemModel.findbyRoleAndGidAndCount(this.roleId, id, count); if(!item) { isEnough = false; break; } this.itemsIndb.set(id, item.count); } if(this.itemsIndb.get(id) < count) { isEnough = false; break; } this.itemsIndb.set(id, this.itemsIndb.get(id) - count); } if(gold.length > 0) { if(!this.itemsIndb.has(this.goldId)) { let role = await RoleModel.findByRoleId(this.roleId, 'gold coin'); this.itemsIndb.set(this.goldId, role.gold); this.itemsIndb.set(this.coinId, role.coin); } let goldCost = gold.reduce((pre, cur) => { return pre + cur.count }, 0); if(this.itemsIndb.get(this.goldId) < goldCost) isEnough = false; } if(isEnough && coin.length > 0) { if(!this.itemsIndb.has(this.coinId)) { let role = await RoleModel.findByRoleId(this.roleId, 'gold coin'); this.itemsIndb.set(this.goldId, role.gold); this.itemsIndb.set(this.coinId, role.coin); } let coinCost = coin.reduce((pre, cur) => pre + cur, 0); if(this.itemsIndb.get(this.coinId) < coinCost) isEnough = false; } if(isEnough) this.consumes.push(...goods); return isEnough; } public getConsume() { return this.consumes; } } export async function handleCost(roleId: string, sid: string, goods: Array, reason: ITEM_CHANGE_REASON) { let uids = [{ uid: roleId, sid }]; let { items, equips, gold, coin } = sortItems(goods, HANDLE_REWARD_TYPE.COST); let equipSeqIds = equips.map(cur => cur.seqId); let resEquips: EquipType[] = []; // 检查货币是否充足 let role = await RoleModel.findByRoleId(roleId); if (gold.length > 0 || coin.length > 0) { let { gold: originGold, coin: originCoin } = role; for(let {count} of gold) { originGold -= count }; for(let count of coin) { originCoin -= count }; if(originGold < 0 || originCoin < 0) return false; } //检查装备是否存在 if (equips.length > 0) { resEquips = await EquipModel.getEquips(roleId, equipSeqIds); if (resEquips.length < equips.length) return false; } //检查并修改道具 if (items.length > 0) { let { hasError, result } = await ItemModel.decreaseItems(roleId, items); if (hasError) return false; pinus.app.get('channelService').pushMessageByUids('onItemUpdate', resResult(STATUS.SUCCESS, { goods: result.map(cur => ({...cur, reason })) }), uids); } //删除装备 if (resEquips.length > 0) { let heroMap = new Map(); for(let equip of resEquips) { if(equip.hid > 0) { if(!heroMap.has(equip.hid)) { let hero = await HeroModel.findByHidAndRoleWithEquip(equip.hid, roleId); heroMap.set(equip.hid, { hero, equips: [] }); } heroMap.get(equip.hid).equips.push(equip); } } for(let [_hid, {hero, equips} ] of heroMap) { let args = calEquipSeids(hero); for(let equip of equips) { hero = await HeroModel.removeEquip(roleId, hero.hid, equip.ePlaceId, equip._id); } await calPlayerCeAndSave(HERO_SYSTEM_TYPE.EQUIP, sid, roleId, hero, {}, args); await checkTaskWithHero(roleId, sid, TASK_TYPE.EQUIP_BY_HERO, hero, [-1]); } let equips = await EquipModel.deleteEquips(roleId, equipSeqIds); pinus.app.get('channelService').pushMessageByUids('onEquipDel', resResult(STATUS.SUCCESS, { equips: equips.map(equip => ({ seqId: equip.seqId, id: equip.id, inc: -1, reason })) }), uids); } //消耗玩家货币 if (gold.length > 0 || coin.length > 0) { let costGold = gold.reduce((pre, cur) => pre + cur.count, 0); let costCoin = coin.reduce((pre, cur) => pre + cur, 0); role = await RoleModel.decreaseGoldAndCoin(roleId, gold, costCoin); pinus.app.get('channelService').pushMessageByUids('onPlayerDataChange', resResult(STATUS.SUCCESS, { gold: role.gold, coin: role.coin, totalCost: role.totalCost }), uids); if(costGold > 0) { reportTAEvent(roleId, TA_EVENT.ITEM_CONSUME, getGoldEventProperties(costGold, role.gold, reason)); reportTAUserSet(TA_USERSET_TYPE.SET, roleId, { current_gold: role.gold }); } if(costCoin > 0) { reportTAEvent(roleId, TA_EVENT.ITEM_CONSUME, getCoinEventProperties(costCoin, role.coin, reason)); reportTAUserSet(TA_USERSET_TYPE.SET, roleId, { current_coin: role.coin }); } } return true; } // TODO: sid 在方法内部获取,且不一定存在 export async function addItems(roleId: string, roleName: string, sid: string, goods: Array, reason: ITEM_CHANGE_REASON) { let uids = [{ uid: roleId, sid }]; let { items, equips, gold, coin, ap, skins, figures } = sortItems(goods, HANDLE_REWARD_TYPE.RECEIVE); let showItems: { id: number, seqId?: number, count: number, isBag?: boolean }[] = []; let role = await RoleModel.findByRoleId(roleId); // 1. 装备处理 if(equips.length > 0) { let { equipCount = 0 } = role; let incEquips = equips, mailEquips: { id?: number, hid?: number, seqId?: number }[] = []; if(equips.length + equipCount > BAG.BAG_EQUIP_UPLIMITED) { // 装备上限 let inc = BAG.BAG_EQUIP_UPLIMITED - equipCount; if(inc < 0) inc = 0; incEquips = equips.slice(0, inc); mailEquips = equips.slice(inc); } // 直接加的 let { equips: equipInfos, pushMessages } = await addEquips(roleId, roleName, <{id: number, hid?: number}[]>incEquips, reason); for (let equip of equipInfos) { showItems.push({ seqId: equip.seqId, id: equip.id, count: 1, isBag: true }); } for(let equip of combineItems(mailEquips)) { showItems.push({ id: equip.id, count: equip.count, isBag: false }); } //装备推送 if (!!equipInfos.length) pinus.app.get('channelService').pushMessageByUids('onEquipAdd', resResult(STATUS.SUCCESS, { equipInfos }), uids); pushTaskUpdate(roleId, sid, pushMessages); //统计装备 if (equipInfos.length > 0) { let { serverId } = await RoleModel.findByRoleId(roleId); await checkActivityTask(serverId, sid, roleId, TASK_TYPE.EQUIP_QUALITY_COUNT, equipInfos.length, { equips: equipInfos.map(obj => { return { quality: obj.quality } }) }) } // 发邮件的 if(mailEquips.length > 0) { await sendMailByContent(MAIL_TYPE.EQUIP_OVER, roleId, { goods: combineItems(mailEquips) }); } } // 2. 道具处理 if(items.length > 0) { let { items: itemInfos } = await addBags(roleId, roleName, items, reason); for (let item of items) { showItems.push({ id: item.id, count: item.count }); } //背包除去装备推送 if (!!itemInfos.length) pinus.app.get('channelService').pushMessageByUids('onItemUpdate', resResult(STATUS.SUCCESS, { goods: itemInfos }), uids); } // 3. 货币推送 if(gold.length > 0 || coin.length > 0 || ap > 0) { let { ap: addAp } = await setAp(roleId, null, role.lv, ap, sid, reason); let incCoin = coin.reduce((pre, cur) => pre + cur, 0); let incGold = gold.reduce((pre, cur) => pre + cur.count, 0); role = await RoleModel.increaseGoldAndCoin(roleId, gold, incCoin); pinus.app.get('channelService').pushMessageByUids('onPlayerDataChange', resResult(STATUS.SUCCESS, { gold: role.gold, coin: role.coin, ap: addAp }), uids); if(gold.length > 0) { gold.forEach(({ count }) => { showItems.push(getGoldObject(count)); }); reportTAEvent(roleId, TA_EVENT.ITEM_GET, getGoldEventProperties(incGold, role.gold, reason)); reportTAUserSet(TA_USERSET_TYPE.SET, roleId, { current_gold: role.gold }); } if(coin.length > 0) { coin.forEach(count => { showItems.push(getCoinObject(count)); }); reportTAEvent(roleId, TA_EVENT.ITEM_GET, getCoinEventProperties(incCoin, role.coin, reason)); reportTAUserSet(TA_USERSET_TYPE.SET, roleId, { current_coin: role.coin }); } if(ap > 0) { showItems.push(getApObject(ap)); } } // 4. 皮肤处理 let figureInfos:{ heads: Figure[], frames: Figure[], spines: Figure[] }[] = []; // 头像变化推送信息 if(skins.length > 0) { let heroskins: {skins: HeroSkin[], hid: number}[] = []; // 皮肤推送信息 let skinInfos: {id: number, hid: number, inc: number, reason: number }[] = []; let calAllHeroResult = undefined; // 全局战力变化推送 for (let skinId of skins) {//皮肤推送 let result = await addSkin(roleId, roleName, skinId, false); if (!!result) { showItems.push({ id: skinId, count: 1 }); figureInfos.push(result.figureInfo); if(result.hero) { heroskins.push({ skins: result.hero.skins, hid: result.hero.hid }); if(result.calAllHeroResult) calAllHeroResult = result.calAllHeroResult; } skinInfos.push({ id: skinId, hid: result.hero?.hid||0, inc: 1, reason }) } } if (!!skinInfos.length) { pushHeroSkinMsg(heroskins, skinInfos, uids); // 推送onHeroSkinChange } // 推送全局加成信息 if(calAllHeroResult) await pushCalAllHeroCe(roleId, sid, calAllHeroResult); } // 5. 获得头像和相框等 if(figures.length > 0) { let figureInfo = await addFigure(roleId, figures, reason); if(figureInfo) figureInfos.push(figureInfo); for (let id of figures) {//皮肤推送 showItems.push({ id, count: 1 }); } } // 获得头像或相框或形象推送 if(!!figureInfos && figureInfos.length > 0) { for(let figureInfo of figureInfos) { pinus.app.get('channelService').pushMessageByUids('onHeadChange', resResult(STATUS.SUCCESS, { ...figureInfo }), uids); } } return showItems; } function getGoldEventProperties(inc: number, count: number, reason: ITEM_CHANGE_REASON) { let id = CURRENCY_BY_TYPE.get(CURRENCY_TYPE.GOLD); let dicGoods = gameData.goods.get(id); return { item_id: id, item_name: dicGoods.name, item_itid: dicGoods.itid, change_count: inc, change_after: count, change_reason: reason } } function getCoinEventProperties(inc: number, count: number, reason: ITEM_CHANGE_REASON) { let id = CURRENCY_BY_TYPE.get(CURRENCY_TYPE.COIN); let dicGoods = gameData.goods.get(id); return { item_id: id, item_name: dicGoods.name, item_itid: dicGoods.itid, change_count: inc, change_after: count, change_reason: reason } } export function combineItems(items: { id?: number, count?: number }[]) { let result: { id: number, count: number }[] = []; for(let { id, count = 1 } of items) { let index = result.findIndex(cur => cur.id == id); if(index == -1) { result.push({ id, count }); } else { result[index].count += count; } } return result; } export function combineItemAndEquips(items: { id?: number, count?: number }[]) { let result: { id: number, count: number }[] = []; for(let { id, count = 1 } of items) { let dicGoods = gameData.goods.get(id); let dicItid = ITID.get(dicGoods.itid); if(dicItid.table != 'equip') { let index = result.findIndex(cur => cur.id == id); if(index == -1) { result.push({ id, count }); } else { result[index].count += count; } } else { result.push({ id, count }); } } return result; } function sortItems(goods: ItemInter[], handleType: HANDLE_REWARD_TYPE) { let items: { id: number, count: number }[] = []; // 可叠加道具 let equips: { seqId?: number, id?: number, hid?: number }[] = []; // 不可叠加装备 let gold: { count: number, isPay: boolean }[] = []; // 金币 let coin: number[] = []; let ap: number = 0; let skins: number[] = []; let figures: number[] = []; for(let good of goods) { if(good.count == 0) continue; let dicGood = gameData.goods.get(good.id); if(!dicGood) { errlogger.error(`物品 ${good.id} 未配置`); continue; } let dicItid = ITID.get(dicGood.itid); if(!dicItid) { errlogger.error(`itid ${dicGood.itid} 未配置`); continue; } let { type, table, isCurrency } = dicItid; if(table == ITEM_TABLE.EQUIP) { // 装备 if(handleType == HANDLE_REWARD_TYPE.RECEIVE) { for(let i = 0; i < good.count; i++) { equips.push({ id: good.id, hid: good.hid }) } } else { if(!!good.seqId) { equips.push({ seqId: good.seqId }); } } } else if (table == ITEM_TABLE.ITEM) { // 可叠加道具 let index = items.findIndex(cur => cur.id == good.id); if(index > 0) { items[index].count += good.count; } else { items.push({ id: good.id, count: good.count }); } } else if (table == ITEM_TABLE.SKIN) { // 皮肤,不可重复获得,不可删 if(handleType == HANDLE_REWARD_TYPE.RECEIVE) { let index = skins.indexOf(good.id); if (index == -1) { skins.push(good.id); } } } else if (table == ITEM_TABLE.ROLE) { if(isCurrency) { // 3种货币 let dicCurrency = CURRENCY.get(good.id); if(dicCurrency) { if(dicCurrency.type == CURRENCY_TYPE.GOLD) { // 金币,区分付费和免费,默认免费 let index = gold.findIndex(cur => cur.isPay == !!good.isPay); if(index > 0) { gold[index].count += good.count; } else { gold.push({ count: good.count, isPay: !!good.isPay }); } } else if (dicCurrency.type == CURRENCY_TYPE.COIN) { // 铜钱 coin.push(good.count); } else if (dicCurrency.type == CURRENCY_TYPE.ACTION_POINT) { // 体力 ap += good.count; } } } else { if (type == CONSUME_TYPE.HEAD || type == CONSUME_TYPE.FRAME || type == CONSUME_TYPE.SPINE) { // 头像等,不可重复获得,不可删 let index = figures.indexOf(good.id); if (index == -1) { figures.push(good.id); } } } } } return { items, equips, gold, coin, ap, skins, figures } } export async function checkGoods(roleId: string, goodIds: Array) { let equipIds: Array = []; let itemIds: Array = []; let hids: Array = []; goodIds = uniq(goodIds); for (let goodId of goodIds) { let goodInfo = gameData.goods.get(goodId); if (!!goodInfo) { let { table } = ITID.get(goodInfo.itid); if (table == ITEM_TABLE.EQUIP) { equipIds.push(goodId); } else if (table == ITEM_TABLE.ITEM) { itemIds.push(goodId); } } } //检查装备是否存在 if (!!equipIds.length) { let resEquips = await EquipModel.getEquipsByIds(roleId, equipIds); resEquips = uniq(resEquips, function (resEquip) { return resEquip.id; }); if (resEquips.length < equipIds.length) return false; } //检查并修改道具 if (itemIds.length > 0) { let items = await ItemModel.findbyRoleAndIds(roleId, itemIds); if (items.length < itemIds.length) return false; } return true; } export async function checkHeroes(roleId: string, hids: number[]) { if (!!hids.length) { let heros = await HeroModel.findByHidRange(hids, roleId); if (heros.length < hids.length) return false; } return true } export async function unlockFigure(sid: string, roleId: string, conditions: { type: number, paramHid?: number, paramFavourLv?: number, paramSkinId?: number, paramWinStreakNum?: number }[], role?: RoleType) { let figureInfo = await pubUnlockFigure(roleId, conditions, role); await pushFigureUpdate(roleId, sid, figureInfo); } export async function pushFigureUpdate(roleId: string, sid: string, figureInfo: { heads: Figure[], frames: Figure[], spines: Figure[] }) { if (!!figureInfo && (figureInfo.heads.length > 0 || figureInfo.frames.length > 0 || figureInfo.spines.length > 0)) { let uids = [{ uid: roleId, sid }]; pinus.app.get('channelService').pushMessageByUids('onHeadChange', resResult(STATUS.SUCCESS, { ...figureInfo }), uids); } } /** * 创建多个武将 * @param roleId * @param sid * @param serverId * @param heroInfo */ export async function createHeroes(roleId: string, roleName: string, sid: string, serverId: number, heroInfo: CreateHeroParam[]) { let hids = heroInfo.map(cur => cur.hid); let userHeroesMap = await HeroModel.findMapByHidRange(hids, roleId); let infos: Map = new Map(), pieces: ItemInter[] = []; for (let h of heroInfo) { let heroCount = h.count || 1; if (userHeroesMap.has(h.hid)) { let { pieceId, count } = transPiece(h.hid); pieces.push({ id: pieceId, count: count * heroCount }); } else { let initInfo: { heroInfo: HeroUpdate, skinInfo: SkinUpdate }; if(pinus.app.getServerType() == 'role') { initInfo = getInitHeroById(h.hid); } else { let roleServers = pinus.app.getServersByType('role'); let server = getRandSingleEelm(roleServers); initInfo = await pinus.app.rpc.role.roleRemote.getInitHeroById.toServer(server.id, h.hid); } initInfo.heroInfo = { ...initInfo.heroInfo, ...h }; infos.set(h.hid, initInfo); userHeroesMap.set(h.hid, null); if (heroCount > 1) { let { pieceId, count } = transPiece(h.hid); pieces.push({ id: pieceId, count: count * (heroCount - 1) }); } } } let resultHeroes: HeroType[] = [], resultItems: RewardInter[] = [], heroes: HeroShowParam[] = []; if (infos.size > 0) { let createHero = new CreateHeroes(roleId, roleName, serverId); await createHero.createWithHeroInfo(infos); await createHero.clearTask(await getActivities()) await createHero.pushMessage(pinus, sid); await createHero.updateRedisRank(Rank); heroes = createHero.getShowHeroes(); resultHeroes = createHero.getResultHeroes(); } if (pieces.length > 0) { let goods = await addItems(roleId, roleName, sid, pieces, ITEM_CHANGE_REASON.HERO_TRANSFER_PIECE); resultItems = goods; } return { heroes, resultHeroes, goods: resultItems } } export async function createHero(roleId: string, roleName: string, sid: string, serverId: number, heroInfo: CreateHeroParam) { let result = await createHeroes(roleId, roleName, sid, serverId, [heroInfo]); return result; } /** * 皮肤数据变化去重、推送 * @param heroskins 推送的皮肤 * @param uids 玩家 */ function pushHeroSkinMsg(heroskins: {skins: HeroSkin[], hid: number}[], skinInfos: {id: number, hid: number }[], uids: {uid: string, sid: string}[]) { let pushSkinInfos: {skins: HeroSkin[], hid: number}[] = []; // 可能会有重复的 for(let { skins, hid } of heroskins) { let index = pushSkinInfos.findIndex(cur => cur.hid == hid); if(index == -1) { pushSkinInfos.push({skins, hid}); } else { if(skins.length > pushSkinInfos[index].skins.length) { pushSkinInfos[index] = {skins, hid}; } } } if(pushSkinInfos.length > 0 || skinInfos.length > 0) { pinus.app.get('channelService').pushMessageByUids('onHeroSkinChange', resResult(STATUS.SUCCESS, { heros: heroskins, skins: skinInfos }), uids); } }