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"; /** * 获取招募列表 * @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 = 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; constructor(userGacha: UserGachaType ) { if(userGacha) { this.floor = userGacha.floor; this.hope = userGacha.hope; this.pickHero = userGacha.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, target.length - 2); 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)||[] 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; 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; } } isReplaceLower() { return [ GACHA_FLOOR_TYPE.MAIN_FLOOR, GACHA_FLOOR_TYPE.ONLY_ONE, GACHA_FLOOR_TYPE.TEN_MOST_LOWER, ].indexOf(this.floorType) != -1 } } /** * 抽卡方法 */ export class GachaPull { private gachaType: GACHA_TYPE; // 抽卡类型 // 字典 private floors: GachaFloor[] = []; // 保底次数 private percent: GachaPercent; // 玩家数据 private player: PlayerGachaRecord; // 结果 private result = new GachaResults(); constructor(dicGacha: DicGacha, userGacha?: UserGachaType) { this.gachaType = dicGacha.gachaType; this.percent = new GachaPercent(dicGacha.percent); this.player = new PlayerGachaRecord(userGacha); 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: 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 == 10 && 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 } 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; }