424 lines
18 KiB
TypeScript
424 lines
18 KiB
TypeScript
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<number>, 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<string, NodeJS.Timer>) {
|
||
let timer = timerMap.get(teamCode);
|
||
if (timer) {
|
||
clearTimeout(timer);
|
||
}
|
||
}
|
||
|
||
export function setComBtlTimer(teamCode: string, timer: NodeJS.Timer, timerMap: Map<string, NodeJS.Timer>) {
|
||
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<number>) {
|
||
let teams = await ComBattleTeamModel.getAssistTeamsByTime(roleId, qualityArr, new Date(new Date().setHours(0, 0, 0, 0)), true);
|
||
let cntMap = new Map<number, number>();
|
||
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<string, NodeJS.Timer>, teamMap: Map<string, any>, 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<string, NodeJS.Timer>} robotHurtTimer
|
||
*/
|
||
export function updateRobotHurtByTime(teamStatus, roleSt: RoleStatus, interval: number, channel: Channel, robotHurtTimer: Map<string, NodeJS.Timer>, teamMap: Map<string, any>) {
|
||
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<string, NodeJS.Timer>} robotHurtTimer
|
||
*/
|
||
export function clearRobotHurtTimer(teamStatus, robotHurtTimer: Map<string, NodeJS.Timer>) {
|
||
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<string, any>, roleId: string, teamDisTimer: Map<string, NodeJS.Timer>, 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<string, any>, roleId: string, teamDisTimer: Map<string, NodeJS.Timer>, 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;
|
||
} |