Files
ZYZ/game-server/app/services/battle/rougeService.ts

1014 lines
45 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { clone, result } from "underscore";
import { ROUGE_CHARA_INITIAL, ROUGE_CHARA_TYPE, ROUGE_EFFECT_TYPE, ROUGE_LIKE_CARD_TYPE, ROUGE_LIKE_CHOOSE_REWARD, ROUGE_LIKE_NODE_TYPE, ROUGE_LIKE_STATUS, SHOP_REFRESH_TYPE } from "../../consts";
import { Card, RougelikeCharaModel, RougelikeCharaPara, RougelikeCharaType } from "../../db/RougelikeChara";
import { RougelikeLayerModel, RougelikeLayerType } from "../../db/RougelikeLayer";
import { RougelikeRecordModel, RougelikeRecordType } from "../../db/RougelikeRecord";
import { gameData } from "../../pubUtils/data";
import { ROUGELIKE } from "../../pubUtils/dicParam";
import { genCode, getRandEelm, getRandEelmWithWeight, getRandEelmWithWeightAndNum, getRandValueByMinMax } from "../../pubUtils/util";
import { RougelikeCardModel, RougelikeCardType } from "../../db/RougelikeCard";
import RougelikeRecordDetail, { RougelikeRecordDetailModel, RougelikeRecordDetailPara, RougelikeRecordDetailType } from "../../db/RougelikeRecordDetail";
import { CollectionReturnParam, CommonCard, CommonChara, CommonNode, CommonReward, RewardInter, layerNode } from "../../pubUtils/interface";
import { DicRougeQuestionMarkPlan } from "../../pubUtils/dictionary/DicRougeQuestionMarkPlan";
import { DicRougeRandomEventPlan } from "../../pubUtils/dictionary/DicRougeRandomEventPlan";
import * as util from 'util';
import { RougelikeCollectionModel, } from "../../db/RougelikeCollection";
import { RougelikeScoreModel } from "../../db/RougelikeScore";
import { getTechData } from "./rougeTechService";
import { getZeroPointOfTimeD } from "../../pubUtils/timeUtil";
import { sendMailByContent } from "../mailService";
import { DicRougeCharaCardPlan } from "../../pubUtils/DicRougeCharaCardPlan";
import { MAIL_TYPE, PUSH_ROUTE } from "../../consts";
import { sendMessageToUserWithSuc } from "../pushService";
import { RougeEffect, getAddChoosePassive, getAddPassiveWeight, getChooseQualityPassives, getShopDiscount } from "./rougeEffectService";
import { formateCharasOrCards } from "./rougeCollectService";
import { errlogger } from "../../util/logger";
import { RougelikeExtendModel } from "../../db/RougelikeExtend";
import { isDevelopEnv } from "../utilService";
export async function getRougeData(roleId: string) {
let isPlaying = true, gameCode = '';
const dbRecord = await RougelikeRecordModel.findByRoleIdAndStatus(roleId, ROUGE_LIKE_STATUS.SUCCESS);
if (!dbRecord) isPlaying = false;
else gameCode = dbRecord.gameCode;
let dbScore = await RougelikeScoreModel.findByRoleId(roleId);
let techData = await getTechData(roleId);
let dbCollections = await RougelikeCollectionModel.findByRoleId(roleId);
let collections = dbCollections.map((obj) => new CollectionReturnParam(obj));
const dbExtends = await RougelikeExtendModel.findByRoleId(roleId);
const limitIds = dbExtends.map(cur => cur.limitId);
return { isPlaying, gameCode, weeklyScore: dbScore?.score || 0, receivedScore: dbScore?.received || [], ...techData, collections, limitIds, takeoutRewardCnt: dbScore?.takeoutRewardCnt }
}
/*
/**
* 获取初始三名角色卡
* @param
* @returns
*/
export function getInitCharaCard() {
let canRandomCharas = gameData.rougeCharaByInitial.get(ROUGE_CHARA_INITIAL.CAN);
if (!canRandomCharas || canRandomCharas.length < ROUGELIKE.INIT_RANDOM_CHARA_COUNT) {
console.error("getInitChara--配置表中能初始随机的角色卡不足, canRandomCharas=%s", canRandomCharas);
return;
}
let randomData = getRandEelm(canRandomCharas, ROUGELIKE.INIT_RANDOM_CHARA_COUNT)
return randomData;
}
/**
* 获取大地图生成数据
* @param layerPlan
* @param layerCount
*/
export function getMap(layerPlan: number, layerCount: number) {
let retLayer = getLayerNodeRandom(layerPlan, layerCount);
if (!retLayer) return [];
return getLayerNodeLineRandom(retLayer) || [];
}
export function getLayerNodeRandom(layerPlan: number, layerCount: number) {
if (!layerPlan || !layerCount) return;
const layerPlanDatas = gameData.rougeLayerPlanByPlanId.get(layerPlan);
if (!layerPlanDatas || layerPlanDatas.length != layerCount) return console.error("getMap--获取配置层数不一致, layerPlan=%s, layerCount=%s", layerPlan, layerCount);
let retLayer = new Map<number, CommonNode>();
//获取可随机到的节点
for (let data of layerPlanDatas) {
let layerNodeNumPlanDatas = gameData.rougeLayerNodeNumPlan.get(data.nodeNumPlan);
if (!layerNodeNumPlanDatas) return console.error("getMap--rougeLayerNodeNumPlan配置错误, planId=%s", data.nodeNumPlan);
let nodeNum = 0;
if (layerNodeNumPlanDatas.length == 1) nodeNum = 1;
else nodeNum = getRandEelmWithWeight(layerNodeNumPlanDatas).dic.nodeNum;
let layerNodePlans = gameData.rougeLayerNodePlan.get(data.nodePlan);
if (!layerNodePlans || layerNodePlans.length == 0) return console.error("getMap--rougeLayerNodePlan配置错误, planId=%s", data.nodePlan);
let randomNodes: CommonNode['layerNodes'] = [], tempIndex = 0;
if (layerNodePlans.length > nodeNum) {
randomNodes = getRandEelmWithWeightAndNum(layerNodePlans, nodeNum).map((cur) => {
let nodeId = cur.dic.nodeId;
let nodeData = gameData.rougeNode.get(nodeId);
if (!nodeData) errlogger.error(`nodePlane ${data.nodePlan} 's nodeId ${nodeId} not found`);
return { detailCode: genCode(8), index: tempIndex++, nodeId, preNodeIndexs: [], type: nodeData.nodeType, isChoose: 0 };
});
} else {
randomNodes = layerNodePlans.map((cur) => {
let nodeId = cur.nodeId;
let nodeData = gameData.rougeNode.get(nodeId);
return { detailCode: genCode(8), index: tempIndex++, nodeId: cur.nodeId, preNodeIndexs: [] as number[], type: nodeData.nodeType, isChoose: 0 };
});
}
retLayer.set(data.layerIndex, { layer: data.layerIndex, layerNodes: randomNodes });
}
// console.log('-x-x--x-x-x-x-x-x-x-x-x- retLayer', util.inspect(retLayer, { depth: null }));
return retLayer;
}
/**
* 随机节点连线
* @param retLayer
* @returns
*/
export function getLayerNodeLineRandom(retLayer: Map<number, CommonNode>) {
for (let [key, value] of retLayer) {
let preLayer = retLayer.get(key - 1);
let curLayer = retLayer.get(key);
if (!preLayer || !curLayer) continue;
let tempPreNodes = preLayer.layerNodes; // 前一层节点数据
let tempCurNodes = curLayer.layerNodes; // 当前层节点数据
let indexMap = new Map<number, { dx: number, index: number }>(); //记录下前一层有那些节点与当前层是否连线
//当前层首节点
tempCurNodes[0].preNodeIndexs.push(tempPreNodes[0].index);
indexMap.set(tempPreNodes[0].index, { dx: 0, index: tempCurNodes[0].index });
//当前层尾节点
if (!(tempCurNodes.length == 1 && tempPreNodes.length == 1)) {
tempCurNodes[tempCurNodes.length - 1].preNodeIndexs.push(tempPreNodes[tempPreNodes.length - 1].index);
indexMap.set(tempPreNodes[tempPreNodes.length - 1].index, { dx: tempCurNodes.length - 1, index: tempCurNodes[tempCurNodes.length - 1].index });
}
for (let i = 0; i < tempCurNodes.length - 1; i++) {
let minIndex = 0, maxIndex = 0, start = 0;
if (i != 0) start = minIndex = maxIndex = Math.max(...tempCurNodes[i - 1].preNodeIndexs);
if (tempPreNodes[start + 1]) maxIndex = tempPreNodes[start + 1].index;
if (tempPreNodes[start + 2]) maxIndex = tempPreNodes[start + 2].index;
let randomIndex = minIndex;
if (minIndex < maxIndex) randomIndex = getRandValueByMinMax(minIndex, maxIndex + 1, 0);
if (tempCurNodes[i].preNodeIndexs.indexOf(randomIndex) != -1) continue;
tempCurNodes[i].preNodeIndexs.push(randomIndex);
if (indexMap.get(randomIndex) && indexMap.get(randomIndex).dx < i) continue;
indexMap.set(randomIndex, { dx: i, index: tempCurNodes[i].index });
}
//处理前一层有节点未连接情况
for (let i = 1; i < tempPreNodes.length - 1; i++) {
if (indexMap.get(tempPreNodes[i].index)) continue;
let minDx = 0, maxDx = 0;
let tempMap = new Map<number, number>();
if (tempPreNodes[i - 1] && indexMap.get(tempPreNodes[i - 1].index)) {
minDx = maxDx = indexMap.get(tempPreNodes[i - 1].index).dx
tempMap.set(minDx, indexMap.get(tempPreNodes[i - 1].index).index);
}
if (tempPreNodes[i + 1] && indexMap.get(tempPreNodes[i + 1].index)) {
maxDx = indexMap.get(tempPreNodes[i + 1].index).dx
tempMap.set(maxDx, indexMap.get(tempPreNodes[i + 1].index).index);
}
let randomDx = minDx;
if (minDx < maxDx) randomDx = getRandValueByMinMax(minDx, maxDx + 1, 0);
tempCurNodes[randomDx].preNodeIndexs.push(tempPreNodes[i].index);
if (indexMap.get(tempPreNodes[i].index) && indexMap.get(tempPreNodes[i].index).dx < randomDx) continue;
indexMap.set(tempPreNodes[i].index, { dx: randomDx, index: tempMap.get(randomDx) });
}
}
// console.log('-x-x--x-x-x-x-x-x-x-x-x- [...retLayer.values()]', util.inspect([...retLayer.values()], { depth: null }));
return [...retLayer.values()];
}
/**
* 选择节点
* @param dbRecord
* @param layerChooseNode
* @returns
*/
export async function chooseNode(dbRecord: RougelikeRecordType, layerChooseNode: layerNode, layer: number) {
const { roleId, gameCode, type, grade, curLayer, authorType } = dbRecord;
const { detailCode, nodeId, } = layerChooseNode
let nodeType = layerChooseNode.type;
const typeGradeData = gameData.rougeTypeGrade.get(type + '_' + grade);
const nodeData = gameData.rougeNode.get(nodeId);
if (!typeGradeData || !nodeData) return;
const layerPlanData = gameData.rougeLayerPlan.get(typeGradeData.layerPlan + '_' + layer);
// console.log("-x--x-x-x-x- nodeData", nodeData)
// console.log("-x--x-x-x-x- typeGradeData", typeGradeData)
// console.log("-x--x-x-x-x- layerPlanData", layerPlanData)
if (!layerPlanData) return;
let isReward = false, isShop = false;
let warId, reward = {} as CommonReward, shops: RougelikeRecordDetailType['shops'] = [],
challenge = {} as RougelikeRecordDetailType['challenge'], question = {} as RougelikeRecordDetail['question'], restPoints: RougelikeRecordDetail['restPoints'] = [];
const dbDetail = await RougelikeRecordDetailModel.findByCode(gameCode, detailCode);
let status = 0;
if (dbDetail) status = dbDetail.status || 0;
let dbPara = { roleId, layer, nodeId, nodeType, status } as RougelikeRecordDetailPara;
//普通关、精英关、boss关
if (nodeType == ROUGE_LIKE_NODE_TYPE.ORDINARY || nodeType == ROUGE_LIKE_NODE_TYPE.ELITE || nodeType == ROUGE_LIKE_NODE_TYPE.BOSS) {
isReward = true;
warId = dbPara.warId = nodeData.param;
}
//挑战关
else if (nodeType == ROUGE_LIKE_NODE_TYPE.CHALLENGE) {
let getChallengeData = getChallenge(typeGradeData.challengePlan);
if (getChallengeData) {
challenge = { challengeId: getChallengeData.challengeId, status: 1, progress: 0 } as RougelikeRecordDetailType['challenge'];
dbPara.challenge = challenge;
}
// isReward = true;
}
//商店
else if (nodeType == ROUGE_LIKE_NODE_TYPE.SHOP) {
isShop = true;
}
//休整点
else if (nodeType == ROUGE_LIKE_NODE_TYPE.REST_POINT) {
isReward = true;
if (dbDetail && dbDetail.restPoints) restPoints = dbDetail.restPoints || restPoints;
else dbPara.restPoints = restPoints;
}
//问号点
else if (nodeType == ROUGE_LIKE_NODE_TYPE.QUEST_POINT) {
if (dbDetail) {
question = dbDetail.question || {} as RougelikeRecordDetail['question'];
warId = dbDetail.warId;
}
else {
const questionMarkPLanData = gameData.rougeQuestionMarkPlan.get(nodeData.param);
if (!questionMarkPLanData) return;
let randomData = {} as DicRougeQuestionMarkPlan;
if (questionMarkPLanData.length == 1) randomData = questionMarkPLanData[0];
else randomData = getRandEelmWithWeight(questionMarkPLanData).dic;
dbPara.questType = randomData.nodeType;
if (randomData.nodeType == ROUGE_LIKE_NODE_TYPE.ORDINARY || randomData.nodeType == ROUGE_LIKE_NODE_TYPE.ELITE) {
isReward = true;
warId = dbPara.warId = 101110101//randomData.param;
}
else if (randomData.nodeType == ROUGE_LIKE_NODE_TYPE.SHOP) {
isShop = true
}
else if (randomData.nodeType == ROUGE_LIKE_NODE_TYPE.EVENT) {
let random = {} as DicRougeRandomEventPlan;
const randomEventPlanData = gameData.rougeRandomEventPlan.get(typeGradeData.randomEventPlan);
if (!randomEventPlanData) return;
if (randomEventPlanData.length == 1) random = randomEventPlanData[0];
else random = getRandEelmWithWeight(randomEventPlanData).dic;
question.randomEventId = random.randomEventId;
question.EventOptions = [];
dbPara.question = question;
}
}
}
let weightRecords = [];
if ((!dbDetail || !dbDetail.shops || dbDetail.shops.length == 0) && isShop) {
let result = await getLayerShopReward(roleId, gameCode, authorType, nodeId, layerPlanData.shopPlan);
dbPara.shops = shops = result.shops;
weightRecords = dbPara.weightRecords = (result?.weightRecords || [])
}
if (dbDetail && dbDetail.shops) { shops = dbDetail.shops || shops; weightRecords = dbDetail.weightRecords }
if ((!dbDetail || !dbDetail.rewards || dbDetail.rewards.length == 0) && isReward) {
let result = await getLayerNodeReward(roleId, gameCode, authorType, nodeId, layerPlanData.rewardPlan, layer, dbPara.questType);
if (result && result.rewards) {
reward = result;
dbPara.rewards = result.rewards;
}
if (isDevelopEnv()) {
weightRecords = dbPara.weightRecords = (result?.weightRecords || []);
}
}
if (dbDetail && dbDetail.rewards) {
let tempType = (dbDetail?.questType || 0) > 0 ? dbDetail?.questType : nodeType
const layerRewardData = gameData.rougeLayerRewardPlan.get(layerPlanData.rewardPlan + '_' + tempType);
if (!layerRewardData) return;
let { coin, score, tech } = layerRewardData;
reward = { rewards: dbDetail.rewards || [], score: score || 0, techScore: tech || 0, takeoutReward: layerPlanData.takeoutReward || [] };
if (isDevelopEnv()) {
weightRecords = dbDetail.weightRecords
}
}
if (!dbDetail) {
// console.log('-x-x--x-x-x-x-x-x-x-x-x- dbPara', util.inspect(dbPara, { depth: null }));
await RougelikeRecordDetailModel.updateByCode(gameCode, detailCode, { $set: dbPara });
await RougelikeRecordModel.updateByGameCode(gameCode, { $set: { curLayer: layer } })
await RougelikeLayerModel.updateByGameCodeAndLayer(gameCode, layer, detailCode, ROUGE_LIKE_CHOOSE_REWARD.CHOOSE)
}
let curNode = { detailCode, nodeId, nodeType, status, warId, reward, shops, challenge, question, restPoints, weightRecords }
// console.log('-x-x--x-x-x-x-x-x-x-x-x- curNode', util.inspect(curNode, { depth: null }));
return curNode;
}
/**
* 获取当前层当前节点奖励
* @param gameCode
* @param detailCode
* @param nodeId 关卡id
* @param rewardPlan 赠送奖励id
* @returns
*/
export async function getLayerNodeReward(roleId: string, gameCode: string, type: number, nodeId: number, rewardPlan: number, layer: number, nodeType?: number) {
const nodeData = gameData.rougeNode.get(nodeId);
if (!nodeData) return;
if (!nodeType) nodeType = nodeData?.nodeType || 0;
const layerRewardData = gameData.rougeLayerRewardPlan.get(rewardPlan + '_' + nodeType);
if (!layerRewardData) return;
let { charaPlan, charaRandomNum, charaChooseNum,
passiveCardPlan, charaPassivePlan, passiveCardRandomNum, passiveCardChooseNum,
holyCardPlan, holyCardRandomNum, holyCardChooseNum,
coin, score, tech } = layerRewardData;
let dbRougelikeCards = await RougelikeCardModel.findByGameCodeAndType(gameCode, ROUGE_LIKE_CARD_TYPE.PASSIVE);
let rewards: RougelikeRecordDetailType['rewards'] = [];
let charaCards = getCharaCardPlan(charaPlan, charaRandomNum);
if (charaCards && charaCards.length > 0) {
let tempOptions = [], index = 0;
for (let ele of charaCards) {
tempOptions.push({
optionIndex: index++, rewardId: ele.cardId, optionStatus: ROUGE_LIKE_CHOOSE_REWARD.NOCHOOSE,
passiveCardIds: await getSelfPassiveCards(ele.cardId, charaPassivePlan, type, dbRougelikeCards, gameCode, roleId)
})
}
rewards.push({
groupIndex: rewards.length + 1,
rewardType: ROUGE_LIKE_CARD_TYPE.CHARA,
options: tempOptions,
groupStatus: charaChooseNum > 0 ? ROUGE_LIKE_CHOOSE_REWARD.NOCHOOSE : ROUGE_LIKE_CHOOSE_REWARD.CHOOSE,
chooseNum: charaChooseNum,
reRandRewardCnt: 0,
});
}
passiveCardRandomNum = await getPassiveCardRandom(passiveCardChooseNum, passiveCardRandomNum, gameCode, layer, roleId);
let { passiveCards, passiveWeightRecords } = await getPassiveCardPlan(passiveCardPlan, passiveCardRandomNum, type, dbRougelikeCards, gameCode, roleId);
if (passiveCards && passiveCards.length > 0) {
let chooseNum = await getPassiveCardChooseNum(passiveCardChooseNum, passiveCardRandomNum, gameCode, layer, roleId);
rewards.push({
groupIndex: rewards.length + 1,
rewardType: ROUGE_LIKE_CARD_TYPE.PASSIVE,
options: passiveCards.map((ele, index) => { return { optionIndex: index++, rewardId: ele.cardId, optionStatus: ROUGE_LIKE_CHOOSE_REWARD.NOCHOOSE } }),
groupStatus: chooseNum > 0 ? ROUGE_LIKE_CHOOSE_REWARD.NOCHOOSE : ROUGE_LIKE_CHOOSE_REWARD.CHOOSE,
chooseNum,
reRandRewardCnt: 0,
});
}
let { holyCards, holyWeightRecords } = await getHolyCardPlan(holyCardPlan, holyCardRandomNum, dbRougelikeCards, gameCode, roleId);
if (holyCards && holyCards.length > 0) {
rewards.push({
groupIndex: rewards.length + 1,
rewardType: ROUGE_LIKE_CARD_TYPE.HOLY,
options: holyCards.map((ele, index) => { return { optionIndex: index++, rewardId: ele.cardId, optionStatus: ROUGE_LIKE_CHOOSE_REWARD.NOCHOOSE } }),
groupStatus: holyCardChooseNum > 0 ? ROUGE_LIKE_CHOOSE_REWARD.NOCHOOSE : ROUGE_LIKE_CHOOSE_REWARD.CHOOSE,
chooseNum: holyCardChooseNum,
reRandRewardCnt: 0,
});
}
// rewards.push({ groupIndex: rewards.length + 1, rewardType: 0, groupStatus: (coin || 0) > 0 ? ROUGE_LIKE_CHOOSE_REWARD.NOCHOOSE : ROUGE_LIKE_CHOOSE_REWARD.CHOOSE, chooseNum: coin || 0 })
return { rewards, score, techScore: tech, weightRecords: [...(passiveWeightRecords || []), ...(holyWeightRecords || [])] };
}
// 处理挑战类型中 接下来X次选择特性卡时可选择的卡片数量少1
export async function getPassiveCardRandom(passiveCardChooseNum: number, passiveCardRandomNum: number, gameCode: string, layer: number, roleId: string) {
let num = passiveCardRandomNum;
let dbDetails = await RougelikeRecordDetailModel.findByGameCodeAndLtLayer(gameCode, layer);
if (dbDetails.length == 0) return num;
for (let { challenge } of dbDetails) {
if (challenge && Object.entries(challenge).length != 0 && challenge.status == 1) {
let { challengeId } = challenge;
const rougeChallengeData = gameData.rougeChallenge.get(challengeId);
if (!rougeChallengeData) return num;
for (let effectId of (rougeChallengeData.effectId || [])) {
const rougeEffectTypeData = gameData.rougeEffect.get(effectId);
if (rougeEffectTypeData.effectType != ROUGE_EFFECT_TYPE.CHALLENGE_PASSIVE_CARD_REDUCE) continue;
num -= (rougeEffectTypeData.effectParam[1] || 0);
}
}
}
return num >= 0 ? num : 0;
}
export async function getPassiveCardChooseNum(passiveCardChooseNum: number, passiveCardRandomNum: number, gameCode: string, layer: number, roleId: string) {
let chooseNum = passiveCardChooseNum;
let dbDetails = await RougelikeRecordDetailModel.findByGameCodeAndLtLayer(gameCode, layer);
if (dbDetails.length == 0) return chooseNum;
// for (let { challenge } of dbDetails) {
// if (challenge && Object.entries(challenge).length != 0 && challenge.status == 1) {
// let { challengeId } = challenge;
// const rougeChallengeData = gameData.rougeChallenge.get(challengeId);
// if (!rougeChallengeData) return chooseNum;
// for (let effectId of (rougeChallengeData.effectId || [])) {
// const rougeEffectTypeData = gameData.rougeEffect.get(effectId);
// if (rougeEffectTypeData.effectType != ROUGE_EFFECT_TYPE.CHALLENGE_PASSIVE_CARD_REDUCE) continue;
// chooseNum -= (rougeEffectTypeData.effectParam[1] || 0);
// }
// }
// }
chooseNum += await getAddChoosePassive(roleId, gameCode);
if (chooseNum < 0) chooseNum = 0;
if (chooseNum > passiveCardRandomNum) chooseNum = passiveCardRandomNum;
return chooseNum;
}
/**
* 获取高级角色卡自带特性
* @param charaId
* @param passiveCardPlan
* @param passiveCardRandomNum
* @param type
* @param dbRougelikeCards
* @returns
*/
export async function getSelfPassiveCards(charaId: number, passiveCardPlan: number, type: number, dbRougelikeCards: RougelikeCardType[], gameCode: string, roleId: string) {
let result: number[] = [];
let charaData = gameData.rougeChara.get(charaId);
if (!charaData) return result;
if (charaData.charaType != ROUGE_CHARA_TYPE.HIGH) return result;
let { passiveCards } = await getPassiveCardPlan(passiveCardPlan, charaData.initCardCnt, type, dbRougelikeCards, gameCode, roleId);
if (passiveCards && passiveCards.length > 0) result.push(...passiveCards.map((ele) => { return ele.cardId }),);
return result;
}
/**
* 获取当前层当前节点商店数据
* @param gameCode
* @param detailCode
* @param nodeId
* @param shopPlan
* @returns
*/
export async function getLayerShopReward(roleId: string, gameCode: string, type: number, nodeId: number, shopPlan: number) {
let shops: RougelikeRecordDetailType['shops'] = [];
let shopPlanData = gameData.rougeShopPlan.get(shopPlan);
// let nodeData = gameData.rougeNode.get(nodeId);
if (!shopPlanData) return { shops };
let dbRougelikeCards = await RougelikeCardModel.findByGameCodeAndType(gameCode, ROUGE_LIKE_CARD_TYPE.PASSIVE);
let { passiveCards, passiveWeightRecords } = await getPassiveCardPlan(shopPlanData.passivecardPlanId, shopPlanData.passiveCardRandomNum, type || 0, dbRougelikeCards, gameCode, roleId);
let index = 0, discount = await getShopDiscount(roleId, gameCode);
if (passiveCards && passiveCards.length > 0) {
for (let ele of passiveCards) {
let price = gameData.rougePassiveCard.get(ele.cardId)?.price || 0
shops.push({
optionIndex: shops.length + index,
rewardType: ROUGE_LIKE_CARD_TYPE.PASSIVE,
rewardId: ele.cardId,
optionStatus: ROUGE_LIKE_CHOOSE_REWARD.NOCHOOSE,
price,
discountPrice: Math.floor(price * discount / 100),
})
index++;
}
}
let { holyCards, holyWeightRecords } = await getHolyCardPlan(shopPlanData?.holyCardPlanId, shopPlanData?.holyCardRandomNum, dbRougelikeCards, gameCode, roleId);
if (holyCards && holyCards.length > 0) {
for (let ele of holyCards) {
let price = gameData.rougeHolyCard.get(ele.cardId)?.purchasePrice || 0;
shops.push({
optionIndex: shops.length + index,
rewardType: ROUGE_LIKE_CARD_TYPE.HOLY,
rewardId: ele.cardId, optionStatus: ROUGE_LIKE_CHOOSE_REWARD.NOCHOOSE,
price,
discountPrice: Math.floor(price * discount / 100),
})
index++;
}
}
return { shops, weightRecords: [...(passiveWeightRecords || []), ...(holyWeightRecords || [])] };
}
/**
* 检测配置数据是否满足随机数量
* @param planId
* @param randomNum
* @returns
*/
export function checkRandomLimit(planId: number, randomNum: number, rewardType: number) {
let cards: DicRougeCharaCardPlan[] = [];
if (!planId || planId == 0 || !randomNum || randomNum == 0) return cards;
let cardPlanDatas = gameData.rougeCharaCardPlan.get(planId);
if (rewardType == ROUGE_LIKE_CARD_TYPE.PASSIVE) cardPlanDatas = gameData.rougePassiveCardPlan.get(planId);
else if (rewardType == ROUGE_LIKE_CARD_TYPE.HOLY) cardPlanDatas = gameData.rougeHolyCardPlan.get(planId);
if (!cardPlanDatas) return cards;
else if (cardPlanDatas.length < randomNum) {
console.error("checkRandomLimit可随机的角色卡数量少于需要数量, planId=%s, randomNum=%s", planId, randomNum);
return cardPlanDatas;
}
else if (cardPlanDatas.length >= randomNum) return cardPlanDatas;
return cards;
}
/**
* 获取角色卡随机
* @param planId
* @param charaRandomNum
* @returns
*/
export function getCharaCardPlan(planId: number, charaRandomNum: number) {
let cards = checkRandomLimit(planId, charaRandomNum, ROUGE_LIKE_CARD_TYPE.CHARA);
if (cards.length <= charaRandomNum) return cards;
let randResult = getRandEelmWithWeightAndNum(cards, charaRandomNum);
return randResult.map(cur => cur.dic);
}
/**
* 获取特性卡随机
* @param passiveCardPlan
* @param passiveCardRandomNum
*/
export async function getPassiveCardPlan(passiveCardPlan: number, passiveCardRandomNum: number, type: number, dbRougelikeCards: RougelikeCardType[], gameCode: string, roleId: string) {
let cards = checkRandomLimit(passiveCardPlan, passiveCardRandomNum, ROUGE_LIKE_CARD_TYPE.PASSIVE);
// if (cards.length <= passiveCardRandomNum) return { passiveCards: cards };
if (cards.length == 0) return { passiveCards: cards };
// 计算变化权重
let lableMap = new Map<number, number>(); //统计lable数量
if (dbRougelikeCards && dbRougelikeCards.length > ROUGELIKE.PASSIVE_LABLE_NUM) {
for (let { cardId } of dbRougelikeCards) {
if (!cardId) continue;
let passiveCardData = gameData.rougePassiveCard.get(cardId);
if (!passiveCardData || !passiveCardData.passiveLabel || passiveCardData.passiveLabel.length == 0) continue;
for (let val of passiveCardData.passiveLabel) {
if (!lableMap.get(val)) {
lableMap.set(val, 1);
continue;
}
lableMap.set(val, lableMap.get(val) + 1);
}
}
}
let newCards = [];
let cardsMap = await getCardCount(gameCode, ROUGE_LIKE_CARD_TYPE.PASSIVE);
const { chooseCardsMap, noChooseCardsMap } = await getIsChooseCard(gameCode);
for (let obj of cards) {
let weightRecord: { originalWight?: number, passiveRedWight?: number, holyAddWeight?: number, passiveLableNum?: number, authorAddWeight?: number, passiveLableNumAddWeight?: number, finalWeight?: number } = {};
if (!obj) continue;
let { cardId, weight } = obj;
if (!cardId || !weight) continue;
let passiveCardData = gameData.rougePassiveCard.get(cardId);
if (!passiveCardData) continue;
const getLimit = cardsMap.get(cardId) || 0;
if (getLimit >= (passiveCardData?.getLimit || 0)) continue; //处理限制获取数量
weightRecord.originalWight = weight;
if (chooseCardsMap.has(cardId)) {
weight = Math.floor(weight * (1 - ROUGELIKE.SELECT_PASSIVECARD_WEIGHT / 100));
weightRecord.passiveRedWight = Math.floor(weight * ROUGELIKE.SELECT_PASSIVECARD_WEIGHT / 100);
}
else if (noChooseCardsMap.has(cardId)) {
weight = Math.floor(weight * (1 - ROUGELIKE.RANDOM_PASSIVECARD_WEIGHT / 100));
weightRecord.passiveRedWight = Math.floor(weight * ROUGELIKE.RANDOM_PASSIVECARD_WEIGHT / 100);
}
const holyAddWeight = await getAddPassiveWeight(roleId, gameCode, type);
weight += holyAddWeight;
weightRecord.holyAddWeight = holyAddWeight;
const { authorType = 0, quality = 0, lv = 0 } = passiveCardData;
if (authorType == type) {
const passiveWeightData = gameData.rougePassiveWeight.get(authorType + '_' + quality + '_' + lv);
if (passiveWeightData && passiveWeightData.authorTypeWeightAdd) {
weight += passiveWeightData.authorTypeWeightAdd;
weightRecord.authorAddWeight = passiveWeightData.authorTypeWeightAdd;
}
}
let labelNum = lableMap.get(cardId) || 0;
if (labelNum >= ROUGELIKE.PASSIVE_LABLE_NUM) {
weight += ROUGELIKE.PASSIVE_LABLE_ADD_RANDOM * (Math.ceil(labelNum / ROUGELIKE.PASSIVE_LABLE_NUM));
weightRecord.passiveLableNum = labelNum;
weightRecord.passiveLableNumAddWeight = ROUGELIKE.PASSIVE_LABLE_ADD_RANDOM * (Math.ceil(labelNum / ROUGELIKE.PASSIVE_LABLE_NUM));
}
weightRecord.finalWeight = weight;
newCards.push({ ...obj, weight, weightRecord });
}
let targetPassives = await getChooseQualityPassives(roleId, gameCode, newCards);
let result = [];
if (passiveCardRandomNum >= 1 && targetPassives.length > 0) result.push(getRandEelmWithWeight(targetPassives).dic);
let randResult = getRandEelmWithWeightAndNum(newCards, passiveCardRandomNum - result.length);
return { passiveCards: [...result, ...randResult.map(cur => cur.dic)], passiveWeightRecords: newCards }
}
export async function getIsChooseCard(gameCode: string) {
const dbDetails = await RougelikeRecordDetailModel.findByGameCode(gameCode);
let chooseCardsMap = new Map<number, number>();
let noChooseCardsMap = new Map<number, number>();
for (const { rewards } of dbDetails) {
if (!rewards || rewards.length == 0) continue;
for (const { options } of rewards) {
if (!options || options.length == 0) continue;
for (const { rewardId, optionStatus } of options) {
if (optionStatus != 0) {
chooseCardsMap.set(rewardId, rewardId);
continue;
}
noChooseCardsMap.set(rewardId, rewardId);
}
}
}
return { chooseCardsMap, noChooseCardsMap };
}
export async function getCardCount(gameCode: string, type: number) {
const dbCards: RougelikeCardType[] = await RougelikeCardModel.findByGameCodeAndType(gameCode, type);
let cardsMap = new Map<number, number>();
dbCards.forEach((cur) => { cardsMap.set(cur.cardId, (cardsMap.get(cur.cardId) || 0) + 1); })
return cardsMap;
}
/**
* 获取圣物随机
* @param planId
* @param holyCardPlan
*/
export async function getHolyCardPlan(holyCardPlan: number, holyCardRandomNum: number, dbRougelikeCards: RougelikeCardType[], gameCode: string, roleId: string) {
let cards = checkRandomLimit(holyCardPlan, holyCardRandomNum, ROUGE_LIKE_CARD_TYPE.HOLY);
// if (cards.length <= holyCardRandomNum) return { holyCards: cards };
if (cards.length == 0) return { holyCards: cards };
let lableMap = new Map<number, number>();//统计lable数量
if (dbRougelikeCards && dbRougelikeCards.length > ROUGELIKE.HOLY_LABLE_NUM) {
for (let { cardId } of dbRougelikeCards) {
if (!cardId) continue;
let passiveCardData = gameData.rougePassiveCard.get(cardId);
if (!passiveCardData || !passiveCardData.holyLabel || passiveCardData.holyLabel.length == 0) continue;
for (let val of passiveCardData.holyLabel) {
if (!lableMap.get(val)) {
lableMap.set(val, 1);
continue;
}
lableMap.set(val, lableMap.get(val) + 1);
}
}
}
// 计算变化权重
let newCards = [];
let cardsMap = await getCardCount(gameCode, ROUGE_LIKE_CARD_TYPE.HOLY);
const { chooseCardsMap, noChooseCardsMap } = await getIsChooseCard(gameCode);
for (let obj of cards) {
let weightRecord: {
originalWight?: number, passiveRedWight?: number, holyRedWight?: number, authorAddWeight?: number,
passiveLableNum?: number, passiveLableNumAddWeight?: number, holyLableNum?: number, holyLableNumAddWeight?: number,
finalWeight?: number
} = {};
if (!obj) continue;
let { cardId, weight } = obj;
if (!cardId || !weight) continue;
let holyCardData = gameData.rougeHolyCard.get(cardId);
if (!holyCardData) continue;
const getLimit = cardsMap.get(cardId) || 0;
if (getLimit >= (holyCardData?.getLimit || 0)) continue; //处理限制获取数量
weightRecord.originalWight = weight;
if (chooseCardsMap.has(cardId)) {
weight = Math.floor(weight * (1 - ROUGELIKE.SELECT_HOLLYCARD_WEIGHT / 100));
weightRecord.holyRedWight = Math.floor(weight * ROUGELIKE.SELECT_HOLLYCARD_WEIGHT / 100);
}
else if (noChooseCardsMap.has(cardId)) {
weight = Math.floor(weight * (1 - ROUGELIKE.RANDOM_HOLLYCARD_WEIGHT / 100));
weightRecord.holyRedWight = Math.floor(weight * ROUGELIKE.RANDOM_HOLLYCARD_WEIGHT / 100);
}
if (!holyCardData.label) {
newCards.push({ ...obj });
weightRecord.finalWeight = weight;
continue;
};
let labelNum = lableMap.get(holyCardData.label) || 0;
if (labelNum < ROUGELIKE.HOLY_LABLE_NUM || ROUGELIKE.HOLY_LABLE_NUM == 0) {
newCards.push({ ...obj });
weightRecord.finalWeight = weight;
continue;
};
weight += ROUGELIKE.HOLY_LABLE_ADD_RANDOM * (Math.ceil(labelNum / ROUGELIKE.HOLY_LABLE_NUM));
weightRecord.holyLableNum = labelNum;
weightRecord.holyLableNumAddWeight = ROUGELIKE.HOLY_LABLE_ADD_RANDOM * (Math.ceil(labelNum / ROUGELIKE.HOLY_LABLE_NUM));
weightRecord.finalWeight = weight;
newCards.push({ ...obj, weight, weightRecord });
}
let randResult = getRandEelmWithWeightAndNum(newCards, holyCardRandomNum);
return { holyCards: randResult.map(cur => cur.dic), holyWeightRecords: newCards }
}
/**
* 获取挑战关卡数据
* @param planId
* @returns
*/
export function getChallenge(planId: number) {
const randomChallenge = getChallengePlan(planId);
if (!randomChallenge) return;
const rougeChallengeData = gameData.rougeChallenge.get(randomChallenge.challengeId);
if (!rougeChallengeData) return;
return rougeChallengeData;
}
/**
* 获取挑战关随机
* @param planId
* @returns
*/
export function getChallengePlan(planId: number) {
const challengePlanData = gameData.rougeChallengePlan.get(planId);
if (!challengePlanData) return;
if (challengePlanData.length == 1) return challengePlanData[0];
return getRandEelmWithWeight(challengePlanData).dic;
}
export async function updateChalleng(dbRecord: RougelikeRecordType, roleId: string, sid: string, gameCode: string, curLayer: number, rougeDamage, isAp?: boolean, isRound?: boolean) {
let len = rougeDamage.length;
let challenges: { challengeId: number, status: number, progress: number, detailCode: string }[] = [];
const { authorType, type, grade } = dbRecord;
let dbDetails = await RougelikeRecordDetailModel.findByGameCodeAndLtLayer(gameCode, curLayer);
if (dbDetails.length == 0) return true;
let updateChallengs: RougelikeRecordDetailPara[] = [];
for (let { detailCode, challenge, rewards = [], nodeId, layer } of dbDetails) {
if (challenge && Object.entries(challenge).length != 0 && challenge.status == 1) {
let { challengeId } = challenge;
const rougeChallengeData = gameData.rougeChallenge.get(challengeId);
if (!rougeChallengeData) return true;
for (let effectId of (rougeChallengeData.effectId || [])) {
const rougeEffectTypeData = gameData.rougeEffect.get(effectId);
if (len == 0) {
//接下来X次选择特性卡时可选择的卡片数量少1
if (rougeEffectTypeData.effectType != ROUGE_EFFECT_TYPE.CHALLENGE_PASSIVE_CARD_REDUCE) continue;
} else {
if (rougeEffectTypeData.effectType == ROUGE_EFFECT_TYPE.CHALLENGE_PASSIVE_CARD_REDUCE) continue;
if (rougeEffectTypeData.effectType == ROUGE_EFFECT_TYPE.CHALLENGE_CHARA_NO_AP_SKILL && isAp) continue;
if (rougeEffectTypeData.effectType == ROUGE_EFFECT_TYPE.CHALLENGE_CHARA_NO_ROUND_SKILL && isRound) continue;
if (rougeEffectTypeData.effectType == ROUGE_EFFECT_TYPE.CHALLENGE_CHARA_HP_LIMIT) {
let isNext = true;
for (let obj of rougeDamage) {
const { maxHp = 0, hp = 0 } = (obj || {});
if (maxHp == 0 || hp == 0) { isNext = false; break; }
if (maxHp * (rougeEffectTypeData.effectParam[1] || 0) / 100 > hp) { isNext = false; break; }
}
if (!isNext) continue;
}
if (rougeEffectTypeData.effectType == ROUGE_EFFECT_TYPE.CHALLENGE_CHARA_NUM_LIMIT && (rougeEffectTypeData.effectParam[1] || 0) < len) continue;//接下来X场战斗每场战斗不超过上阵2名学员
}
challenge.progress += 1;
if (challenge.progress == rougeChallengeData.condition) {
challenge.status = 2;
//处理在挑战进度完成时再随机奖励
const typeGradeData = gameData.rougeTypeGrade.get(type + '_' + grade);
if (!typeGradeData) continue;
const layerPlanData = gameData.rougeLayerPlan.get(typeGradeData.layerPlan + '_' + layer);
if (!layerPlanData) continue;
let result = await getLayerNodeReward(roleId, gameCode, authorType, nodeId, layerPlanData.rewardPlan, layer);
rewards = result.rewards;
};
}
challenges.push({ challengeId, status: challenge.status, progress: challenge.progress, detailCode });
updateChallengs.push({ gameCode, detailCode, challenge, status: 1, rewards });
}
}
await RougelikeRecordDetailModel.bulkWriteUpdate(updateChallengs);
await sendMessageToUserWithSuc(roleId, PUSH_ROUTE.ROUGE_CHALLENGE_UPDATE, { challenges }, sid);
return true;
}
export function getLayerRewardOneData(type: number, grade: number, layer: number, nodeType: number) {
let result: { takeoutReward?: RewardInter[], coin?: number, score?: number, tech?: number, spiritPlan?: RewardInter[] } = {};
const typeGradeData = gameData.rougeTypeGrade.get(type + '_' + grade);
if (!typeGradeData) return result;
const layerPlanData = gameData.rougeLayerPlan.get(typeGradeData.layerPlan + '_' + layer);
if (!layerPlanData) return result;
result = { takeoutReward: layerPlanData.takeoutReward, spiritPlan: layerPlanData.spiritPlan }
const layerRewardData = gameData.rougeLayerRewardPlan.get(layerPlanData.rewardPlan + '_' + nodeType);
if (!layerRewardData) return result;
result = { ...layerRewardData, ...result };
return result;
}
export function getRandomSpirit(spiritPlan: number, randomNum: number) {
let spiritId: number[] = [];
const spiritPlanData = gameData.spiritPlan.get(spiritPlan);
if (randomNum == 0 || !spiritPlanData || spiritPlanData.length == 0) return spiritId;
let random = getRandEelmWithWeightAndNum(spiritPlanData, randomNum);
spiritId = random.map(cur => { return cur.dic.spiritId });
return spiritId;
}
/**
* 获取
* @param authorType
* @param cards
* @returns
*/
export function getAuthorTypeCardNum(authorType: number, cards: Card[]) {
return cards.filter(card => {
let dicCard = card.cardId == 0 ? null : gameData.rougePassiveCard.get(card.cardId);
if (!dicCard) return false;
return dicCard.authorType == authorType;
}).length;
}
export async function repaireSendScoreReward() {
let maxNum = gameData.rougeScoreNum.num || 0;
let refTime = getZeroPointOfTimeD(Date.now() - 86400000, SHOP_REFRESH_TYPE.WEEKLY);
let allRewards = await RougelikeScoreModel.findByReceiveNum(refTime, maxNum);
let _ids: string[] = [];
for (let { roleId, received, _id, score } of allRewards) {
let goods: RewardInter[] = [];
for (let [index, { reward, score: targetScore }] of gameData.rougeScoreReward) {
if (score >= targetScore && !received.includes(index)) goods.push(...reward);
}
await sendMailByContent(MAIL_TYPE.ROUGE_SCORE_REPAIRE, roleId, { goods });
_ids.push(_id);
}
await RougelikeScoreModel.receiveAll(_ids, maxNum + 1);
}
/**
* 获取最大血量
* @param charaId
* @param type
* @param grade
* @returns
*/
export async function getMaxHp(roleId: string, gameCode: string, charaId: number, type: number, grade: number,) {
let maxHp = 0;
const charaData = gameData.rougeChara.get(charaId);
if (!charaData || !charaData.heroId) return maxHp;
const heroData = gameData.hero.get(charaData.heroId);
// console.log("x-x-x-x--x-xx- heroData", heroData);
if (!heroData || !heroData.hp) return maxHp;
const typeGradeData = gameData.rougeTypeGrade.get(type + '_' + grade);
// console.log("x-x-x-x--x-xx- typeGradeData", typeGradeData);
if (!typeGradeData || !typeGradeData.heroValue) return maxHp;
let rougeEffect = new RougeEffect(roleId, gameCode);
const holyMaxHp = await rougeEffect.getEffectMaxHp();
maxHp = heroData.hp * typeGradeData.heroValue / 10000 * (1 + holyMaxHp / 100);
return Math.floor(maxHp);
}
export async function updateMaxHp(roleId: string, gameCode: string, type: number, grade: number, updateCharasMap?: Map<string, RougelikeCharaPara>) {
let dbCharas = await RougelikeCharaModel.findByGameCode(gameCode);
if (dbCharas.length == 0) return [];
let result: RougelikeCharaType[] = [];
for (let val of dbCharas) {
if (updateCharasMap && updateCharasMap.has(val.charaCode)) {
val = { ...val, ...updateCharasMap.get(val.charaCode) }
}
let tempMaxHp = await getMaxHp(roleId, gameCode, val.charaId, type, grade);
if (tempMaxHp == val.maxHp && updateCharasMap && !updateCharasMap.has(val.charaCode)) continue;
let preMaxHp = val.maxHp;
val.maxHp = tempMaxHp;
val.hp = Math.min(val.hp + tempMaxHp - preMaxHp, tempMaxHp);
result.push(val);
}
await RougelikeCharaModel.bulkWriteUpdate(result);
let { charas } = formateCharasOrCards(result, ROUGE_LIKE_CARD_TYPE.CHARA)
return charas || [];
}
// 获取当前层之前所有未完成挑战关
export async function getPreCurLayerChallengs(gameCode: string, layer: number) {
let challenges: { challengeId: number, status: number, progress: number, detailCode: string, reward: CommonReward }[] = [];
let dbDetails = await RougelikeRecordDetailModel.findByGameCodeAndLtLayer(gameCode, layer);
if (dbDetails.length == 0) return challenges;
for (let { detailCode, challenge, rewards } of dbDetails) {
if (!challenge) continue;
const { challengeId, status, progress } = challenge;
if (status == 3) continue;
challenges.push({ challengeId, status, progress, detailCode, reward: { rewards } });
}
return challenges;
}
export async function getGame(roleId: string) {
let isPlaying = true, nodes: RougelikeLayerType[] = [], hasPass = false, curNode = {};
const dbRecord = await RougelikeRecordModel.findByRoleIdAndStatus(roleId, ROUGE_LIKE_STATUS.SUCCESS);
if (!dbRecord) {
isPlaying = false;
return { isPlaying };
}
const { gameCode, grade, type, authorType = 0, curLayer = 0, maxLayer = 0, coin = 0, score = 0, techScore = 0, coinTotal = 0 } = dbRecord;
const dbNodes = await RougelikeLayerModel.findByGameCode(gameCode);
let dbCurLayerChooseNode = {} as layerNode;
if (dbNodes) nodes = dbNodes.map((obj) => {
const { layer, layerNodes, hasPass: dbHasPass = false } = obj;
if (layer == curLayer) {
hasPass = dbHasPass;
dbCurLayerChooseNode = layerNodes.find(cur => cur.isChoose == ROUGE_LIKE_CHOOSE_REWARD.CHOOSE);
}
return { layer, layerNodes } as RougelikeLayerType
})
const charas: CommonChara[] = formateCharasOrCards(await RougelikeCharaModel.findByGameCode(gameCode), ROUGE_LIKE_CARD_TYPE.CHARA)?.charas || [];
const cards: CommonCard[] = formateCharasOrCards(await RougelikeCardModel.findByGameCode(gameCode), ROUGE_LIKE_CARD_TYPE.PASSIVE | ROUGE_LIKE_CARD_TYPE.HOLY)?.cards || [];
// console.log("x-x-x-x-x-x-x-x- dbCurLayerChooseNode", dbCurLayerChooseNode)
if (Object.entries(dbCurLayerChooseNode).length != 0) curNode = await chooseNode(dbRecord, dbCurLayerChooseNode, curLayer)
return {
isPlaying, gameCode, grade, type, authorType,
curLayer, hasPass, maxLayer, coin, coinTotal, score, techScore, nodes: nodes || [], charas, cards, curNode,
preChallengs: await getPreCurLayerChallengs(gameCode, curLayer)
};
}