diff --git a/game-server/app/servers/activity/handler/firstGiftHandler.ts b/game-server/app/servers/activity/handler/firstGiftHandler.ts index 6987e4c94..6fe174975 100644 --- a/game-server/app/servers/activity/handler/firstGiftHandler.ts +++ b/game-server/app/servers/activity/handler/firstGiftHandler.ts @@ -1,7 +1,7 @@ import { Application, BackendSession, HandlerService, } from 'pinus'; import { resResult } from '../../../pubUtils/util'; import { FIRST_GIFT_STATE, ITEM_CHANGE_REASON, STATUS } from '../../../consts'; -import { getPlayerFirstGiftData } from '../../../services/activity/firstGiftService'; +import { getPlayerFirstGiftData, getPlayerFirstGiftDataShow } from '../../../services/activity/firstGiftService'; import { RoleModel } from '../../../db/Role'; import { addReward, stringToRewardParam } from '../../../services/activity/giftPackageService'; import { ActivityFirstGiftModel } from '../../../db/ActivityFirstGift'; @@ -30,7 +30,7 @@ export class FirstGiftHandler { const roleId = session.get('roleId'); const serverId = session.get('serverId'); - let playerData = await getPlayerFirstGiftData(activityId, serverId, roleId); + let playerData = await getPlayerFirstGiftDataShow(activityId, serverId, roleId); if (!playerData) { return resResult(STATUS.ACTIVITY_MISSING); } diff --git a/game-server/app/servers/activity/handler/luckyTurntableHandler.ts b/game-server/app/servers/activity/handler/luckyTurntableHandler.ts new file mode 100644 index 000000000..497b85fb6 --- /dev/null +++ b/game-server/app/servers/activity/handler/luckyTurntableHandler.ts @@ -0,0 +1,98 @@ +import { Application, BackendSession, HandlerService, } from 'pinus'; +import { resResult } from '../../../pubUtils/util'; +import { ITEM_CHANGE_REASON, STATUS } from '../../../consts'; +import { getPlayerLuckyTurntableDataShow, getPlayerLuckyTurntableData } from '../../../services/activity/luckyTurntableService'; +import { addItems, combineItems, handleCost } from '../../../services/rewardService'; +import { ActivityTurntableModel } from '../../../db/ActivityTurntableRec'; +import { pick } from 'underscore'; +import { addReward, stringToRewardInter, stringToRewardParam } from '../../../services/activity/giftPackageService'; + + +export default function (app: Application) { + new HandlerService(app, {}); + return new LuckyTurntableHandler(app); +} + +export class LuckyTurntableHandler { + constructor(private app: Application) { + } + + /************************幸运转盘****************************/ + /** + * @description 幸运转盘活动 + * @param {{ activityId: number, }} msg + * @param {BackendSession} session + * @memberof LuckyTurntableHandler + */ + async getTurntableData(msg: { activityId: number }, session: BackendSession) { + const { activityId } = msg; + const roleId = session.get('roleId'); + const serverId = session.get('serverId'); + + let playerData = await getPlayerLuckyTurntableDataShow(activityId, serverId, roleId); + if (!playerData) { + return resResult(STATUS.ACTIVITY_MISSING); + } + + return resResult(STATUS.SUCCESS, { playerData }); + } + + /** + * @description 转转盘 + * @param {{ activityId: number, }} msg + * @param {BackendSession} session + * @memberof LuckyTurntableHandler + */ + async pull(msg: { activityId: number, count: number }, session: BackendSession) { + const { activityId, count } = msg; + const roleId = session.get('roleId'); + const roleName = session.get('roleName'); + const sid = session.get('sid'); + const serverId = session.get('serverId'); + + let playerData = await getPlayerLuckyTurntableData(activityId, serverId, roleId); + if (!playerData) { + return resResult(STATUS.ACTIVITY_MISSING); + } + + let cost = playerData.getCost(count); + let costResult = await handleCost(roleId, sid, cost, ITEM_CHANGE_REASON.ACT_TURNTABLE_PULL); + if(!costResult) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH); + + let { result, records, goodResult } = playerData.pull(roleName, count); + await ActivityTurntableModel.updateData(serverId, activityId, roleId, pick(playerData, ['todayCount', 'count', 'records', 'greatRewardCount'])); + let goods = await addItems(roleId, roleName, sid, goodResult, ITEM_CHANGE_REASON.ACT_TURNTABLE_PULL); + + return resResult(STATUS.SUCCESS, { result, records, goods: combineItems(goods) }); + } + + + /** + * @description 领宝箱 + * @param {{ activityId: number, boxCount: number }} msg + * @param {BackendSession} session + * @memberof LuckyTurntableHandler + */ + async receiveBox(msg: { activityId: number, boxCount: number }, session: BackendSession) { + const { activityId, boxCount } = msg; + const roleId = session.get('roleId'); + const roleName = session.get('roleName'); + const sid = session.get('sid'); + const serverId = session.get('serverId'); + + let playerData = await getPlayerLuckyTurntableData(activityId, serverId, roleId); + if (!playerData) { + return resResult(STATUS.ACTIVITY_MISSING); + } + + let canReceive = playerData.canReceive(boxCount); + if(!canReceive) return resResult(STATUS.BOX_CAN_NOT_RECEIVE); + + let box = playerData.findBox(boxCount); + let rewardArray = stringToRewardParam(box.reward) + let result = await addReward(roleId, roleName, sid, serverId, rewardArray, ITEM_CHANGE_REASON.BUY_LIMIT_PACKAGE); + await ActivityTurntableModel.receiveBox(serverId, activityId, roleId, boxCount); + + return resResult(STATUS.SUCCESS, { ...result, box: {...box, isReceived: true} }); + } +} diff --git a/game-server/app/services/activity/activityService.ts b/game-server/app/services/activity/activityService.ts index ddd1a3059..85c200c1e 100644 --- a/game-server/app/services/activity/activityService.ts +++ b/game-server/app/services/activity/activityService.ts @@ -1,5 +1,5 @@ import { ACTIVITY_TYPE, STATUS } from '../../consts'; -import { getPlayerFirstGiftData } from './firstGiftService'; +import { getPlayerFirstGiftDataShow } from './firstGiftService'; import { getPlayerSignInData, } from './signInService'; import { getPlayerGrowthFundData, } from './growthFundService'; import { getPlayerLimitPackageData, } from './limitPackageService'; @@ -78,7 +78,7 @@ export async function getActivity(serverId: number, roleId: string, activityId: } case ACTIVITY_TYPE.FIRST_GIFT://首冲礼包活动 14 { - activityData = await getPlayerFirstGiftData(activityId, serverId, roleId); + activityData = await getPlayerFirstGiftDataShow(activityId, serverId, roleId); break; } case ACTIVITY_TYPE.NEW_PLAYER_LIMIT_PACKAGE://新手限定RMB购买礼包 15 diff --git a/game-server/app/services/activity/firstGiftService.ts b/game-server/app/services/activity/firstGiftService.ts index fa36dd6f8..5e251ce13 100644 --- a/game-server/app/services/activity/firstGiftService.ts +++ b/game-server/app/services/activity/firstGiftService.ts @@ -52,6 +52,21 @@ export async function getPlayerFirstGiftData(activityId: number, serverId: numbe return playerData; } +/** + * 获取可显示的数据 + * @param activityId + * @param serverId + * @param roleId + * @returns + */ +export async function getPlayerFirstGiftDataShow(activityId: number, serverId: number, roleId: string) { + let playerData = await getPlayerFirstGiftData(activityId, serverId, roleId); + if(playerData && playerData.canShow && playerData.canShow()) { + return playerData.getShowResult(); + } + return null +} + /** * 记录玩家首充记录 * @param {RoleType} role 玩家数据 diff --git a/game-server/app/services/activity/luckyTurntableService.ts b/game-server/app/services/activity/luckyTurntableService.ts new file mode 100644 index 000000000..fba5f8c8d --- /dev/null +++ b/game-server/app/services/activity/luckyTurntableService.ts @@ -0,0 +1,34 @@ +import { ActivityTurntableModel } from "../../db/ActivityTurntableRec"; +import { RoleModel } from "../../db/Role"; +import { LuckyTurntableData } from "../../domain/activityField/luckyTurntableField"; +import { shouldRefresh } from "../../pubUtils/util"; +import { getActivityById } from "./activityService"; + +/** + * 玩家活动数据 + * + * @param {number} serverId 区Id + * @param {number} activityId 活动Id + * @param {string} roleId 角色Id + * + */ + export async function getPlayerLuckyTurntableData(activityId: number, serverId: number, roleId: string) { + let activityData = await getActivityById(activityId); + let playerRecord = await ActivityTurntableModel.findByActivityId(serverId, activityId, roleId); + if(playerRecord && shouldRefresh(playerRecord.refTodayCount, new Date())) { + playerRecord = await ActivityTurntableModel.refreshTodayCount(serverId, activityId, roleId); + } + + let { createTime } = await RoleModel.findByRoleId(roleId); + let playerData = new LuckyTurntableData(activityData, createTime); + playerData.setPlayerRecords(playerRecord); + return playerData; +} + +export async function getPlayerLuckyTurntableDataShow(activityId: number, serverId: number, roleId: string) { + let playerData = await getPlayerLuckyTurntableData(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 d29a38411..cf428cc38 100644 --- a/shared/consts/constModules/activityConst.ts +++ b/shared/consts/constModules/activityConst.ts @@ -52,6 +52,7 @@ export enum ACTIVITY_TYPE { NEW_HERO_GIFTS = 37, //新将好礼(很多红包,用积分兑换一遍结束) NEW_HERO_GK = 38, //新将演绎 (配置N个武将,每个武将有X个关卡;活动期间,*天(时间自定义)开启每个武将对应的一个关卡,只有第一次通关会获得奖励) NEW_HERO_GACHA = 39, //新将擢迁(新武将抽卡) + LUCKY_TURNTABLE = 40, // 幸运转盘 } /** diff --git a/shared/consts/constModules/sysConst.ts b/shared/consts/constModules/sysConst.ts index c279d7a21..02543cce6 100644 --- a/shared/consts/constModules/sysConst.ts +++ b/shared/consts/constModules/sysConst.ts @@ -960,6 +960,7 @@ export enum ITEM_CHANGE_REASON { EQUIP_STARUP = 138, // 装备升星 COMPOSE_STONE = 139, // 合成地玉石 REBIRTH = 140, // 武将重生 + ACT_TURNTABLE_PULL = 141, // 转盘 } export enum TA_EVENT { diff --git a/shared/consts/statusCode.ts b/shared/consts/statusCode.ts index 6d59278a6..d093e49ab 100644 --- a/shared/consts/statusCode.ts +++ b/shared/consts/statusCode.ts @@ -254,6 +254,7 @@ export const STATUS = { AP_BUY_TIMES_LACK: { code: 30007, simStr: '购买次数不足' }, CONSUME_TYPE_ERR: { code: 30008, simStr: '道具类型错误' }, DB_DATA_NOT_FOUND: { code: 30009, simStr: '数据库数据未找到' }, + BOX_CAN_NOT_RECEIVE: { code: 30010, simStr: '宝箱不可领取' }, // 武将养成通用 30100 - 30199 diff --git a/shared/db/ActivityTurntableRec.ts b/shared/db/ActivityTurntableRec.ts new file mode 100644 index 000000000..481c718f8 --- /dev/null +++ b/shared/db/ActivityTurntableRec.ts @@ -0,0 +1,73 @@ +import BaseModel from './BaseModel'; +import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoose'; + +export class TurntableRecord { + @prop({ required: true }) + roleName: string; + + @prop({ required: true }) + gid: number; + + @prop({ required: true }) + count: number; +} + +/** + * 幸运转盘活动 +*/ +@index({ roleId: 1 }) + +export default class Activity_Turntable_Rec extends BaseModel { + + @prop({ required: true }) + activityId: number; // 活动id + + @prop({ required: true }) + serverId: number; // 区id + + @prop({ required: true }) + roleId: string; // 用户id + + @prop({ required: true }) + count: number; // 抽卡次数 + + @prop({ required: true }) + greatRewardCount: number; // 中头奖次数 + + @prop({ required: true }) + todayCount: number; // 本日抽卡 + + @prop({ required: true }) + refTodayCount: Date; // 本日抽卡刷新时间 + + @prop({ required: true, type: TurntableRecord }) + records: TurntableRecord[]; // 本日抽卡刷新时间 + + @prop({ required: true, type: Number }) + box: number[]; // 宝箱已领取次数 + + public static async findByActivityId(serverId: number, activityId: number, roleId: string) { + let rec: ActivityTurntableModelType = await ActivityTurntableModel.findOne({ roleId, activityId, serverId }).lean(); + return rec; + } + + public static async updateData(serverId: number, activityId: number, roleId: string, update: ActivityTurntableModelTypeParam) { + let rec: ActivityTurntableModelType = await ActivityTurntableModel.findOneAndUpdate({ roleId, activityId, serverId }, { $set: update }, { upsert: true, new: true }).lean(); + return rec; + } + + public static async refreshTodayCount(serverId: number, activityId: number, roleId: string) { + let rec: ActivityTurntableModelType = await ActivityTurntableModel.findOneAndUpdate({ roleId, activityId, serverId }, {$set: { todayCount: 0, refTodayCount: new Date() }}).lean(); + return rec; + } + + public static async receiveBox(serverId: number, activityId: number, roleId: string, boxCount: number) { + let rec: ActivityTurntableModelType = await ActivityTurntableModel.findOneAndUpdate({ roleId, activityId, serverId }, {$addToSet: { box: boxCount }}, { new: true }).lean(); + return rec; + } +} + +export const ActivityTurntableModel = getModelForClass(Activity_Turntable_Rec); + +export interface ActivityTurntableModelType extends Pick, keyof Activity_Turntable_Rec> { } +export type ActivityTurntableModelTypeParam = Partial; // 将所有字段变成可选项 \ No newline at end of file diff --git a/shared/domain/activityField/firstGiftField.ts b/shared/domain/activityField/firstGiftField.ts index d37701864..c9fc5c315 100644 --- a/shared/domain/activityField/firstGiftField.ts +++ b/shared/domain/activityField/firstGiftField.ts @@ -1,4 +1,5 @@ import moment = require('moment'); +import { pick } from 'underscore'; import { FIRST_GIFT_STATE } from '../../consts'; import { ActivityModelType } from '../../db/Activity'; import { ActivityFirstGiftModelType } from '../../db/ActivityFirstGift'; @@ -136,6 +137,10 @@ export class FirstGiftData extends ActivityBase { } } + public getShowResult() { + return pick(this, 'list'); + } + constructor(activityData: ActivityModelType, createTime: number) { super(activityData, createTime) this.initData(activityData.data) diff --git a/shared/domain/activityField/luckyTurntableField.ts b/shared/domain/activityField/luckyTurntableField.ts new file mode 100644 index 000000000..1aed65811 --- /dev/null +++ b/shared/domain/activityField/luckyTurntableField.ts @@ -0,0 +1,183 @@ +import { pick } from 'underscore'; +import { ActivityModelType } from '../../db/Activity'; +import { ActivityTurntableModelType, TurntableRecord } from '../../db/ActivityTurntableRec'; +import { RewardInter } from '../../pubUtils/interface'; +import { getRandEelmWithWeight, parseGoodStr } from '../../pubUtils/util'; +import { ActivityBase } from './activityField'; + +/************** 在数据库中的格式 ***********/ + +interface TurntablePoolInDb { + id: number; // 奖池id + gid: number; // 物品id + count: number; // 道具数量 + weight: number; // 权重 +} + +interface TurntableFloorInDb { // 保底,每sum次必出count个头奖,头奖:权重最低的一项 + sum: number; + count: number; +} + +interface TurntableBoxInDb { + count: number; // 次数 + reward: string; // 奖励 type&id&count +} + +interface TurntableInDb { + cost: string; // 抽1次的消耗,id&count + freeCount: number; // 免费次数 + pool: TurntablePoolInDb[]; // 奖池 + floor: TurntableFloorInDb; // 保底 + box: TurntableBoxInDb[]; +} + +/************** 给客户端返回的数据 ***********/ +// 转盘数据 +export class LuckyTurntablePool { + id: number; // 奖池项唯一id + gid: number; // 物品id + count: number; // 物品数量 + weight: number; // 权重-最好填写整数,会自动做加和,如weight为1,2,则他们的概率为1/3和2/3 + + constructor(data: TurntablePoolInDb) { + this.id = data.id; + this.gid = data.gid; + this.count = data.count; + this.weight = data.weight; + } + + public getShowResult() { + return pick(this, ['id', 'gid', 'count']); + } +} + +export class LuckyTurntableBox { + count: number; // 次数 + reward: string; // 奖励 + + isReceived: boolean = false; // 是否已领取 + + constructor(data: TurntableBoxInDb) { + this.count = data.count; + this.reward = data.reward; + } + + public setReceived(box: number[] = []) { + if(box.indexOf(this.count) != -1) { + this.isReceived = true; + } + } +} + +// 新云转盘活动数据 +export class LuckyTurntableData extends ActivityBase { + cost: string; // 抽1次的消耗 + freeCount: number; // 免费次数 + pool: LuckyTurntablePool[] = []; + greateReward: LuckyTurntablePool; // 头奖,weight最小的那一个 + box: LuckyTurntableBox[] = []; + floor: { // 保底 + sum: number; + count: number; + }; + + todayCount: number = 0; // 今天抽了几次 + count: number = 0; // 一共抽了几次 + records: TurntableRecord[] = []; // 奖励记录 + greatRewardCount: number = 0; // 中头奖次数 + + public getCost(count: number) { + if(this.todayCount < this.freeCount) { + count -= this.freeCount - this.todayCount; + } + let cost = parseGoodStr(this.cost); + return cost.map(cur => ({...cur, count: cur.count * count})); + } + + public pull(roleName: string, count: number) { + let records: TurntableRecord[] = []; + let result: number[] = []; + let goodResult: RewardInter[] = []; + for(let i = 0; i < count; i++) { + let pool = this.pool; + let numNow = this.count % this.floor.sum; + let getNumNow = this.greatRewardCount % this.floor.count; + if(getNumNow >= this.floor.count) { // 不再抽到 + pool = this.pool.filter(cur => cur.id != this.greateReward.id); + if(pool.length <= 0) pool = this.pool; + } else if (this.floor.sum - numNow - getNumNow <= this.floor.count) { // 到最后几个了,直接给保底 + pool = [this.greateReward]; + } + let { dic: randResult } = getRandEelmWithWeight(this.pool); + if(randResult) { + this.count++; + this.todayCount++; + let record = { roleName, gid: randResult.gid, count: randResult.count }; + records.push(record); + this.records.push(record); + result.push(randResult.id); + goodResult.push({ id: randResult.gid, count: randResult.count }); + if(randResult.id == this.greateReward.id) { + this.greatRewardCount++ + } + } + } + return { result, records, goodResult }; + } + + // 宝箱是否可以领取 + public canReceive(boxCount: number) { + if(this.count < boxCount) return false; + let box = this.box.find(cur => cur.count == boxCount); + if(!box) return false + if(box.isReceived) return false; + return true; + } + + public findBox(boxCount: number) { + let box = this.box.find(cur => cur.count == boxCount); + return box; + } + + public setPlayerRecords(playerData: ActivityTurntableModelType) { + if(!playerData) return null + let { todayCount, count, records, box, greatRewardCount } = playerData; + this.todayCount = todayCount; + this.count = count; + this.records = records; + for(let boxData of this.box) { + boxData.setReceived(box); + } + this.greatRewardCount = greatRewardCount; + } + + public initData(data: string) { + let dataObj: TurntableInDb = JSON.parse(data); + this.cost = dataObj.cost; + this.freeCount = dataObj.freeCount; + for(let pool of dataObj.pool) { + let poolObj = new LuckyTurntablePool(pool); + this.pool.push(poolObj); + if(!this.greateReward || this.greateReward.weight > poolObj.weight) { + this.greateReward = poolObj; + } + } + for(let box of dataObj.box) { + this.box.push(new LuckyTurntableBox(box)); + } + this.floor = dataObj.floor; + } + + public getShowResult() { + return { + ...pick(this, ['cost', 'freeCount', 'todayCount', 'count', 'box', 'records']), + pool: this.pool.map(pool => pool.getShowResult()) + } + } + + constructor(activityData: ActivityModelType, createTime: number) { + super(activityData, createTime) + this.initData(activityData.data) + } +} \ No newline at end of file