✨ feat(gift): 修改宝箱保底机制
This commit is contained in:
@@ -13,7 +13,7 @@ import { combineItems } from '../role/util';
|
||||
import { recordGuildFund } from './timeLimitRankService';
|
||||
import { filterGoods, isGoodsHidden, isHeroHidden } from '../dataService';
|
||||
import { DicGiftPackagePlan } from '../../pubUtils/dictionary/DicGiftPackagePlan';
|
||||
import { GiftPackageFloorModel } from '../../db/GiftPackageFloor';
|
||||
import { Floor, GiftPackageFloorModel } from '../../db/GiftPackageFloor';
|
||||
|
||||
|
||||
|
||||
@@ -152,38 +152,114 @@ export function getSelectedReward(plans: DicGiftPackagePlan[], selected: Array<n
|
||||
return selectedReward;
|
||||
}
|
||||
|
||||
// 根据是否触发保底,更新抽取次数
|
||||
function updateFloorCount(floors: Floor[], maxQaulity: number, isTrigger: boolean) {
|
||||
for (let floor of floors) {
|
||||
if (!isTrigger) {
|
||||
floor.count++;
|
||||
} else {
|
||||
if (floor.quality <= maxQaulity) {
|
||||
floor.count = 0;
|
||||
} else {
|
||||
floor.count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return floors;
|
||||
}
|
||||
|
||||
// 将随机池子根据保底 id 进行分组
|
||||
function groupByFloorId(plans: DicGiftPackagePlan[]): { [key: number]: DicGiftPackagePlan[] } {
|
||||
return plans.reduce((acc, cur) => {
|
||||
if (cur.floorId !== 0) {
|
||||
if (!acc[cur.floorId]) acc[cur.floorId] = [];
|
||||
acc[cur.floorId].push(cur);
|
||||
}
|
||||
return acc;
|
||||
}, {} as { [key: number]: DicGiftPackagePlan[] });
|
||||
}
|
||||
|
||||
// 初始化用户某个礼包的保底数据
|
||||
async function initFloorData(floors: Floor[], floorPlans: { [key: number]: DicGiftPackagePlan[] }) {
|
||||
for (let floorId in floorPlans) {
|
||||
let hasFloorFlag = false;
|
||||
for (let floor of floors) {
|
||||
if (floor.id === parseInt(floorId)) {
|
||||
hasFloorFlag = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!hasFloorFlag) {
|
||||
floors.push({ id: parseInt(floorId), count: 0, quality: gameData.giftPackageFloor.get(parseInt(floorId)).quality });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function randomSelectedData(pool: DicGiftPackagePlan[], roleId: string, giftPackageId: number, count: number) {
|
||||
let rewards: RewardParam[] = [];
|
||||
let floorPlan = pool.find(cur => cur.floorCount > 0);
|
||||
let dropHistory = floorPlan? await GiftPackageFloorModel.findByPlanId(roleId, giftPackageId, floorPlan?.id): { getNum: 0, allNum: 0, getSum: 0, allSum: 0 };
|
||||
let { getNum = 0, allNum = 0, getSum = 0, allSum = 0 } = dropHistory;
|
||||
const floorPlans = groupByFloorId(pool);
|
||||
let dropHistory = (floorPlans && Object.keys(floorPlans).length > 0) ? await GiftPackageFloorModel.findByGiftPackageId(roleId, giftPackageId): { floors: [], getSum: 0, allSum: 0 };
|
||||
let { floors = [], getSum = 0, allSum = 0 } = dropHistory;
|
||||
|
||||
for(let i = 0; i < count; i++) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
let randResult = getRandEelmWithWeight(pool)?.dic;
|
||||
if(!randResult) continue;
|
||||
if(!floorPlan) {
|
||||
if (!randResult) continue;
|
||||
// 如果没有保底池子,直接返回
|
||||
if (!floorPlans || Object.keys(floorPlans).length === 0) {
|
||||
rewards.push({ type: randResult.contentType, id: randResult.content, count: randResult.count });
|
||||
continue;
|
||||
}
|
||||
|
||||
let flag = randResult.id == floorPlan.id; // 是否获得
|
||||
if(allNum - getNum >= floorPlan.floorFrequency - floorPlan.floorCount) {
|
||||
initFloorData(floors, floorPlans);
|
||||
|
||||
// 检查随机结果的 id 是否在 floorPlans 中,是的话抽中保底
|
||||
let flag = false;
|
||||
for (let floorId in floorPlans) {
|
||||
if (floorPlans[floorId].findIndex(cur => cur.id === randResult.id) !== -1) {
|
||||
flag = true;
|
||||
break;
|
||||
}
|
||||
if(flag) {
|
||||
getNum ++; getSum++;
|
||||
rewards.push({ type: floorPlan.contentType, id: floorPlan.content, count: floorPlan.count });
|
||||
} else {
|
||||
}
|
||||
|
||||
// 检查是否达到了保底次数,并且记录最大品质的 floorId
|
||||
let maxQaulityFloorId = 0; // 最大品质的 floorId
|
||||
let maxQaulity = 0;
|
||||
if (!flag) {
|
||||
for (let floor of floors) {
|
||||
if (floor.count + 1 >= gameData.giftPackageFloor.get(floor.id).times) {
|
||||
flag = true;
|
||||
if (gameData.giftPackageFloor.get(floor.id).quality > maxQaulity) {
|
||||
maxQaulityFloorId = floor.id;
|
||||
maxQaulity = gameData.giftPackageFloor.get(floor.id).quality;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新抽取次数,确定要返回的物品
|
||||
if (flag && maxQaulityFloorId == 0) { // 抽到了保底物品
|
||||
const quality = gameData.giftPackageFloor.get(randResult.floorId).quality;
|
||||
rewards.push({ type: randResult.contentType, id: randResult.content, count: randResult.count });
|
||||
updateFloorCount(floors, quality, true);
|
||||
getSum++;
|
||||
console.log('got floorId: ', randResult.floorId, quality);
|
||||
} else if (flag && maxQaulityFloorId > 0) { // 次数保底
|
||||
let floorResult = getRandEelmWithWeight(floorPlans[maxQaulityFloorId])?.dic;
|
||||
if (!floorResult) continue;
|
||||
rewards.push({ type: floorResult.contentType, id: floorResult.content, count: floorResult.count });
|
||||
updateFloorCount(floors, maxQaulity, true);
|
||||
getSum++;
|
||||
console.log('got floorId: ', maxQaulityFloorId, maxQaulity);
|
||||
} else { // 未达到保底次数
|
||||
rewards.push({ type: randResult.contentType, id: randResult.content, count: randResult.count });
|
||||
updateFloorCount(floors, 0, false);
|
||||
console.log('got floorId: ', randResult.floorId, 0);
|
||||
}
|
||||
allNum ++; allSum ++;
|
||||
if(getNum >= floorPlan.floorCount) {
|
||||
allNum = 0; getNum = 0;
|
||||
allSum++;
|
||||
}
|
||||
}
|
||||
if(floorPlan) {
|
||||
await GiftPackageFloorModel.updateByPlanId(roleId, giftPackageId, floorPlan.id, {
|
||||
getNum, allNum, getSum, allSum
|
||||
if(floorPlans && Object.keys(floorPlans).length > 0) {
|
||||
await GiftPackageFloorModel.updateByGiftPackageId(roleId, giftPackageId, {
|
||||
floors, getSum, allSum
|
||||
});
|
||||
}
|
||||
return rewards
|
||||
|
||||
47
game-server/test/gift.test.ts
Normal file
47
game-server/test/gift.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'mocha';
|
||||
import { PinusWSClient } from 'pinus-robot-plugin';
|
||||
import { expect } from 'chai';
|
||||
import { Client } from './Client';
|
||||
import { addItem, checkSuccessResponse } from './CheckPatten';
|
||||
describe('测试英杰传宝箱', function () {
|
||||
let pinusClient: PinusWSClient;
|
||||
|
||||
let roleInfo;
|
||||
const GOOD_ID_ZHUGE = 71058;
|
||||
const GOOD_ID_RED_RAND = 71072;
|
||||
|
||||
before(function (done) {
|
||||
const c = new Client();
|
||||
const timer = setInterval(() => {
|
||||
if (c.client) {
|
||||
pinusClient = c.client;
|
||||
roleInfo = c.roleInfo;
|
||||
clearInterval(timer);
|
||||
done();
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
|
||||
afterEach(function (done) {
|
||||
pinusClient.disconnect();
|
||||
// disconnect 后等待 500ms,供服务器清理环境、退出频道等
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
it('使用随机保底宝箱', function (done) {
|
||||
addItem(pinusClient, GOOD_ID_RED_RAND, 1, (res) => {
|
||||
pinusClient.request('role.itemHandler.useItem', {
|
||||
id: GOOD_ID_RED_RAND,
|
||||
selected: [],
|
||||
count: 1
|
||||
}, (res) => {
|
||||
checkSuccessResponse(res);
|
||||
console.log(JSON.stringify(res));
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -543,6 +543,7 @@ export const FILENAME = {
|
||||
DIC_GACHA_FLOOR: 'dic_zyz_gachaFloor',
|
||||
DIC_GIFT_PACKAGE: 'dic_zyz_giftPackage',
|
||||
DIC_GIFT_PACKAGE_PLAN: 'dic_zyz_giftPackagePlan',
|
||||
DIC_GIFT_PACKAGE_FLOOR: 'dic_zyz_giftPackageFloor',
|
||||
DIC_RECRUIT: 'dic_zyz_recruit',
|
||||
DIC_RMB: 'dic_zyz_rmb',
|
||||
DIC_ACTIVITY_TYPE: 'dic_zyz_activityType',
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import BaseModel from './BaseModel';
|
||||
import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoose';
|
||||
|
||||
export class Floor {
|
||||
@prop({ required: true })
|
||||
id: number; // 保底类型,对应 DicGiftPackageFloor 的 floorId
|
||||
@prop({ required: true })
|
||||
count: number; // 抽卡次数
|
||||
@prop({ required: true })
|
||||
quality: number; // 品质
|
||||
}
|
||||
|
||||
/**
|
||||
* 战斗记录接口
|
||||
* 宝箱保底
|
||||
*/
|
||||
@index({ roleId: 1, giftPackageId: 1, floorPlanId: 1 })
|
||||
|
||||
@@ -12,12 +21,14 @@ export default class GiftPackageFloor extends BaseModel {
|
||||
@prop({ required: true })
|
||||
giftPackageId: number; // 礼包 id
|
||||
@prop({ required: true })
|
||||
floorPlanId: number; // 物品id
|
||||
floorPlanId: number; // 礼包内容 id,唯一 id
|
||||
|
||||
@prop({ required: true, default: 0 })
|
||||
getNum: number; // 获取次数,当allNum超过配置次数重置
|
||||
@prop({ required: true, default: 0 })
|
||||
allNum: number; // 全部次数,当超过配置次数重置
|
||||
@prop({ required: true, type: Floor, default: [], _id: false })
|
||||
floors: Floor[]; // 已抽卡次数
|
||||
@prop({ required: true, default: 0 })
|
||||
getSum: number; // 获取道具的全部次数
|
||||
@prop({ required: true, default: 0 })
|
||||
@@ -29,6 +40,18 @@ export default class GiftPackageFloor extends BaseModel {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 根据礼包 id 查找
|
||||
public static async findByGiftPackageId(roleId: string, giftPackageId: number, lean = true) {
|
||||
const result: GiftPackageFloorType = await GiftPackageFloorModel.findOneAndUpdate({ roleId, giftPackageId}, {}, {new: true, upsert: true}).lean(lean);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 根据礼包 id 更新
|
||||
public static async updateByGiftPackageId(roleId: string, giftPackageId: number, params: {floors: Floor[], getSum: number, allSum: number}, lean = true) {
|
||||
const result: GiftPackageFloorType = await GiftPackageFloorModel.findOneAndUpdate({roleId, giftPackageId}, {$set: params}, {new: true}).lean(lean);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static async updateByPlanId(roleId: string, giftPackageId: number, floorPlanId: number, params: {getNum: number, allNum: number, getSum: number, allSum: number}, lean = true) {
|
||||
const result: GiftPackageFloorType = await GiftPackageFloorModel.findOneAndUpdate({roleId, giftPackageId, floorPlanId}, {$set: params}).lean(lean);
|
||||
return result;
|
||||
|
||||
@@ -118,6 +118,7 @@ import { dicArtifactSeid, loadArtifactSeid } from "./dictionary/DicArtifactSeid"
|
||||
import { DicArtifact } from "./dictionary/DicArtifact";
|
||||
import { DicArtifactQuality } from "./dictionary/DicArtifactQuality";
|
||||
import { dicGiftPackagePlan, loadGiftPackagePlan } from "./dictionary/DicGiftPackagePlan";
|
||||
import { dicGiftPackageFloor, loadGiftPackageFloor } from "./dictionary/DicGiftPackageFloor";
|
||||
import { dicGVGPeriod, loadGVGPeriod } from './dictionary/DicGVGPeriod';
|
||||
import { dicGVGTech, loadGVGTech } from "./dictionary/DicGVGTech";
|
||||
import { dicGVGItem, loadGVGItem } from "./dictionary/DicGVGItems";
|
||||
@@ -246,6 +247,7 @@ export const gameData = {
|
||||
heroTransPiece: new Map<number, number>(),
|
||||
giftPackage: dicGiftPackage,
|
||||
giftPackagePlan: dicGiftPackagePlan,
|
||||
giftPackageFloor: dicGiftPackageFloor,
|
||||
comBtlLvRange: new Map<number, {min: number, max: number}>(),
|
||||
recruit: dicRecruit,
|
||||
rmb: dicRMB,
|
||||
@@ -1417,6 +1419,7 @@ function loadDatas() {
|
||||
loadGachaFloor();
|
||||
loadGiftPackage();
|
||||
loadGiftPackagePlan();
|
||||
loadGiftPackageFloor();
|
||||
loadRecruit();
|
||||
loadRMB();
|
||||
loadActivityType();
|
||||
|
||||
23
shared/pubUtils/dictionary/DicGiftPackageFloor.ts
Normal file
23
shared/pubUtils/dictionary/DicGiftPackageFloor.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
// 礼包奖励表
|
||||
import { readFileAndParse } from '../util';
|
||||
import { FILENAME } from '../../consts';
|
||||
|
||||
// 礼包保底表
|
||||
export interface DicGiftPackageFloor {
|
||||
// 唯一id
|
||||
readonly id: number;
|
||||
// 触发保底次数
|
||||
readonly times: number;
|
||||
// 品质(触发多保底时的优先级覆盖关系)
|
||||
readonly quality: number;
|
||||
}
|
||||
|
||||
export const dicGiftPackageFloor = new Map<number, DicGiftPackageFloor>(); // floorId => [floor]
|
||||
export function loadGiftPackageFloor() {
|
||||
dicGiftPackageFloor.clear();
|
||||
let arr = readFileAndParse(FILENAME.DIC_GIFT_PACKAGE_FLOOR);
|
||||
arr.forEach(o => {
|
||||
dicGiftPackageFloor.set(o.id, o);
|
||||
});
|
||||
arr = undefined;
|
||||
}
|
||||
@@ -16,10 +16,8 @@ export interface DicGiftPackagePlan {
|
||||
readonly count: number;
|
||||
// 权重
|
||||
readonly weight: number;
|
||||
// 保底, 每floor必出floorCount个,提前获取到次数就重置
|
||||
readonly floorFrequency: number;
|
||||
// 保底数量
|
||||
readonly floorCount: number;
|
||||
// 保底表id
|
||||
readonly floorId: number;
|
||||
}
|
||||
|
||||
type KeysEnum<T> = { [P in keyof Required<T>]: true };
|
||||
@@ -30,8 +28,7 @@ const DicGiftPackagePlanKeys: KeysEnum<DicGiftPackagePlan> = {
|
||||
content: true,
|
||||
count: true,
|
||||
weight: true,
|
||||
floorFrequency: true,
|
||||
floorCount: true
|
||||
floorId: true,
|
||||
}
|
||||
|
||||
export const dicGiftPackagePlan = new Map<number, DicGiftPackagePlan[]>(); // packageId => [plan]
|
||||
@@ -40,14 +37,7 @@ export function loadGiftPackagePlan() {
|
||||
let arr = readFileAndParse(FILENAME.DIC_GIFT_PACKAGE_PLAN);
|
||||
|
||||
arr.forEach(o => {
|
||||
if(o.floor == '&') {
|
||||
o.floorFrequency = 0;
|
||||
o.floorCount = 0;
|
||||
} else {
|
||||
let arr = o.floor.split('&');
|
||||
o.floorFrequency = isNaN(parseInt(arr[0]))? 0: parseInt(arr[0]);
|
||||
o.floorCount = isNaN(parseInt(arr[1]))? 0: parseInt(arr[1]);
|
||||
}
|
||||
o.floorId = o.floor;
|
||||
if(!dicGiftPackagePlan.has(o.giftPackageId)) {
|
||||
dicGiftPackagePlan.set(o.giftPackageId, []);
|
||||
}
|
||||
|
||||
12
shared/resource/jsons/dic_zyz_giftPackageFloor.json
Normal file
12
shared/resource/jsons/dic_zyz_giftPackageFloor.json
Normal file
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"quality": 3,
|
||||
"times": 40
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"quality": 4,
|
||||
"times": 100
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user