Files
ZYZ/game-server/app/services/activity/gachaService.ts
2021-07-17 14:49:33 +08:00

540 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { GachaData, Floor, GachaResult, Hope, GachaListReturn } from "../../domain/activityField/gachaField";;
import { ActivityModel } from "../../db/Activity";
import { DicGacha } from "../../pubUtils/dictionary/DicGacha";
import { UserGachaType, UserGachaModel } from "../../db/UserGacha";
import { shouldRefresh, getRandEelm, getRandEelmWithWeight } from "../../pubUtils/util";
import { REFRESH_TIME, GACHA_TO_FLOOR, GACHA_FLOOR_TYPE, GACHA_CONTENT_TYPE, HERO_QUALITY_TYPE, GACHA_OCCUPY_HID, IT_TYPE, ITID, CONSUME_TYPE, SPECIAL_ATTR, TIME_OUTPUT_TYPE, GACHA_ID } from "../../consts";
import { getTimeFunD, getZeroPointD } from "../../pubUtils/timeUtil";
import { gameData, getDicGachaFloor } from "../../pubUtils/data";
import { RoleModel } from "../../db/Role";
import { RewardInter } from "../../pubUtils/interface";
import { CreateHeroParam } from "../../domain/roleField/hero";
import { transPiece } from "../../pubUtils/itemUtils";
import { HeroType } from "../../db/Hero";
import { NewHeroGachaItem } from "../../domain/activityField/newHeroGachaField";
/**
* 获取招募列表
* @param roleId
*/
export async function getGachaList(roleId: string) {
let userGachaList = await UserGachaModel.findAllByRole(roleId);
let list: GachaListReturn[] = [];
for (let [id, dicGacha] of gameData.gacha) {
if (id == GACHA_ID.TIMELIMIT) continue; // 不包括限时
let userGacha = userGachaList.find(cur => cur.gachaId == id);
if (userGacha)
userGacha = await refreshGacha(dicGacha, userGacha);
let param = new GachaListReturn(dicGacha, userGacha);
list.push(param);
}
return list;
}
/**
* 获取活动页签里的限时卡池
*
* @param roleId 玩家id
* @param aid 活动id
*/
export async function getLimitGacha(serverId: number, roleId: string, activityId: number) {
let activityData = await ActivityModel.findActivity(activityId);
if (!activityData) return false;
let { createTime } = await RoleModel.findByRoleId(roleId);
let gachaData = new GachaData(activityData, createTime);
let userGacha = await UserGachaModel.findByRole(roleId, gachaData.gachaId, activityId);
userGacha = await refreshGacha(gameData.gacha.get(gachaData.gachaId), userGacha);
gachaData.setUserGacha(userGacha);
return gachaData
}
/**
* 刷新免费次数
* @param dicGacha
* @param userGacha
*/
export async function refreshGacha(dicGacha: DicGacha, userGacha: UserGachaType) {
let { day, count } = dicGacha.free;
if (count <= 0) {
return userGacha;
}
let { roleId, gachaId, refFreeTime, refHopeTime, hope } = userGacha;
if (shouldRefresh(refFreeTime, new Date(), REFRESH_TIME, day)) {
let ref = <Date>getTimeFunD().getAfterDayByGap(refFreeTime, day);
userGacha = await UserGachaModel.refreshFreeCount(roleId, gachaId, 0, ref);
}
if (shouldRefresh(refHopeTime, new Date(), REFRESH_TIME)) {
hope = hope.map(cur => { return { ...cur, hasGet: false } });
let ref = getZeroPointD();
userGacha = await UserGachaModel.refreshHopeCount(roleId, gachaId, 0, hope, ref);
}
return userGacha
}
export async function getVisitedHeroList(roleId: string) {
let { visitedHero, refVisitedTime } = await UserGachaModel.findByRole(roleId, GACHA_ID.NORMAL, 0);
if (shouldRefresh(refVisitedTime, new Date())) {
visitedHero = [];
}
return visitedHero;
}
/**
* 抽卡方法
*/
export class GachaPull {
private gachaType: GACHA_ID; // 抽卡类型
private dicFloorType: GACHA_FLOOR_TYPE; // 保底类型 金将保底,指定保底
// 字典
private dicFloorCount: number; // 保底次数
private percent: { id: number, weight: number, goodId?: number }[];
private dicFloor: { id: number, weight: number, goodId?: number }; // 保底的一项percent中的一项
// 玩家数据
private lv: number; // 玩家等级
private userHeroes: HeroType[]; // 玩家的所有武将
private floorCount: number = 0; // 记录下的保底次数
private hasGetFloor: boolean = false; // 玩家是否达到保底
private hope: Hope[] = []; // 玩家记录的心愿单达成情况
constructor(gachaType: GACHA_ID, lv: number, userHeroes: HeroType[]) {
this.gachaType = gachaType;
if (gachaType == GACHA_ID.NORMAL) { // 元宝招募,金色武将保底,按次数给保底,抽到就重新计算次数,单抽也算
this.dicFloorType = GACHA_FLOOR_TYPE.GOLD;
} else if (gachaType == GACHA_ID.ASSIGN || gachaType == GACHA_ID.TIMELIMIT) { // 求贤若渴和活动抽卡金色保底伪随机n次内给且只给一个单抽也算
this.dicFloorType = GACHA_FLOOR_TYPE.ASSIGN;
} else {
this.dicFloorType = GACHA_FLOOR_TYPE.GOLD;
}
this.userHeroes = userHeroes;
this.lv = lv;
}
// 一般抽卡setter
public setByUserGacha(dicGacha: DicGacha, userGacha: UserGachaType) {
// 字典方面
let percent = dicGacha.percent;
// 设置pickup的武将
let newPercent = [];
for (let { id, weight } of percent) {
if (id == gameData.gachaContentHero.get(0)) {
newPercent.push({ id, weight, goodId: userGacha.pickHero })
} else {
newPercent.push({ id, weight });
}
}
this.percent = newPercent;
if (dicGacha.floorReward > 0) {
this.dicFloor = this.percent[dicGacha.floorReward - 1];
}
this.dicFloorCount = getDicGachaFloor(this.dicFloorType);
// 玩家数据
let userFloor = userGacha.floor.find(cur => cur.id == this.dicFloorType);
if (userFloor) {
this.floorCount = userFloor.count;
this.hasGetFloor = userFloor.hasGetFloor || false;
}
this.hope = userGacha.hope;
}
// 新武将活动setter
public setByActivity(item: NewHeroGachaItem) {
this.floorCount = item.count;
this.hasGetFloor = item.hasGetFloor;
this.percent = item.percent;
this.dicFloorCount = item.floorCount;
if (item.floorReward >= 0) {
this.dicFloor = item.percent[item.floorReward]
}
}
private getFloorResult(base: { id: number, weight: number, goodId?: number }) {
if (!this.dicFloor) return base;
let isTarget = base.id == this.dicFloor.id; // 是否抽到的就是那个保底
if (this.dicFloorType == GACHA_FLOOR_TYPE.GOLD) { // 元宝招募 橙色 按次数保底,抽到就重计
if (isTarget || this.floorCount + 1 >= this.dicFloorCount) {
this.floorCount = 0;
return this.dicFloor;
} else {
this.floorCount++;
return base;
}
} else if (this.dicFloorType == GACHA_FLOOR_TYPE.ASSIGN) {
let result = base;
if (isTarget || this.floorCount + 1 >= this.dicFloorCount) {
if (this.hasGetFloor) { // 已经获得过一次保底了, 不给,换个给
let { dic } = getRandEelmWithWeight(this.percent.filter(cur => cur.id != this.dicFloor.id));
result = dic
} else {
this.hasGetFloor = true;
result = this.dicFloor;
}
}
this.floorCount++;
if (this.floorCount >= this.dicFloorCount) {
this.hasGetFloor = false;
this.floorCount = 0;
}
console.log('******', this.floorCount, this.dicFloorCount, this.hasGetFloor)
return result;
} else {
return base
}
}
/**
* 根据contentId获得抽卡结果包括心愿单
* @param contentId dic_zyz_gachaContent的id
* @param lv 玩家等级
* @param hope 玩家心愿单
*/
private getResultFromContentId(contentId: number, goodId?: number) {
let dic = gameData.gachaContent.get(contentId);
let { type, param, count } = dic;
if (type == GACHA_CONTENT_TYPE.HERO) {
let pool: number[] = [];
if (this.hope.length > 0) {
pool = this.getPoolByHope(this.hope, param[0]); // 根据心愿单获得卡池
} else {
pool = getAllHeroByQuality(param[0]); // 没有心愿单就根据武将颜色
}
let hero = getRandEelm(pool);
let result = new GachaResult(contentId);
result.setHero(hero[0]);
if (goodId > 0) {
result.setHero(goodId);
}
return result
} else {
let pool: number[] = [];
if (type == GACHA_CONTENT_TYPE.HERO_PIECE) {
pool = getAllItemByQuality(IT_TYPE.HERO_PIECE, param[0], this.lv);
} else if (type == GACHA_CONTENT_TYPE.BLUEPRT) {
pool = getAllItemByQuality(IT_TYPE.BLUEPRT, param[0], this.lv);
} else if (type == GACHA_CONTENT_TYPE.JEWEL) {
pool = getAllJewelByLv(param[0]);
} else if (type == GACHA_CONTENT_TYPE.TERAPH_MATERIAL) {
pool = param;
} else if (type == GACHA_CONTENT_TYPE.SUIT_PAPER) {
pool = getSuitPaper(this.lv);
}
let item = getRandEelm(pool);
let result = new GachaResult(contentId);
result.setItem(item[0], count);
if (goodId > 0) {
result.setItem(goodId, count);
}
return result;
}
}
/**
* 心愿
* @param hope 玩家数据里的心愿单
* @param qualtiy 武将品质
*/
private getPoolByHope(hope: Hope[], qualtiy: number) {
if (qualtiy == HERO_QUALITY_TYPE.GOLD) {
let hopeMap = new Map<number, Hope>();
let hasGetHope: number[] = [];
for (let h of hope) {
if (h.hid > 0) {
hopeMap.set(h.id, h);
if (h.hasGet) hasGetHope.push(h.id);
}
}
let list = gameData.gachaHope.map(cur => {
return { ...cur, hasGet: hopeMap.get(cur.id)?.hasGet || false };
})
let { dic: { id, hasGet } } = getRandEelmWithWeight(list);
if (id == 0 || hasGet) {
return getAllHeroByQuality(qualtiy).filter(cur => !hasGetHope.includes(cur));
} else {
if (hopeMap.has(id)) {
let curHope = hopeMap.get(id);
curHope.hasGet = true;
return [curHope.hid];
} else {
return getAllHeroByQuality(qualtiy).filter(cur => !hasGetHope.includes(cur));
}
}
} else {
return getAllHeroByQuality(qualtiy);
}
}
public pull(count: number) {
let items: RewardInter[] = [], heroInfo: CreateHeroParam[] = [], resultList: GachaResult[] = [];
let activityData = [];//活动需要统计抽中的英雄、碎片品质
for (let i = 0; i < count; i++) {
// 按照一般概率抽出
let { dic } = getRandEelmWithWeight(this.percent);
let { id: contentId, goodId } = this.getFloorResult(dic); // 根据保底替换
contentId = this.getPurpleFloor(count, i, contentId, resultList);
let result = this.getResultFromContentId(contentId, goodId);
if (result.hid > 0) {
let hasHero = this.userHeroes.find(cur => cur.hid == result.hid);
if (hasHero) { // 已有转换为碎片
activityData.push({ hid: hasHero.hid, quality: hasHero.quality });
let { pieceId, count } = transPiece(result.hid);
result.transferToPiece(pieceId, count);
items.push({ id: pieceId, count });
} else {
heroInfo.push({ hid: result.hid, count: 1 })//默认1个英雄
}
} else {
items.push({ id: result.id, count: result.count });
}
resultList.push(result);
}
return { items, heroInfo, resultList, activityData }
}
private getPurpleFloor(count: number, index: number, contentId: number, resultList: GachaResult[]) {
if (this.gachaType == GACHA_ID.NORMAL && count == 10 && index == count - 1) { // 10连到最后一抽
let { type, param } = gameData.gachaContent.get(contentId);
if (type != GACHA_CONTENT_TYPE.HERO || param[0] < HERO_QUALITY_TYPE.PURPLE) {
let hasPurple = false;
for (let { contentId } of resultList) {
let { type, param } = gameData.gachaContent.get(contentId);
if (type == GACHA_CONTENT_TYPE.HERO && param[0] >= HERO_QUALITY_TYPE.PURPLE) {
hasPurple = true; break;
}
}
if (!hasPurple) {
return gameData.gachaContentHero.get(HERO_QUALITY_TYPE.PURPLE);
}
}
}
return contentId
}
// 获得需要储存的数据
// 一般抽卡getter
public getUserGachaParam(userGacha: UserGachaType) {
let floor = userGacha.floor || [];
let curFloor = floor.find(cur => cur.id == this.dicFloorType);
if (curFloor) {
curFloor.hasGetFloor = this.hasGetFloor;
curFloor.count = this.floorCount;
} else {
floor.push({ id: this.dicFloorType, hasGetFloor: this.hasGetFloor, count: this.floorCount })
}
return { hope: this.hope, floor }
}
// 活动getter
public getActivityParam() {
return { hasGetFloor: this.hasGetFloor, floorCount: this.floorCount };
}
}
/**
* @description 获得保底结果
* @param gachaId 招募表id
* @param base 按普通概率获得的东西 内容参考gachaContent
* @param floor 玩家保底数据
*/
export function getFloorResult(gachaId: number, base: number, floor: Floor[]) {
let dicGachaContent = gameData.gachaContent.get(base); // 普通随机得出的结果是哪一类;
if (!dicGachaContent) {
console.error('dic_zyz_gachaContent error');
return false; // DIC_NOT_FOUND 直接报错
}
let touchedFloor = 0, touchedHeroQuality: number; // 触发的保底的id
let dicFloorType = GACHA_TO_FLOOR.get(gachaId);
for (let id of dicFloorType) {
let { type, param } = dicGachaContent;
let heroQuality = getHeroQuality(id);
if (heroQuality == false) continue;
let isTarget = type == GACHA_CONTENT_TYPE.HERO && param[0] == heroQuality;
if (!isTarget) { // 触发保底
let percent = countFloorPercent(id, floor);
let rand = Math.random();
if (rand < percent) { // 可以替换为保底
if (touchedFloor > 0 && touchedFloor < id) { // 如果已经触发过保底了
// 更新被覆盖掉的那个保底数量
updateUserFloor(floor, touchedFloor, false);
}
touchedFloor = id;
touchedHeroQuality = heroQuality;
} else {
updateUserFloor(floor, id, false);
}
} else {
updateUserFloor(floor, id, true);
}
}
if (touchedFloor > 0) {
updateUserFloor(floor, touchedFloor, true);
return gameData.gachaContentHero.get(touchedHeroQuality);
} else {
return base;
}
}
/**
* @description 更新玩家保底数据
* @param floor 玩家保底数据
* @param id 更新的保底类型
* @param needReset 是否重置保底
*/
function updateUserFloor(floor: Floor[], id: number, needReset: boolean) {
let curFloor = floor.find(cur => cur.id == id);
if (!curFloor) {
curFloor = { id, count: 0 };
floor.push(curFloor);
}
if (needReset) {
curFloor.count = 0;
} else {
curFloor.count++;
}
}
/**
* 计算保底概率
* @param id 保底类型
* @param floor 玩家保底数据
*/
function countFloorPercent(id: number, floor: Floor[]) {
let curFloor = floor.find(cur => cur.id == id);
let count = curFloor ? curFloor.count : 0;
// 保底概率,暂时使用线性公式,由策划使用后选择
let floorCount = getDicGachaFloor(id);
return count / floorCount;
}
/**
* 获取保底武将的品质
* @param id 保底类型id in GACHA_FLOOR_TYPE
*/
function getHeroQuality(id: number) {
if (id == GACHA_FLOOR_TYPE.PURPLE) {
return HERO_QUALITY_TYPE.PURPLE;
} else if (id == GACHA_FLOOR_TYPE.GOLD) {
return HERO_QUALITY_TYPE.GOLD;
} else if (id == GACHA_FLOOR_TYPE.ASSIGN) {
return 0
} else {
return false;
}
}
/**
* 根据品质获得武将池
* @param quality 品质
*/
export function getAllHeroByQuality(quality: number) {
if (quality == 0) return [GACHA_OCCUPY_HID];
let allHero: number[] = [];
let allQuality: number[] = [];
for (let { actorId } of gameData.recruit) {
let dicHero = gameData.hero.get(actorId);
if (dicHero.quality == quality) {
allHero.push(actorId);
}
if (allQuality.indexOf(dicHero.quality) == -1) allQuality.push(dicHero.quality);
}
allQuality.sort((a, b) => a - b);
if (allQuality.length == 0) return [];
if (allHero.length == 0) {
allHero = getAllHeroByQuality(allQuality[0]);
}
return allHero;
}
function getAllItemByQuality(itid: number, quality: number, lv: number) {
let allPiece: number[] = [];
for (let [id, dicGoods] of gameData.goods) {
if (dicGoods.itid == itid) {
if ((quality == 0 || dicGoods.quality == quality) || dicGoods.lvLimited <= lv) {
allPiece.push(id);
}
}
}
return allPiece;
}
function getAllJewelByLv(lv: number) {
let itids: number[] = [];
for (let [id, { type }] of ITID) {
if (type == CONSUME_TYPE.JEWEL) itids.push(id);
}
let items: number[] = [];
for (let [id, dicGoods] of gameData.goods) {
if (itids.includes(dicGoods.itid)) {
if (lv == 0 || dicGoods.lvLimited == lv) {
items.push(id);
}
}
}
return items;
}
function getSuitPaper(lv: number) {
let items: number[] = [];
for (let [id, dicGoods] of gameData.goods) {
if (dicGoods.itid == IT_TYPE.PAPER) {
if (dicGoods.lvLimited <= lv) {
items.push(id);
}
}
}
return items;
}
/**
* 根据contentId获得抽卡结果 新武将抽卡活动
* @param contentId dic_zyz_gachaContent的id
* @param lv 玩家等级
* @param goodId 大于0指定id0:随机
*/
export function getResultFromContentIdNewHeroActivity(contentId: number, goodId: number, lv: number,) {
let dic = gameData.gachaContent.get(contentId);
let { type, param, count } = dic;
if (type == GACHA_CONTENT_TYPE.HERO) {
let result = new GachaResult(contentId);
result.setHero(goodId);
return result
} else {
let pool: number[] = [];
if (type == GACHA_CONTENT_TYPE.HERO_PIECE) {
pool = getAllItemByQuality(IT_TYPE.HERO_PIECE, param[0], lv);
} else if (type == GACHA_CONTENT_TYPE.BLUEPRT) {
pool = getAllItemByQuality(IT_TYPE.BLUEPRT, param[0], lv);
} else if (type == GACHA_CONTENT_TYPE.JEWEL) {
pool = getAllJewelByLv(param[0]);
} else if (type == GACHA_CONTENT_TYPE.TERAPH_MATERIAL) {
pool = param;
} else if (type == GACHA_CONTENT_TYPE.SUIT_PAPER) {
pool = getSuitPaper(lv);
}
let item = getRandEelm(pool);
let result = new GachaResult(contentId);
result.setItem(item[0], count);
if (goodId > 0) {
result.setItem(goodId, count);
}
return result;
}
}