Files
ZYZ/game-server/app/services/activity/gachaService.ts
luying c6348e1da7 Revert " feat(db): 给UserGacha添加只读库和相关操作"
This reverts commit 8bcac0c7fe943033570d5c341e18ab90ff423f14.
2023-05-22 19:40:53 +08:00

574 lines
21 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 { Floor as UserFloor, Hope as UserHope, GachaListReturn, GachaResultIndb } from "../../domain/activityField/gachaField";;
import { DicGacha } from "../../pubUtils/dictionary/DicGacha";
import { UserGachaType, UserGachaModel } from "../../db/UserGacha";
import { shouldRefresh, getRandEelm, getRandEelmWithWeight, getRandSingleIndex, getRandSingleEelm } from "../../pubUtils/util";
import { REFRESH_TIME, GACHA_FLOOR_TYPE, HERO_QUALITY_TYPE, IT_TYPE, ITID, CONSUME_TYPE, SPECIAL_ATTR, TIME_OUTPUT_TYPE, GACHA_TYPE, GACHA_PLAN_TYPE } from "../../consts";
import { getTimeFunD, getZeroPointD } from "../../pubUtils/timeUtil";
import { gameData } from "../../pubUtils/data";
import { RewardInter } from "../../pubUtils/interface";
import { CreateHeroParam } from "../../domain/roleField/hero";
import { HeroType } from "../../db/Hero";
import { getActivityById } from "./activityService";
import { transPiece } from "../role/util";
import { getRoleCreateTime, getServerCreateTime } from "../redisService";
import { SimpleResult, GuideGachaData } from "../../domain/activityField/guideGachaField";
import { isGoodsHidden, isHeroHidden } from "../dataService";
/**
* 获取招募列表
* @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(!dicGacha.showInMainPage) continue;
let userGacha = userGachaList.find(cur => cur.gachaId == id);
if (userGacha)
userGacha = await refreshGacha(dicGacha, userGacha);
dicGacha = getDicGachaByGachaCnt(dicGacha, userGacha?.count||0);
let param = new GachaListReturn(dicGacha, userGacha);
list.push(param);
}
return list;
}
export function getNormalGachaId() {
let ids = gameData.gachaByType.get(GACHA_TYPE.NORMAL);
return ids.find(gachaId => gameData.gacha.get(gachaId)?.showInMainPage == 1);
}
/**
* 刷新免费次数
* @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 gachaId = getNormalGachaId();
let { visitedHero, refVisitedTime } = await UserGachaModel.findByRole(roleId, gachaId, 0);
if (shouldRefresh(refVisitedTime, new Date())) {
visitedHero = [];
}
return visitedHero;
}
class PlayerGachaRecord {
public lv: number; // 玩家等级
public hope: UserHope[] = []; // 玩家记录的心愿单达成情况
public floor: UserFloor[] = [];
public pickHero: number = 0;
public setUserGacha(floor: UserFloor[], hope: UserHope[], pickHero: number ) {
this.floor = floor;
this.hope = hope;
this.pickHero = pickHero;
}
public getFloorCountById(id: number) {
let userFloor = this.floor.find(cur => cur.id == id);
if(userFloor) {
return { floorCount: userFloor.count, hasGetFloor: userFloor.hasGetFloor }
} else {
return { floorCount: 0, hasGetFloor: false }
}
}
public setFloorCount(id: number, count: number, hasGetFloor: boolean) {
let index = this.floor.findIndex(cur => cur.id == id);
if(index == -1) {
this.floor.push({ id, count, hasGetFloor })
} else {
this.floor[index].count = count;
this.floor[index].hasGetFloor = hasGetFloor;
}
}
public getHopeHero(randHopePosition: number) {
let userHope = this.hope.find(cur => cur.id == randHopePosition);
if(!userHope || userHope.hasGet) return 0;
return userHope.hid||0;
}
public setUserHope(hid: number) {
if(hid <= 0) return;
let userHope = this.hope.find(cur => cur.hid == hid);
if(userHope && !userHope.hasGet) userHope.hasGet = true;
}
public getUserGachaParam() {
return { hope: this.hope, floor: this.floor }
}
}
export class GachaResult {
planId: number = 0; // 计划id
hid: number = 0; // 武将id
isTransfer: boolean = false; // 是否转换为碎片
id: number = 0; // 道具id
count: number = 0; // 道具数量
pickHero: number = 0; // 玩家选择的武将
heroQuality: number = 0; // planId对应的武将品质
constructor(planId: number) {
this.planId = planId;
this.heroQuality = gameData.gachaPlanQuality.get(planId);
}
public changePlan(planId: number, heroQuality: number) {
this.planId = planId;
this.heroQuality = heroQuality;
}
public isTarget(floor: GachaFloor) {
return floor.heroQuality == this.heroQuality;
}
public setHero(hid: number, count: number) {
this.hid = hid;
this.count = count;
}
public setItem(id: number, count: number) {
this.id = id;
if(count) this.count = count;
}
}
export class GachaResults {
count: number = 0;
list: GachaResult[] = [];
public addPlan(planId: number) {
this.count++;
this.list.push(new GachaResult(planId));
}
public addBySimpleResult(simpleResult: SimpleResult[]) {
for(let { planId, hid } of simpleResult) {
let gachaResult = new GachaResult(planId);
gachaResult.setHero(hid, 1);
this.list.push(gachaResult);
}
}
public getTargetCnt(floor: GachaFloor) {
let target = this.list.filter(cur => cur.heroQuality == floor.heroQuality);
return target.length;
}
public getRandomResultOfTarget(floor: GachaFloor, cnt: number) {
let target = this.list.filter(cur => cur.heroQuality == floor.heroQuality);
let targetRemoveLast = target.slice(0, cnt + 1);
return getRandEelm(targetRemoveLast, cnt);
}
// 具体的卡池
public processDetail(pickHero?: number, count?: number) {
for(let gachaResult of this.list) {
let detail = this.getRandomDetail(gachaResult.planId);
if(!detail) continue;
switch(detail.type) {
case GACHA_PLAN_TYPE.HERO:
gachaResult.setHero(detail.content, count||1);
break;
case GACHA_PLAN_TYPE.ASSIGN_HERO:
gachaResult.setHero(pickHero, getPickHeroCnt(pickHero));
break;
case GACHA_PLAN_TYPE.ITEM:
gachaResult.setItem(detail.content, detail.count);
break;
case GACHA_PLAN_TYPE.ASSIGN_ITEM:
gachaResult.setItem(getPickHeroPiece(pickHero), detail.count);
break;
}
}
}
private getRandomDetail(planId: number) {
let details = (gameData.gachaPlan.get(planId)||[]).filter(detail => {
switch(detail.type) {
case GACHA_PLAN_TYPE.HERO:
return !isHeroHidden(detail.content);
case GACHA_PLAN_TYPE.ITEM:
return !isGoodsHidden(detail.content);
case GACHA_PLAN_TYPE.ASSIGN_HERO:
case GACHA_PLAN_TYPE.ASSIGN_ITEM:
return true;
}
});
let { dic } = getRandEelmWithWeight(details);
return dic
}
public transferToFinalResult(userHeroes: HeroType[]) {
let hids = userHeroes.map(cur => cur.hid);
let items: RewardInter[] = [], heroInfo: CreateHeroParam[] = [];
let newResult: GachaResultIndb[] = [];
for (let result of this.list) {
if (result.hid > 0) {
for(let i = 0; i < result.count; i++) {
newResult.push(new GachaResultIndb(result));
}
} else {
newResult.push(new GachaResultIndb(result));
}
}
for(let result of newResult) {
if (result.hid > 0) {
let hasHero = hids.indexOf(result.hid) != -1;
if (hasHero) { // 已有转换为碎片
let { pieceId, count } = transPiece(result.hid);
result.transferToPiece(pieceId, count);
items.push({ id: pieceId, count });
} else {
heroInfo.push({ hid: result.hid, count: 1 })//默认1个英雄
hids.push(result.hid);
}
} else {
items.push({ id: result.id, count: result.count });
}
}
return { items, heroInfo, resultList: newResult }
}
}
class GachaPlan {
index: number;
planId: number;
weight: number;
heroQuality: number;
constructor(planId: number, weight: number, index: number) {
this.planId = planId;
this.weight = weight;
this.index = index;
this.heroQuality = gameData.gachaPlanQuality.get(planId);
}
}
class GachaPercent {
percents: GachaPlan[] = [];
constructor(percent: { planId: number, weight: number }[]) {
for(let i = 1; i <= percent.length; i++) {
let { planId, weight } = percent[i - 1];
this.percents.push(new GachaPlan(planId, weight, i));
}
}
public getRandomPlan() {
let { dic } = getRandEelmWithWeight(this.percents);
return dic
}
public getReplacePlan(heroQuality: number, isLower: boolean) {
// 低一级顺序按 heroQuality: -1 => weight: 1 => index: -1 排序拿最前面那个
// 高一级顺序按 heroQuality: 1 => weight: -1 => index: 1 排序最前面那个
let arr = this.percents.filter(plan => {
return isLower? plan.heroQuality < heroQuality: plan.heroQuality > heroQuality;
}).sort((a, b) => {
if(a.heroQuality != b.heroQuality) {
return isLower? b.heroQuality - a.heroQuality: a.heroQuality - b.heroQuality;
} else if(a.weight != b.weight) {
return isLower? a.weight - b.weight: b.weight - a.weight;
} else {
return isLower? b.index - a.index: a.index - b.index;
}
});
return arr[0];
}
public getFloorTarget(heroQuality: number) {
let arr = this.percents.filter(plan => plan.heroQuality == heroQuality).sort((a, b) => {
if(a.weight != b.weight) {
return a.weight - b.weight;
} else {
return b.index - a.index;
}
});
return arr[0];
}
}
class GachaFloor {
id: number;
floorType: GACHA_FLOOR_TYPE;
heroQuality: number;
param: number;
param2: number = 0;
constructor(id: number) {
let dicGachaFloor = gameData.gachaFloor.get(id);
if(dicGachaFloor) {
this.id = dicGachaFloor.id;
this.floorType = dicGachaFloor.floorType;
this.heroQuality = dicGachaFloor.heroQuality;
this.param = dicGachaFloor.param;
if(dicGachaFloor.floorType == GACHA_FLOOR_TYPE.TEN_MOST_HIGHER || dicGachaFloor.floorType == GACHA_FLOOR_TYPE.TEN_MOST_LOWER) {
this.param2 = 10;
} else if(dicGachaFloor.floorType == GACHA_FLOOR_TYPE.FIVE_MOST_LOWER) {
this.param2 = 5;
}
}
}
isReplaceLower() {
return [
GACHA_FLOOR_TYPE.MAIN_FLOOR,
GACHA_FLOOR_TYPE.ONLY_ONE,
GACHA_FLOOR_TYPE.TEN_MOST_LOWER,
GACHA_FLOOR_TYPE.FIVE_MOST_LOWER,
].indexOf(this.floorType) != -1
}
}
/**
* 抽卡方法
*/
export class GachaPull {
private gachaType: GACHA_TYPE; // 抽卡类型
// 字典
private floors: GachaFloor[] = []; // 保底次数
private percent: GachaPercent;
// 玩家数据
private player: PlayerGachaRecord = new PlayerGachaRecord();
// 结果
private result = new GachaResults();
constructor(dicGacha: DicGacha, userGacha?: { floor: UserFloor[], hope: UserHope[], pickHero: number }) {
this.gachaType = dicGacha.gachaType;
this.percent = new GachaPercent(dicGacha.percent);
if(userGacha)
this.player.setUserGacha(userGacha.floor, userGacha.hope, userGacha.pickHero);
for(let id of dicGacha.floor) {
let floor = new GachaFloor(id);
if(floor && floor.id) this.floors.push(floor);
}
}
public pull(count: number, userHeroes: HeroType[]) {
this.pullContent(count);
this.processFloors();
this.result.processDetail(this.player.pickHero);
this.processHope();
let { heroInfo, items, resultList } = this.getFinalResult(userHeroes)
return { resultList, heroInfo, items };
}
// 根据基础的percent抽基地
private pullContent(count: number) {
for (let i = 0; i < count; i++) {
// 按照一般概率抽出
let dic = this.percent.getRandomPlan();
if(dic) this.result.addPlan(dic.planId);
}
}
// 保底
private processFloors() {
for(let floor of this.floors) {
switch(floor.floorType) {
case GACHA_FLOOR_TYPE.MAIN_FLOOR:
this.setMainFloor(floor);
break;
case GACHA_FLOOR_TYPE.ONLY_ONE:
this.setAssignFloor(floor);
break;
case GACHA_FLOOR_TYPE.TEN_MOST_HIGHER:
case GACHA_FLOOR_TYPE.TEN_MOST_LOWER:
case GACHA_FLOOR_TYPE.FIVE_MOST_LOWER:
this.setTenMostFloor(floor);
break;
}
}
}
// 元宝招募保底,金将保底,按次数给保底,抽到就重新计算次数,单抽也算
private setMainFloor(floor: GachaFloor) {
let { floorCount: historyCount } = this.player.getFloorCountById(floor.id);
for(let gachaResult of this.result.list) {
if(++historyCount >= floor.param || gachaResult.isTarget(floor)) {
let replacePlan = this.percent.getFloorTarget(floor.heroQuality);
if(replacePlan) gachaResult.changePlan(replacePlan.planId, replacePlan.heroQuality);
historyCount = 0;
}
}
this.player.setFloorCount(floor.id, historyCount, false);
}
// 求贤若渴和活动抽卡金色保底伪随机n次内给且只给一个单抽也算
public setAssignFloor(floor: GachaFloor) {
let { floorCount: historyCount, hasGetFloor } = this.player.getFloorCountById(floor.id);
for(let gachaResult of this.result.list) {
if(++historyCount >= floor.param || gachaResult.isTarget(floor)) {
if(hasGetFloor) { // 已经获得过一次保底了, 不给,换个给
let replacePlan = this.percent.getReplacePlan(gachaResult.heroQuality, floor.isReplaceLower());
if(replacePlan) gachaResult.changePlan(replacePlan.planId, replacePlan.heroQuality);
} else { // 设置为保底
let replacePlan = this.percent.getFloorTarget(floor.heroQuality);
if(replacePlan) gachaResult.changePlan(replacePlan.planId, replacePlan.heroQuality);
hasGetFloor = true;
}
}
if(historyCount >= floor.param) {
hasGetFloor = false;
historyCount = 0;
}
}
this.player.setFloorCount(floor.id, historyCount, hasGetFloor);
}
// 10连最多多少个武将
private setTenMostFloor(floor: GachaFloor) {
let targetCnt = this.result.getTargetCnt(floor);
if(this.result.count == floor.param2 && targetCnt > floor.param) {
let gachaResults = this.result.getRandomResultOfTarget(floor, targetCnt - floor.param);
for(let gachaResult of gachaResults) {
let replacePlan = this.percent.getReplacePlan(gachaResult.heroQuality, floor.isReplaceLower());
if(replacePlan) gachaResult.changePlan(replacePlan.planId, replacePlan.heroQuality)
}
}
}
// 心愿单
private processHope() {
if(this.gachaType != GACHA_TYPE.NORMAL) return;
for(let gachaResult of this.result.list) {
if(gachaResult.heroQuality != HERO_QUALITY_TYPE.GOLD) continue; // 只有橙将
let { dic: { id: randHopePosition } } = getRandEelmWithWeight(gameData.gachaHope);
if(randHopePosition > 0) { // 按玩家设置的心愿单塞
let hopeHero = this.player.getHopeHero(randHopePosition)
if(hopeHero > 0) gachaResult.setHero(hopeHero, 1);
}
this.player.setUserHope(gachaResult.hid); // 设置心愿单中了没有
}
}
// 创建信息
public getFinalResult(userHeroes: HeroType[]) {
return this.result.transferToFinalResult(userHeroes);
}
// 获得需要储存的数据
// 一般抽卡getter
public getUserGachaParam() {
return this.player.getUserGachaParam()
}
public clear() {
this.floors = undefined;
this.percent = undefined;
this.player = undefined;
this.result = undefined;
}
}
/**
* 根据品质获得武将池
* @param quality 品质
*/
export function getAllHeroByQuality(quality: number) {
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 getPickHeroCnt(hid: number) {
let dicHero = gameData.hero.get(hid);
let cnt = gameData.gachaPickHeroCnt.get(dicHero?.quality)||0;
return cnt
}
function getPickHeroPiece(hid: number) {
let dicHero = gameData.hero.get(hid);
return dicHero?.pieceId??0
}
export function getDicGachaByGachaCnt(dicGacha: DicGacha, historyCount: number) {
if(isGachaCntIn(historyCount, dicGacha.gachaCnt.min, dicGacha.gachaCnt.max)) {
return dicGacha;
}
let gachaIds = gameData.gachaByType.get(dicGacha.gachaType);
for(let gachaId of gachaIds) {
let newDicGacha = gameData.gacha.get(gachaId);
if(isGachaCntIn(historyCount, newDicGacha.gachaCnt.min, newDicGacha.gachaCnt.max)) {
return {...newDicGacha, id: dicGacha.id};
}
}
return null
}
export function getDicGachas(gachaType: number, historyCount: number, count: number) {
let dics: { dic: DicGacha, min: number, max: number }[] = [];
let gachaIds = gameData.gachaByType.get(gachaType);
for(let gachaId of gachaIds) {
let dicGacha = gameData.gacha.get(gachaId);
if(!dicGacha || (dicGacha.gachaCnt.min > dicGacha.gachaCnt.max && dicGacha.gachaCnt.max != -1)) continue;
if((dicGacha.gachaCnt.max != -1 && dicGacha.gachaCnt.max <= historyCount) || dicGacha.gachaCnt.min > historyCount + count) continue; // dic的范围不包含
let min = dicGacha.gachaCnt.min > historyCount + 1? dicGacha.gachaCnt.min: historyCount + 1;
let max = (dicGacha.gachaCnt.max == -1 || dicGacha.gachaCnt.max > historyCount + count)? historyCount + count: dicGacha.gachaCnt.max;
dics.push({ dic: dicGacha, min, max });
}
return dics.sort((a, b) => a.min - b.min);
}
function isGachaCntIn(historyCount: number, min: number, max: number) {
let nowCnt = historyCount + 1;
return min <= nowCnt && (max >= nowCnt || max == -1);
}
export async function getGuideGachaData(serverId: number, activityId: number, roleId: string) {
let activityData = await getActivityById(activityId);
let createTime = await getRoleCreateTime(roleId);
let serverTime = await getServerCreateTime(serverId);
let playerData = new GuideGachaData(activityData, createTime, serverTime);
let userGacha = await UserGachaModel.findByRole(roleId, playerData.gachaId, activityId);
playerData.setPlayerData(userGacha);
if(playerData.hasChoosen || playerData.neverChoose()) return null;
return playerData;
}