Files
ZYZ/game-server/app/services/comBattleService.ts
T

451 lines
19 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';
import { getFriendLvAdd } from './friendService';
import { getRoleIds } from '../pubUtils/friendUtil';
/**
* 在给定的品质列表中随机返回一定数量的藏宝图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) {
clearComBtlTimer(teamStatus.teamCode, teamDisTimer);
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;
}
/**
* 按照好友关系,新增加成
*
* @param {RoleStatus[]} roleStatus 队伍内的所有队友
*/
export async function getComBattleFriendAdd(roleStatus: RoleStatus[]) {
let hasAdd = new Array<string>(); //roleIds
for(let myRoleStatus of roleStatus) {
for(let rs of roleStatus) {
if(myRoleStatus.roleId == rs.roleId) continue;
if(myRoleStatus.isRobot || rs.isRobot) continue;
let {roleIds} = getRoleIds([myRoleStatus.roleId, rs.roleId]);
if(hasAdd.includes(roleIds)) continue;
let add = await getFriendLvAdd(myRoleStatus.roleId, rs.roleId);
rs.addFrdRatio(add);
myRoleStatus.addFrdRatio(add);
hasAdd.push(roleIds);
}
}
}