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, RougelikeScorePara } from "../../db/RougelikeScore"; import { getTechData } from "./rougeTechService"; import { getZeroPointOfTimeD } from "../../pubUtils/timeUtil"; import { sendMailByContent } from "../mailService"; 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"; import { DicRougeCharaCardPlan } from "../../pubUtils/dictionary/DicRougeCharaCardPlan"; 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); const lastMaxLv = await RougelikeRecordModel.getLastMaxLv(roleId); return { isPlaying, gameCode, weeklyScore: dbScore?.score || 0, receivedScore: dbScore?.received || [], ...techData, collections, limitIds, takeoutRewardCnt: dbScore?.takeoutRewardCnt, lastMaxLv } } /* /** * 获取初始三名角色卡 * @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(); //获取可随机到的节点 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) { 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(); //记录下前一层有那些节点与当前层是否连线 //当前层首节点 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(); 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) { if (dbDetail) { challenge = dbDetail.challenge; } else { 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 = 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 startTime = Date.now(); let result = await getLayerNodeReward(roleId, gameCode, authorType, nodeId, layerPlanData.rewardPlan, layer, dbPara.questType); console.log('*********************************nodeType=%s getLayerNodeReward time=%s', nodeType, Date.now() - startTime) 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(); //统计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); let isHolyAddWeight = false; for (let obj of cards) { let { cardId, weight } = obj || {}; let passiveCardData = gameData.rougePassiveCard.get(cardId); if (!passiveCardData || !passiveCardData.authorType) continue; if (passiveCardData.authorType == type) { isHolyAddWeight = true; break; } } let holyAddWeight = 0; if (isHolyAddWeight) { holyAddWeight = await getAddPassiveWeight(roleId, gameCode, type); } 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 { authorType = 0, quality = 0, lv = 0 } = passiveCardData; if (authorType == type) { weight += holyAddWeight; weightRecord.holyAddWeight = holyAddWeight; 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(); let noChooseCardsMap = new Map(); 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(); 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();//统计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 refTime = new Date(getZeroPointOfTimeD(Date.now() - 86400000, SHOP_REFRESH_TYPE.WEEKLY).getTime() - (86400000 * 7)); let allRewards = await RougelikeScoreModel.findByRefTime(refTime); let updateArr: RougelikeScorePara[] = []; const roleIds = allRewards.map(cur => { return cur.roleId }); const lastMaxLvMap = await RougelikeRecordModel.getLastMaxLvMap(roleIds); for (let { roleId, received, score, receiveNum } of allRewards) { if (!lastMaxLvMap.has(roleId)) continue; let goods: RewardInter[] = []; const rougeScoreRewardByLvData = gameData.rougeScoreRewardByLv.get(lastMaxLvMap.get(roleId)); if (receiveNum >= rougeScoreRewardByLvData.size) continue; for (let [index, { reward, score: targetScore }] of rougeScoreRewardByLvData) { if (score >= targetScore && !received.includes(index)) { goods.push(...reward); } } await sendMailByContent(MAIL_TYPE.ROUGE_SCORE_REPAIRE, roleId, { goods }); updateArr.push({ roleId, refTime, receiveNum: rougeScoreRewardByLvData.size }); } await RougelikeScoreModel.bulkWriteUpdate(updateArr); } /** * 获取最大血量 * @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) { 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; if (val.hp > 0) { 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) }; }