diff --git a/game-server/app/servers/activity/handler/entertainHandler.ts b/game-server/app/servers/activity/handler/entertainHandler.ts new file mode 100644 index 000000000..700f1bc52 --- /dev/null +++ b/game-server/app/servers/activity/handler/entertainHandler.ts @@ -0,0 +1,112 @@ +import { Application, BackendSession, HandlerService, } from 'pinus'; +import { getRandSingleEelm, parseNumberList, resResult } from '../../../pubUtils/util'; +import { ITEM_CHANGE_REASON, STATUS } from '../../../consts'; +import { addReward, stringToConsumeParam, stringToRewardParam } from '../../../services/activity/giftPackageService'; +import { getPlayerEntertainData, getPlayerEntertainDataShow } from '../../../services/activity/entertainService'; +import { ActivityEntertainRecModel } from '../../../db/ActivityEntertainRec'; +import { handleCost } from '../../../services/role/rewardService'; + +export default function (app: Application) { + new HandlerService(app, {}); + return new EntertainHandler(app); +} + +export class EntertainHandler { + constructor(private app: Application) { + } + + /** + * @description 获取火神祭祀活动数据 + * @param {{ activityId: number}} msg + * @param {BackendSession} session + * @memberof EntertainHandler + */ + async getData(msg: { activityId: number }, session: BackendSession) { + const { activityId } = msg; + const roleId = session.get('roleId'); + const serverId = session.get('serverId'); + + let playerData = await getPlayerEntertainDataShow(activityId, serverId, roleId); + + if (!playerData) return resResult(STATUS.ACTIVITY_MISSING); + + return resResult(STATUS.SUCCESS, playerData); + } + + /** + * @description 宴请 + * @param {{ activityId: number }} msg + * @param {BackendSession} session + * @memberof EntertainHandler + */ + async invite(msg: { activityId: number }, session: BackendSession) { + const { activityId } = msg; + const roleId = session.get('roleId'); + const roleName = session.get('roleName'); + const sid = session.get('sid'); + const serverId = session.get('serverId'); + + let playerData = await getPlayerEntertainData(activityId, serverId, roleId); + if (!playerData) return resResult(STATUS.ACTIVITY_MISSING); + // 挑战次数 + if(playerData.playCnt >= playerData.freeCnt + playerData.buyCnt) return resResult(STATUS.ACTIVITY_ENTERTAIN_NO_NUM); + + let pool = playerData.heroes.filter(hero => { + if(hero.num >= hero.maxNum) return false; + for(let condition of hero.conditionArr) { + if(condition.type == 1 && playerData.invitedHeroNum < condition.param) return false; + if(condition.type == 2 && playerData.invitedHids.indexOf(condition.param) == -1) return false; + } + return true; + }); + if(pool.length <= 0) return resResult(STATUS.ACTIVITY_ENTERTAIN_NO_NUM); + let randResult = getRandSingleEelm(pool); + if(!randResult) return resResult(STATUS.ACTIVITY_ENTERTAIN_NO_NUM); + + let rewards = stringToRewardParam(randResult.reward); + await ActivityEntertainRecModel.record(serverId, activityId, playerData.roundIndex, roleId, { todayIndex: playerData.todayIndex, id: randResult.id, hid: randResult.hid, time: new Date(), reward: randResult.reward }) + let { goods } = await addReward(roleId, roleName, sid, serverId, rewards, ITEM_CHANGE_REASON.ACT_DRAGON_BOAT); + randResult.incNum(); + return resResult(STATUS.SUCCESS, { + activityId, + todayPlayCnt: playerData.todayPlayCnt, + playCnt: playerData.playCnt, + curHero: randResult.getShowResult(), + goods + }); + } + + /** + * @description 购买次数 + * @param {{ activityId: number, id: number, count: number}} msg + * @param {BackendSession} session + * @memberof EntertainHandler + */ + async buyCnt(msg: { activityId: number, count: number }, session: BackendSession) { + const { activityId, count } = msg; + const roleId = session.get('roleId'); + const sid = session.get('sid'); + const serverId = session.get('serverId'); + + let playerData = await getPlayerEntertainData(activityId, serverId, roleId); + if (!playerData) return resResult(STATUS.ACTIVITY_MISSING); + + // 可购买次数 + if(playerData.buyCnt + count > playerData.maxBuyCnt) return resResult(STATUS.ACTIVITY_ENTERTAIN_BUY_COUNT_OVER); + if(playerData.todayPlayCnt < playerData.freeCnt) return resResult(STATUS.ACTIVITY_DRAGON_BOAT_CANNOT_BUY); + // 扣材料 + let costResult = await handleCost(roleId, sid, stringToConsumeParam(playerData.buyCost), ITEM_CHANGE_REASON.ACT_DRAGON_BOAT_BUY_COST); + if(!costResult) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH); + + // 保存数据 + let buildResult = await ActivityEntertainRecModel.buyCnt(serverId, activityId, playerData.roundIndex, roleId, count); + // // 更新数据 + playerData.updateBuyCnt(buildResult); + + return resResult(STATUS.SUCCESS, { + activityId, + maxBuyCnt: playerData.maxBuyCnt, + buyCnt: playerData.buyCnt + }); + } +} diff --git a/game-server/app/services/activity/activityService.ts b/game-server/app/services/activity/activityService.ts index bc21fb48b..ecdf37ff4 100644 --- a/game-server/app/services/activity/activityService.ts +++ b/game-server/app/services/activity/activityService.ts @@ -48,6 +48,7 @@ import { getMonthlyFundData } from './monthlyFundService'; import { getPlayerRebateDataShow } from './rebateService'; import { getWebviewDataShow } from './webviewService'; import { getPlayerDragonBoatDataShow } from './dragonBoatService'; +import { getPlayerEntertainDataShow } from './entertainService'; /** * 获取活动数据 @@ -274,6 +275,11 @@ export async function getActivity(serverId: number, roleId: string, uid: number, activityData = await getPlayerDragonBoatDataShow(activityId, serverId, roleId); break } + case ACTIVITY_TYPE.ENTERTAIN: + { + activityData = await getPlayerEntertainDataShow(activityId, serverId, roleId); + break + } default: { console.log('未知活动类型.........', activityType) break; diff --git a/game-server/app/services/activity/entertainService.ts b/game-server/app/services/activity/entertainService.ts new file mode 100644 index 000000000..a5f8b2b37 --- /dev/null +++ b/game-server/app/services/activity/entertainService.ts @@ -0,0 +1,38 @@ +import { ActivityEntertainRecModel } from "../../db/ActivityEntertainRec"; +import { EntertainData } from "../../domain/activityField/entertainField"; +import { getRoleCreateTime, getServerCreateTime } from "../redisService"; +import { getActivityById } from "./activityService"; + +/** + * 玩家活动数据 + * + * @param {number} serverId 区Id + * @param {number} activityId 活动Id + * @param {string} roleId 角色Id + * + */ +export async function getPlayerEntertainData(activityId: number, serverId: number, roleId: string) { + let activityData = await getActivityById(activityId); + let createTime = await getRoleCreateTime(roleId); + let serverTime = await getServerCreateTime(serverId); + let playerData = new EntertainData(activityData, createTime, serverTime); + let playerRecord = await ActivityEntertainRecModel.findData(serverId, activityId, playerData.roundIndex, roleId); + playerData.setPlayerRecords(playerRecord); + return playerData; +} + +/** + * 玩家活动数据 + * + * @param {number} serverId 区Id + * @param {number} activityId 活动Id + * @param {string} roleId 角色Id + * + */ +export async function getPlayerEntertainDataShow(activityId: number, serverId: number, roleId: string) { + let playerData = await getPlayerEntertainData(activityId, serverId, roleId); + if(playerData && playerData.canShow && playerData.canShow()) { + return playerData.getShowResult(); + } + return null +} \ No newline at end of file diff --git a/shared/consts/constModules/activityConst.ts b/shared/consts/constModules/activityConst.ts index b0551c1c8..bfee7912b 100644 --- a/shared/consts/constModules/activityConst.ts +++ b/shared/consts/constModules/activityConst.ts @@ -68,6 +68,7 @@ export enum ACTIVITY_TYPE { REBATE = 53, // 返利 WEBVIEW = 54, // 平台活动页面 DRAGON_BOAT = 55, // 龙舟 + ENTERTAIN = 56, // 宴请百家 } /** diff --git a/shared/consts/statusCode.ts b/shared/consts/statusCode.ts index 6d14ef75f..aa3a24e90 100644 --- a/shared/consts/statusCode.ts +++ b/shared/consts/statusCode.ts @@ -683,6 +683,8 @@ export const STATUS = { ACTIVITY_DRAGON_BOAT_ROUTE_HAS_PASS: { code: 50071, simStr: '该节点已经挑战过了' }, ACTIVITY_DRAGON_BOAT_BUY_COUNT_OVER: { code: 50072, simStr: '购买挑战次数不足' }, ACTIVITY_DRAGON_BOAT_CANNOT_BUY: { code: 50073, simStr: '不可在免费次数未用完的时候购买次数' }, + ACTIVITY_ENTERTAIN_NO_NUM: { code: 50074, simStr: '宴请武将次数已满' }, + ACTIVITY_ENTERTAIN_BUY_COUNT_OVER: { code: 50075, simStr: '购买次数不足' }, // GM后台相关状态 60000 - 69999 GM_ERR_PASSWORD: { code: 60001, simStr: '账号或密码错误' }, diff --git a/shared/db/ActivityEntertainRec.ts b/shared/db/ActivityEntertainRec.ts new file mode 100644 index 000000000..0be2bcbc1 --- /dev/null +++ b/shared/db/ActivityEntertainRec.ts @@ -0,0 +1,64 @@ +import BaseModel from './BaseModel'; +import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoose'; + +/** + * 宴请百家 +*/ +class EntertainRecord { + @prop({ required: true }) + todayIndex: number; // 第几天 + + @prop({ required: true }) + id: number; // 唯一id + + @prop({ required: true }) + hid: number; // 冗余,武将id + + @prop({ required: true }) + reward: string; // 冗余,奖励 + + @prop({ required: true }) + time: Date; // 时间 +} + +@index({ roleId: 1, activityId: 1 }) + +export default class Activity_Entertain_Rec extends BaseModel { + @prop({ required: true }) + serverId: number; // 服Id + + @prop({ required: true }) + activityId: number; // 活动Id + + @prop({ required: true }) + roundIndex: number; // 第几轮 + + @prop({ required: true }) + roleId: string; // 用户Id + + @prop({ required: true }) + buyCnt: number; // 购买次数 + + @prop({ required: true, type: EntertainRecord, _id: false }) + record: EntertainRecord[]; // 宴请记录 + + public static async findData(serverId: number, activityId: number, roundIndex: number, roleId: string) { + let result: ActivityEntertainRecModelType = await ActivityEntertainRecModel.findOne({ serverId, roleId, activityId, roundIndex }).lean(); + return result; + } + + public static async record(serverId: number, activityId: number, roundIndex: number, roleId: string, record: EntertainRecord) { + let result: ActivityEntertainRecModelType = await ActivityEntertainRecModel.findOneAndUpdate({ serverId, roleId, activityId, roundIndex }, { $push: { record }, $setOnInsert: { buyCnt: 0 } }, { new: true, upsert: true }).lean(); + return result; + } + + public static async buyCnt(serverId: number, activityId: number, roundIndex: number, roleId: string, count: number) { + let result: ActivityEntertainRecModelType = await ActivityEntertainRecModel.findOneAndUpdate({ serverId, roleId, activityId, roundIndex }, { $inc: { buyCnt: count }, $setOnInsert: { record: [] } }, { new: true, upsert: true }).lean(); + return result; + } +} + +export const ActivityEntertainRecModel = getModelForClass(Activity_Entertain_Rec); + +export interface ActivityEntertainRecModelType extends Pick, keyof Activity_Entertain_Rec> { } +export type ActivityEntertainRecModelTypeParam = Partial; // 将所有字段变成可选项 \ No newline at end of file diff --git a/shared/domain/activityField/entertainField.ts b/shared/domain/activityField/entertainField.ts new file mode 100644 index 000000000..af6bf2b66 --- /dev/null +++ b/shared/domain/activityField/entertainField.ts @@ -0,0 +1,175 @@ +// 节日活动 - 划龙舟 +import { ActivityModelType } from '../../db/Activity'; +import { ActivityEntertainRecModelType } from '../../db/ActivityEntertainRec'; +// import { ActivityEntertainRecModelType } from '../../db/ActivityEntertainRec'; +import { ActivityBase } from './activityField'; + +// 后台格式 +interface HeroNumInDb { + id: number;// 第几次拜访 + reward: string; // 奖励,type&id&count +} + +interface HeroInDb { + id: number // 编号 + index: number; // 位置 + name: string; // 名字 + imageName: string; // 图片 + condition: string; // 解锁条件,type1:宴请x名武将,type2:宴请某个人 + hid: number; // 武将id + num: HeroNumInDb[]; // 第几次的奖励是什么 +} + +interface EntertainDataInDb { + buyCost: string; // 购买一次宴请次数的消耗,type&id&count + dailyBuyCnt: number; // 每天可以购买的次数 + freeCnt: number; // 每天可以免费划船的次数 + heroes: HeroInDb[]; // 可以宴请的武将的次数 +} + +class HeroData { + id: number; // 编号 + index: number; // 位置 + name: string; // 名字 + imageName: string; // 图片 + condition: string; // 解锁条件,type1:宴请x名武将;type2:宴请某个人 + conditionArr: {type: number, param: number}[] = []; + hid: number; // 武将id + numArr: HeroNumInDb[] = []; // 第几次能获得什么奖励 + + maxNum: number = 0; // 最大次数 + num: number = 0; // 玩家当前第几次拜访 + reward: string = '&'; // 玩家下一次可以获得的奖励 + + constructor(data: HeroInDb) { + this.id = data.id; + this.index = data.index; + this.name = data.name; + this.imageName = data.imageName; + this.condition = data.condition; + let arr = data.condition.split('|'); + for(let str of arr) { + let obj = str?.split('&')||[]; + if(obj[0]) this.conditionArr.push({ type: parseInt(obj[0]), param: parseInt(obj[1]) }); + } + this.hid = data.hid; + this.numArr = data.num; + this.maxNum = data.num.length; + this.calReward(); + } + + public incNum() { + this.num++; + this.calReward(); + } + + public calReward() { + console.log('####', this.numArr, this.num) + let nextReward = this.numArr.find(cur => cur.id == this.num + 1); + this.reward = nextReward?.reward||'&'; + } + + public getShowResult() { + let { id, index, name, imageName, condition, hid, maxNum, num, reward } = this; + return { id, index, name, imageName, condition, hid, maxNum, num, reward } + } +} + +export class EntertainData extends ActivityBase { + buyCost: string; // 购买一次划船次数的消耗,type&id&count + dailyBuyCnt: number; // 每天可以购买的次数 + freeCnt: number; // 每天可以免费划船的次数 + heroes: HeroData[] = []; // 宴请武将 + + maxBuyCnt: number = 0; // 累积到现在可以购买的次数 + buyCnt: number = 0; // 累积到现在已经购买了的次数 + todayPlayCnt: number = 0; // 今天玩的次数 + playCnt: number = 0; // 总计玩的次数 + + invitedHeroNum: number = 0; // 宴请了的武将的数量 + invitedHids: number[] = []; // 宴请了的武将的位置 + + constructor(activityData: ActivityModelType, createTime: number, serverTime: number) { + super(activityData, createTime, serverTime) + this.initData(activityData.data) + } + + public initData(data: string): void { + let dataObj: EntertainDataInDb = JSON.parse(data); + if (!dataObj) return; + + this.buyCost = dataObj.buyCost || '&'; + this.dailyBuyCnt = dataObj.dailyBuyCnt || 0; + this.freeCnt = dataObj.freeCnt || 0; + this.maxBuyCnt = this.todayIndex * this.dailyBuyCnt; + for (let data of (dataObj.heroes || [])) { + this.heroes.push(new HeroData(data)); + } + } + + public findHeroById(id: number) { + let hero = this.heroes.find(cur => cur.id == id); + return hero; + } + + public setPlayerRecords(playerData: ActivityEntertainRecModelType) { + if (!playerData) return; + this.buyCnt = playerData.buyCnt || 0; + this.todayPlayCnt = 0; + this.playCnt = 0; + let recByDay = new Map(); + for (let { id, hid, todayIndex } of (playerData.record || [])) { + if (todayIndex == this.todayIndex) { + this.todayPlayCnt++; + this.playCnt++; + } else { + let n = recByDay.get(todayIndex) || 0; + if (n >= this.freeCnt) { // 不包含之前免费玩的次数 + this.playCnt++; + } + recByDay.set(todayIndex, n + 1); + } + + let hero = this.findHeroById(id); + hero.incNum(); + if(this.invitedHids.indexOf(hid) == -1) { + this.invitedHids.push(hid); + this.invitedHeroNum++; + } + } + } + + public updateBuyCnt(playerData: ActivityEntertainRecModelType) { + if (!playerData) return; + this.buyCnt = playerData.buyCnt || 0; + this.todayPlayCnt = 0; + this.playCnt = 0; + let recByDay = new Map(); + for (let { todayIndex } of (playerData.record || [])) { + if (todayIndex == this.todayIndex) { + this.todayPlayCnt++; + this.playCnt++; + } else { + let n = recByDay.get(todayIndex) || 0; + if (n >= this.freeCnt) { // 不包含之前免费玩的次数 + this.playCnt++; + } + recByDay.set(todayIndex, n + 1); + } + } + } + + public getShowResult() { + return { + ...this.getBaseKeys(), + buyCost: this.buyCost, + dailyBuyCnt: this.dailyBuyCnt, + freeCnt: this.freeCnt, + maxBuyCnt: this.maxBuyCnt, + buyCnt: this.buyCnt, + todayPlayCnt: this.todayPlayCnt, + playCnt: this.playCnt, + heroes: this.heroes.map(route => route.getShowResult()), + } + } +} \ No newline at end of file diff --git a/shared/resource/jsons/dic_zyz_activityType.json b/shared/resource/jsons/dic_zyz_activityType.json index b8df53436..1c97d0033 100644 --- a/shared/resource/jsons/dic_zyz_activityType.json +++ b/shared/resource/jsons/dic_zyz_activityType.json @@ -304,5 +304,11 @@ "activityType": 55, "name": "DRAGON_BOAT", "string": "龙舟" + }, + { + "id": 56, + "activityType": 56, + "name": "ENTERTAIN", + "string": "宴请百家" } ] \ No newline at end of file