import { EquipPrintDropType, EquipPrintDropModel } from './../db/EquipPrintDrop'; import { FriendPointModel } from './../db/FriendPoint'; import { STATUS } from './../consts/statusCode'; import { COM_TEAM_STATUS, CURRENCY_BY_TYPE, CURRENCY_TYPE, FRIEND_DROP_TYPE, COM_BTL_CONST, FRIEND_DROP_MAX } from './../consts'; import { RoleStatus, ComBattleTeamModel } from './../db/ComBattleTeam'; import { getBluePrtByQuality, getComBtlSetByQuality, getRewardByBlueprtId, getWarById, getWarIdByBlueprtId, comBtlRangeInfo } from "../pubUtils/gamedata"; import { getRandEelm, getRandValue, resResult, ratioReward, getRandValueByMinMax, getRandomWithWeight, decodeStr, getRobotInfo } from "../pubUtils/util"; import { getRandRobot } from "./battleService"; import { difference, omit } from 'underscore'; import { Channel } from 'pinus'; import { TREASURE } from '../pubUtils/dicParam'; import { decreaseItems } from './rewardService'; /** * 在给定的品质列表中随机返回一定数量的藏宝图Id * @param qualityArr 品质数组,在所有给定品质的藏宝图中筛选1 * @param cnt 返回藏宝图数量 */ export function getRandBlueprtId(qualityArr: Array, cnt = 1) { if (!qualityArr || !qualityArr.length) return null; let blueprtIdArr = []; for (let q of qualityArr) { blueprtIdArr = blueprtIdArr.concat(getBluePrtByQuality(q)); } if (blueprtIdArr.length === 0) return null; const res = getRandEelm(blueprtIdArr, cnt); return res; } export function getRandComBtlRobots(topFiveCe: number, ceLimit: number, lv: number, cnt: number) { let robotHeroes = getRandRobot(cnt); // 随机几个阵容 let robotInfos = []; // 随机几个机器人信息 for (let i = 0; i < cnt; i++) { robotInfos.push(getRobotInfo()); } // 创建并添加机器人 let robotStArr = [], robotIdArr = []; if (robotHeroes && robotInfos && robotHeroes.length && robotInfos.length && robotInfos.length === robotHeroes.length) { robotHeroes.forEach((robot, idx) => { let robotCe = 0; if (ceLimit) { robotCe = getRandValueByMinMax(ceLimit * COM_BTL_CONST.ROBOT_CE_LIMIT_MIN, ceLimit * COM_BTL_CONST.ROBOT_CE_LIMIT_MAX, 0); } else { robotCe = getRandValue(topFiveCe || 0, COM_BTL_CONST.ROBOT_CE_RATIO, 0); } const robotLv = getRandValue(lv, COM_BTL_CONST.ROBOT_CE_RATIO, 0); const imgHid = robot[Math.floor(Math.random() * robot.length)]; const { robotRoleId, robotRoleName } = robotInfos[idx]; let robotStatus = new RoleStatus(robotRoleId, robotRoleName, false, false, imgHid, imgHid, robotCe, robotLv, robot, true); robotStArr.push(robotStatus); robotIdArr.push(robotRoleId); }); } return { robotStArr, robotIdArr } } export function checkComBattleResult(teamStatus) { if (teamStatus.bossCurHp === 0) { return COM_TEAM_STATUS.WIN; } else { let allPlayerKilled = true; let robotRestHurt = 0; // 看看是否还有活人 teamStatus.roleStatus.forEach(st => { // 设置了阵容,且阵容人数和阵亡人数一样,说明玩家战败 if (!st.isRobot && st.heroes && ((st.heroes.length > 0 && st.killed.length < st.heroes.length) || st.heroes.length === 0)) { allPlayerKilled = false } }); // 没有活人的话看看还有没有机器人没打完伤害 if (allPlayerKilled && teamStatus.curRnd < COM_BTL_CONST.ROBOT_RND_LMT) { teamStatus.roleStatus.forEach(st => { if (st.isRobot) { const deltaRnd = COM_BTL_CONST.ROBOT_RND_LMT - teamStatus.curRnd; let hurtHp = getRandValue(teamStatus.bossHp / COM_BTL_CONST.ROBOT_RND_LMT * COM_BTL_CONST.ROBOT_HURT_RATIO, COM_BTL_CONST.ROBOT_HURT_CH_RATIO, 0) * deltaRnd; // 1 个机器人对 boss 造成的总伤害 robotRestHurt += hurtHp; } }) } if (allPlayerKilled) { // 没有活人且机器人剩余伤害打不死 boss,战败 if (teamStatus.bossCurHp > robotRestHurt) { return COM_TEAM_STATUS.LOOSE; } else { return COM_TEAM_STATUS.WIN; } } } return COM_TEAM_STATUS.FIGHTING; } /** * @description 计算寻宝结算-deprecated * @export * @param {string} roleId * @param {string} battleCode * @returns */ export async function checkComBattleDrop(roleId: string, battleCode: string) { let team = await ComBattleTeamModel.getTeamByRoleAndBattleCode(roleId, battleCode); if (team.status !== COM_TEAM_STATUS.WIN) return { status: -1, resResult: resResult(STATUS.COM_BATTLE_REWARD_ERR) }; let roleSt = null; team.roleStatus.forEach(st => { if (st.roleId === roleId) { roleSt = st; } }); if (!roleSt || roleSt.gotReward) return { status: -1, resResult: resResult(STATUS.COM_BATTLE_REWARD_ERR) }; let fixReward = getRewardByBlueprtId(team.blueprtId); if (!roleSt.isCap) { if (roleSt.isFrd) { fixReward = '&'; } else { fixReward = ratioReward(fixReward, COM_BTL_CONST.ASSIST_REWARD_RATIO); } } await ComBattleTeamModel.updateRewardSt(team.teamCode, roleId, true); return {status: 0, fixReward}; } export function clearComBtlTimer(teamCode: string, timerMap: Map) { let timer = timerMap.get(teamCode); if (timer) { clearTimeout(timer); } } export function setComBtlTimer(teamCode: string, timer: NodeJS.Timer, timerMap: Map) { let preTimer = timerMap.get(teamCode); if (preTimer) { clearTimeout(preTimer); } timerMap.set(teamCode, timer); } export async function getRealReward(blueprtId: number, roleSt: RoleStatus) { let warInfo = getWarById(getWarIdByBlueprtId(blueprtId)); let fixRewardStr = warInfo['fixReward']; if (!roleSt.isCap) { if (roleSt.isFrd) { let frdPointRec = await FriendPointModel.getFrdPointRecToday(roleSt.roleId, FRIEND_DROP_TYPE.COM_BATTLE); if (!frdPointRec || frdPointRec.cnt <= FRIEND_DROP_MAX.COM_BTL - COM_BTL_CONST.FRDCNT_DROP) { fixRewardStr = `${CURRENCY_BY_TYPE.get(CURRENCY_TYPE.FRIEND_POINT)}&${COM_BTL_CONST.FRDCNT_DROP}`; } else if (frdPointRec.cnt < FRIEND_DROP_MAX.COM_BTL) { fixRewardStr = `${CURRENCY_BY_TYPE.get(CURRENCY_TYPE.FRIEND_POINT)}&${COM_BTL_CONST.FRDCNT_DROP - frdPointRec.cnt}`; } else { fixRewardStr = '&'; } } else { fixRewardStr = ratioReward(fixRewardStr, COM_BTL_CONST.ASSIST_REWARD_RATIO); } } return fixRewardStr; } export async function getAssistTimesByQuality(roleId: string, qualityArr?: Array) { let teams = await ComBattleTeamModel.getAssistTeamsByTime(roleId, qualityArr, new Date(new Date().setHours(0, 0, 0, 0)), true); let cntMap = new Map(); teams.forEach(team => { if (team && team.quality && team.roleStatus) { for (let st of team.roleStatus) { if (st.roleId !== roleId || st.isFrd) { continue; } let cnt = cntMap.get(team.quality) || 0; cntMap.set(team.quality, cnt + 1) } } }); return cntMap; } export async function getFrd(roleId: string, quality: number) { let isFrd = false; let assistTimes = await getAssistTimesByQuality(roleId, [quality]); let assistTime = assistTimes.get(quality); let { assistanceTime } = getComBtlSetByQuality(quality); if (assistTime >= assistanceTime) isFrd = true; return isFrd; } /** * @description 更新队伍状态 * @export * @param {number} preStatus 更新前状态,作为筛选条件 * @param {number} newStatus 要设置的新状态 */ export async function updateTeamStatus(preStatus: number, newStatus: number) { if (preStatus === newStatus) return; await ComBattleTeamModel.updateStatusByStatus(preStatus, newStatus); } /** * @description 计算机器人每次对 boss 造成的伤害 * @param {number} bossHp boss 总血量 */ function robotEachHurt(bossHp: number, bossCnt: number) { const robotTotalHurt = bossHp * COM_BTL_CONST.ROBOT_HURT_RATIO; const robotAverageHurt = robotTotalHurt / COM_BTL_CONST.ROBOT_ACT_LMT / bossCnt; return getRandValue(robotAverageHurt, COM_BTL_CONST.ROBOT_HURT_CH_RATIO, 0) } /** * @description 更新机器人阵亡情况 * @param {number} bossHp * @param {RoleStatus} roleSt */ function updateRobotKilled(bossHp: number, roleSt: RoleStatus) { const robotTotalHurt = bossHp * COM_BTL_CONST.ROBOT_HURT_RATIO; // 让阵亡人数和打出伤害的进度同步,比如有 5 个武将,每打出目标总伤害的 1 / 5 应该增加一个阵亡武将 const dmgProgress = Math.floor(roleSt.totalDmg / (robotTotalHurt / roleSt.heroes.length)); if (dmgProgress > roleSt.killed.length && dmgProgress <= roleSt.heroes.length) { const newKilledCnt = dmgProgress - roleSt.killed.length; const aliveHeroes = difference(roleSt.heroes, roleSt.killed); const newKilledHeroes = getRandEelm(aliveHeroes, newKilledCnt); roleSt.killed = roleSt.killed.concat(newKilledHeroes); } } export async function handleComBtlProgress(teamStatus, robotHurtTimer: Map, teamMap: Map, channel: Channel) { const { teamCode } = teamStatus; // 判断战斗是否结束 let battleSt = checkComBattleResult(teamStatus); teamStatus.status = battleSt; if (battleSt === COM_TEAM_STATUS.WIN || battleSt === COM_TEAM_STATUS.LOOSE) { let result = battleSt === COM_TEAM_STATUS.WIN; if (result) { teamStatus.bossHpArr.forEach(bs => { bs.curHp = 0; }); for (let st of teamStatus.roleStatus) { st.fixReward = await getRealReward(teamStatus.blueprtId, st); }; } let team = await ComBattleTeamModel.syncTeamData({teamCode, status: battleSt, roleStatus: teamStatus.roleStatus, bossHpArr: teamStatus.bossHpArr}); if (!team) return resResult(STATUS.COM_BATTLE_RESULT_ERR); // 战斗胜利队长扣减藏宝图 if (result && teamStatus.capId != 'robot') { const { sid } = channel.getMember(teamStatus.capId); let res = await decreaseItems(teamStatus.capId, sid, [{id: teamStatus.blueprtId, count: 1}]); if (res === true) return resResult(STATUS.COM_BATTLE_BLUEPRT_NOT_ENOUGH); } clearRobotHurtTimer(teamStatus, robotHurtTimer); channel.pushMessage('onTeamComplete', resResult(STATUS.SUCCESS, {teamCode, result})); teamMap.delete(teamCode); } } /** * @description 更新机器人对 boss 的伤害 * @export * @param {*} teamStatus 要更新的队伍信息 * @param {RoleStatus} roleSt 要更新的玩家信息 */ export function updateRobotHurt(teamStatus, roleSt: RoleStatus, channel: Channel) { // 机器人的伤害为:boss 血量的一定比例,平均到一定回合数内,再平均到每个敌军,上下浮动一定比例 let eachHurtHp = robotEachHurt(teamStatus.bossHp, teamStatus.bossHpArr.length); let robotTotalHurt = 0; let actBossHurts = []; for (let boss of teamStatus.bossHpArr) { if (boss.curHp === 0) continue; if (boss.curHp >= eachHurtHp) { actBossHurts.push({dataId: boss.dataId, hurtHp: eachHurtHp}); robotTotalHurt += eachHurtHp; boss.curHp -= eachHurtHp; } else if (boss.curHp > 0) { // 丢弃溢出的伤害 actBossHurts.push({dataId: boss.dataId, hurtHp: boss.curHp}); robotTotalHurt += boss.curHp; boss.curHp = 0; } break; } teamStatus.bossCurHp -= robotTotalHurt; roleSt.totalDmg += robotTotalHurt; updateRobotKilled(teamStatus.bossHp, roleSt); channel.pushMessage('onTeammateAct', resResult(STATUS.SUCCESS, { teamCode: teamStatus.teamCode, bossCurHp: teamStatus.bossCurHp, bossHpArr: teamStatus.bossHpArr, roleStatus: teamStatus.roleStatus, actRoleId: roleSt.roleId, actBossHurts })); } /** * @description 按一定时间间隔刷新机器人伤害 * @export * @param {*} teamStatus * @param {RoleStatus} roleSt * @param {number} interval * @param {Channel} channel * @param {Map} robotHurtTimer */ export function updateRobotHurtByTime(teamStatus, roleSt: RoleStatus, interval: number, channel: Channel, robotHurtTimer: Map, teamMap: Map) { const timerKey = `${teamStatus.teamCode}_${roleSt.roleId}`; const robotTimer = setInterval(() => { const robotTotalHurt = teamStatus.bossHp * COM_BTL_CONST.ROBOT_HURT_RATIO; if (roleSt.totalDmg < robotTotalHurt && teamStatus.bossCurHp > 0 && teamMap.has(teamStatus.teamCode)) { updateRobotHurt(teamStatus, roleSt, channel); handleComBtlProgress(teamStatus, robotHurtTimer, teamMap, channel); } else { clearInterval(robotTimer); } }, interval * 1000); if (!robotHurtTimer.has(timerKey)) { robotHurtTimer.set(timerKey, robotTimer); } } /** * @description 清理机器人伤害的 timer * @export * @param {*} teamStatus 寻宝队伍状态 * @param {Map} robotHurtTimer */ export function clearRobotHurtTimer(teamStatus, robotHurtTimer: Map) { teamStatus.roleStatus.forEach(st => { const timerKey = `${teamStatus.teamCode}_${st.roleId}`; if (st.isRobot === true && robotHurtTimer.has(timerKey)) { clearInterval(robotHurtTimer.get(timerKey)); } }); } /** * @description 检查寻宝等级是否合法 * @export * @param {number} lv 玩家等级 * @param {number} lvRange 藏宝图等级范围 * @returns */ export function comBtlLvInvalid(lv: number, lvRange: number) { const lvs = comBtlRangeInfo(lvRange); if (!lvs) return true; const minLv = lvs[0]; return lv < minLv; } export async function dismissTeam(teamStatus, teamMap: Map, roleId: string, teamDisTimer: Map, app) { const { teamCode } = teamStatus; if (!teamStatus || !teamStatus.roleIds || teamStatus.roleIds.indexOf(roleId) === -1) return resResult(STATUS.COM_BATTLE_TEAM_INVALID); if (teamStatus.status !== COM_TEAM_STATUS.DEFAULT) return resResult(STATUS.COM_BATTLE_DISSMISS_ERR); if (roleId !== teamStatus.capId) return resResult(STATUS.COM_BATTLE_CAP_ONLY); let team = await ComBattleTeamModel.removeTeam(teamCode); if (!team) return resResult(STATUS.COM_BATTLE_DISSMISS_ERR); let rmSt = teamMap.delete(teamCode); if (!rmSt) return resResult(STATUS.COM_BATTLE_DISSMISS_ERR); let channelService = app.get('channelService'); let channel = channelService.getChannel(teamCode, false); channel.pushMessage('onTeamDismiss', resResult(STATUS.SUCCESS, {teamCode})); channel.destroy(); clearComBtlTimer(teamCode, teamDisTimer); // 队伍解散停止解散计时 return resResult(STATUS.SUCCESS); } export function setDismissTimer(teamStatus, teamMap: Map, roleId: string, teamDisTimer: Map, app) { if (teamStatus && teamStatus.roleIds && teamStatus.roleIds.length === 3 && teamStatus.status === COM_TEAM_STATUS.DEFAULT) { let timer = setTimeout(async () => { await dismissTeam(teamStatus, teamMap, roleId, teamDisTimer, app); }, COM_BTL_CONST.CAP_START_TIME); teamDisTimer.set(teamStatus.teamCode, timer); } } function initEquipPrintDropData(roleId: string, roleName: string) { let result: EquipPrintDropType = new EquipPrintDropModel().toJSON(); result.roleId = roleId; result.roleName = roleName; result.capTarget = getRandValueByMinMax(1, TREASURE.CAPTAIN_DROP, 0); result.teammateTarget = getRandValueByMinMax(1, TREASURE.TEAMMATE_DROP, 0); return result; } function incEquipPrintDropData(roleSt: RoleStatus, dropRec: EquipPrintDropType) { const { isCap } = roleSt; let dropResult = false; if (isCap === true) { dropRec.capAll += 1; dropRec.capCur += 1; if (dropRec.capCur > TREASURE.CAPTAIN_DROP) { dropRec.capCur = 1; dropRec.capTarget = getRandValueByMinMax(1, TREASURE.CAPTAIN_DROP, 0); } if (dropRec.capCur === dropRec.capTarget) { dropResult = true; dropRec.capDropAll += 1; } } else { dropRec.teammateAll += 1; dropRec.teammateCur += 1; if (dropRec.teammateCur > TREASURE.TEAMMATE_DROP) { dropRec.teammateCur = 1; dropRec.teammateTarget = getRandValueByMinMax(1, TREASURE.TEAMMATE_DROP, 0); } if (dropRec.teammateCur === dropRec.teammateTarget) { dropResult = true; dropRec.teammateDropAll += 1; } } return dropResult; } export async function incEquipPrintDrop(roleSt: RoleStatus) { const { roleId, roleName } = roleSt; let dropRec = await EquipPrintDropModel.getByRoleId(roleId); if (!dropRec) { dropRec = await EquipPrintDropModel.createDoc(initEquipPrintDropData(roleId, roleName)); } const dropResult = incEquipPrintDropData(roleSt, dropRec); dropRec = await EquipPrintDropModel.updateDoc(roleId, omit(dropRec, ['_id', 'createdAt', 'updatedAt'])); return { dropResult, dropRec }; } export function randEquipPrintId(warInfo) { if (!warInfo || !warInfo['jackpotReward']) { return null; } const result = getRandomWithWeight(decodeStr('possibility', warInfo['jackpotReward'])); if (!result || !result.dic || !result.dic.id) { return null; } return result.dic.id; }