import { beginGame, endGame, sendQuestion, updateRound, updateScore } from './WsSvr' import { Puzzle } from '../models/content/Puzzle' import { Schedule } from '../clock/Schedule' import { BaseConst } from '../constants/BaseConst' import { QCategoryCache } from './QCategoryCache' import { PuzzleSession } from '../models/match/PuzzleSession' import { GameEnv } from '../config/GameEnv' import { Shop } from '../models/shop/Shop' import { mission_vo } from '../config/parsers/mission_vo' import { ShopActivity } from '../models/shop/ShopActivity' import { getAccountRank, getRankList, updateRank } from './Rank' import { PuzzleRank } from '../models/match/PuzzleRank' export function transformRecord(records: any[]) { return records.map(o => { let answers = [] for (let i = 1; i <= 4; i++) { if (o[`a${ i }`]) { answers.push(o[`a${ i }`]) } } answers.randomSort() let type = new QCategoryCache().getType(o.tag) let subType = new QCategoryCache().getType(o.sub_tag) return { id: o._id, title: o.question, answers, type: 1, category: type + '-' + subType, quality: o.quality } }) } /** * 检查是否所有人都已经答了当前题目 * * @param history * @param {string} qid * @return {boolean} */ export function checkSubFinish(history: any, qid: string) { for (let [key, val] of history.members) { if (!val.answer?.has(qid)) { return false } } return true } /** * 开始游戏 * @param {string} roomId * @param sessionId * @return {Promise} */ export async function startGame(roomId: string, sessionId: string) { let history = await PuzzleSession.findById(sessionId) let records = await Puzzle.randomQuestions({status: 1, is_hide: 0, deleted: 0, dp: 1}, history.total) await beginGame(roomId, {}) for (let record of records) { history.questions.set(record.id, record.a1) } history.markModified('questions') await history.save() new Schedule().beginSchedule(BaseConst.FIST_QUESTION_DELAY, async function () { await sendOneQuestion(history) }, history.scheduleKey) } export async function sendOneQuestion(history: any) { const roomId = history.room new Schedule().stopSchedule(history.scheduleKey) if (history.current >= history.questions.size) { console.log('match over') history.status = 9 await history.save() await endGame(roomId, {closeTime: BaseConst.ROOM_AUTO_CLOSE_TIME}) return } const questions:string[] = Array.from(history.questions.keys()) let qid: string = questions[history.current] let record = await Puzzle.findById(qid) let qdata: any = transformRecord([record])[0] qdata.no = history.current await updateRound(roomId, history.current) await sendQuestion(roomId, qdata) history.current++ await history.save() new Schedule().beginSchedule(new GameEnv().pvpQuestionInterval, async function () { let subHistory = await PuzzleSession.findById(history.id) let datas = [] for (let [accountid, data] of subHistory.members) { if (!data.answer.has(qid)) { data.answer.set(qid, 0) data.errorCount++ data.comboCount = 0 datas.push({ accountid, score: 0 }) } } subHistory.markModified('members') await subHistory.save() await updateScore(subHistory.room, datas) await sendOneQuestion(subHistory) }, history.scheduleKey) } export function calcExamScore(time: number, combo: number) { const cfg = new GameEnv() return cfg.pvpBaseScore + time / 100 * cfg.pvpTimeRate + combo * cfg.pvpComboRate } export function calcPvpScore(timeKey: string, combo: number) { const time = new Schedule().getLeftTime(timeKey) const cfg = new GameEnv() return cfg.pvpBaseScore + time / 100 * cfg.pvpTimeRate + combo * cfg.pvpComboRate } export function rankKey(shop: string, level: number|string, mode: number) { return `${shop}_${mode}_${level}` } /** * 更新排行榜 * @param {string} shop 店铺id * @param {number} level 关卡id * @param {string} accountId 帐号id * @param {number} score 分数 * @param {string} session * @param {number} mode 模式: 0: 单人 1: pvp * @return {Promise} */ export async function updateSingleRank({shop, level, accountId, score, session, mode } : {shop: string, level: number | string, accountId: string, score: number, mode: number, session: string}) { const key = rankKey(shop, level, mode) await updateRank(accountId, score, key) let record = (await PuzzleRank.findOrCreate({shop, level, accountId, mode})).doc if (record.score < score) { record.score = score record.session = session await record.save() } let scoreTotal = await PuzzleRank.fetchTotalScore(shop, accountId, mode) const totalKey = rankKey(shop, 'total', mode) await updateRank(accountId, scoreTotal, totalKey) } export async function updateExamRank({shop, level, accountId, score, session, mode } : {shop: string, level: number | string, accountId: string, score: number, mode: number, session: string}) { const totalKey = rankKey(shop, 'exam', mode) await updateRank(accountId, score, totalKey) } export async function getRank({shop, level, accountId, mode, skip, limit } : {shop: string, level: number | string, accountId: string, mode: number, skip: number, limit: number}) { // let records = await PuzzleRank.find({shop, level, mode}).limit(limit).skip(skip).sort({'score': -1}) const key = rankKey(shop, level, mode) let datas: any = await getRankList(skip, limit, key) // let scoreMap: Map = new Map() let rankList: any[][] = [] for (let i = 0, l = datas.length; i < l; i += 2) { // scoreMap.set(datas[i], datas[i + 1] << 0) rankList.push([datas[i], datas[i + 1] << 0]) } let userRank = (await getAccountRank(accountId, key)) || 999 return {rankList, userRank} } /** * 单人模式检查游戏是否结束 * @param history * @param {string} accountId * @return {number} 0: 未结束, -1: 失败, 1: 胜利 */ export function checkSingleFinish(history: any, accountId: string): number { let result = 0 let stat = history.members.get(accountId) if (history.hasExpired()) { result = -1 } else { let cfg = fetchLevelCfg(history.level) if (stat.rightCount >= cfg.enemy) { result = 1 } else if (stat.errorCount >= cfg.hp) { result = -1 } } if (result !== 0) { stat.gameResult = result } return result } /** * 计算单人模式的得分 * @return {number} */ export function calcSingleScore(history: any, accountId: string) { let cfgLevel = fetchLevelCfg(history.level) let time = cfgLevel.time - (Date.now() - history.createdAt.getTime()) / 1000 let stat = history.members.get(accountId) let combo = stat.maxCombo let hp = cfgLevel.hp - stat.errorCount hp = hp < 0 ? 0 : hp time = time < 0 ? 0 : time let cfg = new GameEnv() let score = time * cfg.singleTimeRate + hp * cfg.singleHpRate + combo * cfg.singleComboRate; (history.difficultyMode) && (score *= cfg.singleLowLvlRate) stat.score = score let star = 0; (time >= cfgLevel.timestar) && star++; (stat.maxCombo >= cfgLevel.enemystar) && star++; (hp >= cfgLevel.hpstar) && star ++; stat.star = star stat.timeLeft = time return { score, star } } /** * 确定pvp模式题目类型 * @param {string} shopId * @param {string} aid 活动配置 * @return {Promise} */ export async function fetchPvpPuzzleType({shopId, aid} : {shopId: string, aid?: string}) { //{$or:[{tag: {$in: ['a', 'SS']}}, {sub_tag: {$in: ['a', 'SS']}}]} let typeArr: string[] = [] if (aid) { let actData = await ShopActivity.findById(aid) if (actData?.qtypes?.length > 0) { typeArr = actData.qtypes } } if (typeArr.length === 0 ) { let shop = await Shop.findById(shopId) if (shop?.qtypes?.length > 0) { typeArr = shop.qtypes } } // let params = {$or: [{tag: {$in: typeArr}}, {sub_tag: {$in: typeArr}}]} return typeArr } /** * 获取单人模式题目类型 * @param {string} shopId * @param {mission_vo} levelCfg * @return {Promise} */ export async function fetchSinglePuzzleType({shopId, levelCfg} : {shopId: string, levelCfg?: mission_vo}) { let typeArr: string[] = [] let shop = await Shop.findById(shopId) if (shop?.qtypes?.length > 0) { typeArr = shop.qtypes } if (typeArr.length === 0 && levelCfg?.keyID) { typeArr = levelCfg.keyID.split(':') } return typeArr } /** * 获取关卡配置信息 * @param {number} level */ export function fetchLevelCfg(level: number) { const cfgs: mission_vo[] = Array.from(global.$cfg.get(BaseConst.MISSION).values()) return cfgs.find(o => o.number == level) || cfgs[cfgs.length - 1] }