抽卡:添加抽卡接口

This commit is contained in:
luying
2021-04-23 15:46:04 +08:00
parent 1f6839cafb
commit beadccf778
23 changed files with 591 additions and 85 deletions

View File

@@ -1,10 +1,17 @@
import { Application, BackendSession } from "pinus";
import { resResult } from "../../../pubUtils/util";
import { STATUS, GACHA_ID } from "../../../consts";
import { resResult, getRandomWithWeight } from "../../../pubUtils/util";
import { STATUS, GACHA_ID, GACHA_CONTENT_TYPE, GACHA_OCCUPY_HID } from "../../../consts";
import { gameData } from "../../../pubUtils/data";
import { GachaListReturn } from "../../../domain/activityField/gachaField";
import { GachaListReturn, Floor, Hope, GachaResult } from "../../../domain/activityField/gachaField";
import { UserGachaModel } from "../../../db/UserGacha";
import { refreshFreeCount } from "../../../services/gachaService";
import { refreshGacha, getFloorResult, getResultFromContentId, transPiece } from "../../../services/gachaService";
import { RoleModel } from "../../../db/Role";
import { HeroModel, HeroUpdate } from "../../../db/Hero";
import { RewardInter } from "../../../pubUtils/interface";
import { handleCost, createHeroes, addItems } from "../../../services/rewardService";
import { CounterModel } from "../../../db/Counter";
import { getNextTime, getAfterDateByDay } from "../../../pubUtils/timeUtil";
import { UserGachaRecModel } from "../../../db/UserGachaRec";
export default function (app: Application) {
@@ -31,7 +38,8 @@ export class GachaHandler {
if(id == GACHA_ID.TIMELIMIT) continue; // 不包括限时
let userGacha = userGachaList.find(cur => cur.gachaId == id);
userGacha = await refreshFreeCount(dicGacha, userGacha);
userGacha = await refreshGacha(dicGacha, userGacha);
console.log(JSON.stringify(userGacha))
let param = new GachaListReturn(dicGacha, userGacha);
list.push(param);
}
@@ -47,9 +55,81 @@ export class GachaHandler {
*/
async pull(msg: { gachaId: number, activityId: number, count: number }, session: BackendSession) {
const { gachaId, activityId, count } = msg;
if(gachaId == undefined|| activityId == undefined || count == undefined) return resResult(STATUS.WRONG_PARMS);
const roleId: string = session.get('roleId');
const roleName: string = session.get('roleName');
const sid: string = session.get('sid');
const serverId: number = session.get('serverId');
return resResult(STATUS.SUCCESS);
let { lv } = await RoleModel.findByRoleId(roleId);
let dicGacha = gameData.gacha.get(gachaId);
if(!dicGacha) return resResult(STATUS.DIC_DATA_NOT_FOUND);
if(!dicGacha.count.includes(count)) return resResult(STATUS.WRONG_PARMS);
let userGacha = await UserGachaModel.findByRole(roleId, gachaId, activityId);
let { floor, freeCount, hope, point, pickHero, refFreeTime } = await refreshGacha(dicGacha, userGacha);
if((gachaId == GACHA_ID.ASSIGN|| gachaId == GACHA_ID.TIMELIMIT) && pickHero) return resResult(STATUS.GACHA_NOT_ASSIGN);
let userHeroes = await HeroModel.findByRole(roleId);
let items: RewardInter[] = [], heroInfo: HeroUpdate[] = [], resultList: GachaResult[] = [];
for(let i = 0; i < count; i++) {
// 按照一般概率抽出
let { dic: { id: base } } = getRandomWithWeight(dicGacha.percent);
let contentId = getFloorResult(gachaId, base, floor);
if(contentId == false) return resResult(STATUS.DIC_DATA_NOT_FOUND);
let result = getResultFromContentId(contentId, lv, hope);
if(result.hid > 0) {
result.setSetPickHero(pickHero);
let hasHero = userHeroes.find(cur => cur.hid == result.hid);
if(hasHero) { // 已有转换为碎片
let { pieceId, count } = transPiece(result.hid);
result.transferToPiece(pieceId, count);
items.push({ id: pieceId, count });
} else {
let { heroId: hid, name: hName, initialStars: star, quality, jobid: job, initialSkin } = gameData.hero.get(result.hid);
heroInfo.push({ roleId, serverId, roleName, hid, hName, star, quality, job, skins:[{id: initialSkin, enable: true}] })
}
} else {
items.push({ id: result.id, count });
}
resultList.push(result);
}
console.log('***', dicGacha.free.count, dicGacha.free.day, freeCount, count);
let costNum = count;
if(dicGacha.free.count > 0) {
if(count > dicGacha.free.count - freeCount) {
costNum = count - dicGacha.free.count + freeCount;
freeCount = dicGacha.free.count;
} else {
costNum = 0;
freeCount += count;
}
}
// 消耗东西
if(costNum > 0) {
let cost = dicGacha.cost.map(cur => { return { id: cur.id, count: cur.count * costNum }});
let costResult = await handleCost(roleId, sid, cost);
if(!costResult) return resResult(STATUS.GACHA_COST_NOT_ENOUGH);
}
// 给东西
let heroes = await createHeroes(roleId, sid, serverId, heroInfo);
await addItems(roleId, roleName, sid, items);
// 更新数据
point += count;
userGacha = await UserGachaModel.updateInfo(roleId, gachaId, activityId, {
freeCount, hope, floor, count, point
});
await UserGachaRecModel.createRec(roleId, gachaId, activityId, count, resultList);
return resResult(STATUS.SUCCESS, {
gachaId, activityId,
freeCount, refFreeTime: getAfterDateByDay(refFreeTime, dicGacha.free.day), count, point, floor,
heroes, result: resultList
});
}
/**

View File

@@ -47,18 +47,18 @@ export class WishPoolHandler {
let goodInfo = getGoodById(goodId)
if (!goodInfo)
return resResult(STATUS.WRONG_PARMS);
if (!(goodInfo.goodType == IT_TYPE.HERO_PIECE && type == 2 ) && !(goodInfo.goodType == IT_TYPE.EQUIP_PIECE && type == 1 ))
if (!(goodInfo.itid == IT_TYPE.HERO_PIECE && type == 2 ) && !(goodInfo.itid == IT_TYPE.EQUIP_PIECE && type == 1 ))
return resResult(STATUS.WRONG_PARMS);
let userGuild = await getUserGuildWithRefActive(roleId, ' wishDntCnt wishGoods guildCode wishGoods');
if (!userGuild)
return resResult(STATUS.WRONG_PARMS);
let result = await checkGoods(roleId, [goodId]);
if (!result) {
if (goodInfo.goodType == IT_TYPE.HERO_PIECE ) {
if (goodInfo.itid == IT_TYPE.HERO_PIECE ) {
result = await checkGoods(roleId, [goodInfo.hid]);
if (!result)
return resResult(STATUS.GUILD_WISH_POOL_NOT_OWN_HERO);
} else if (goodInfo.goodType == IT_TYPE.EQUIP_PIECE ) {
} else if (goodInfo.itid == IT_TYPE.EQUIP_PIECE ) {
result = await checkGoods(roleId, [goodInfo.equipId]);
if (!result)
return resResult(STATUS.GUILD_WISH_POOL_NOT_OWN_EQUIP);

View File

@@ -200,8 +200,8 @@ export class RoleHandler {
let school = new Array<SclResultInter>();
gameData.school.forEach((dicSchool) => {
let position = new Array<SclPosInter>();
dicPosition.forEach((isOpen, id) => {
id = parseInt(id);
dicPosition.forEach((isOpen, dicId) => {
let id = parseInt(dicId);
let userSchool = userSchoolList.find(cur => cur.schoolId == dicSchool.id && cur.positionId == id);
if (userSchool) {

View File

@@ -5,7 +5,7 @@ import { HANG_UP_CONSTS, TOWER_TASK_CONST, REDIS_KEY, TASK_TYPE } from './../con
import { BattleRecordModel } from './../db/BattleRecord';
import { TowerRecordModel } from './../db/TowerRecord';
import { RoleModel } from './../db/Role';
import { getHeroInfoById, getJobInfoById, getTowerDataByLv, getTaskById, getGamedata, getRandExpedition, getWarById, getWarJsons } from "../pubUtils/gamedata"
import { getHeroInfoById, getJobInfoById, getTowerDataByLv, getTaskById, getGamedata, getWarJsons } from "../pubUtils/gamedata"
import { decodeArrayStr, shouldRefresh, resResult, decodeStr, cal, getRandomWithWeight, getRefTime, decodeStrSingle, genCode } from '../pubUtils/util';
import { STATUS } from '../consts/statusCode';
import { HangUpSpdUpRecModel } from '../db/HangUpSpdUpRec';
@@ -13,6 +13,7 @@ import { TowerTaskRecModel } from '../db/TowerTaskRec';
import { cloneDeep } from 'lodash';
import { Rank } from './rankService';
import { checkTask } from './taskService';
import { getRandExpedition } from '../pubUtils/data';
export async function checkTowerWar(roleId: string, battleId: number, heroes: Array<number>) {
const battleIdStr = `${battleId}`;

View File

@@ -432,7 +432,8 @@ export function randEquipPrintId(warInfo) {
if (!warInfo || !warInfo['jackpotReward']) {
return null;
}
const result = getRandomWithWeight(decodeStr('possibility', warInfo['jackpotReward']));
let jackpotReward: { id: number, weight: number}[] = decodeStr('possibility', warInfo['jackpotReward']);
const result = getRandomWithWeight(jackpotReward);
if (!result || !result.dic || !result.dic.id) {
return null;
}

View File

@@ -6,6 +6,7 @@ import { genCode, decodeStrSingle, decodeStr, getRandomWithWeight, resResult } f
import { EVENT_STATUS, EVENT_RECORD_STATUS, EVENT_TYPE, EVENT_RANDOM_TYPE_ONE_OPEN, EVENT_QUIZ_NUM, EVENT_ANSWER_STATUS, FUNCS_ID } from '../consts';
import { EVENT_REFRESH_NUM } from '../consts';
import { STATUS } from '../consts/statusCode';
import { gameData } from '../pubUtils/data';
/**
* 从检查接口调用检查是否有这么个战斗顺便保存一下battleCode
@@ -193,7 +194,7 @@ export async function refreshEvent(num: number, roleId: string, roleName: string
}).map(cur => cur.point);
let event = new Array();
let dicEvent = getGamedata('dic_zyz_event');
let dicEvent = gameData.eventList;
let role = await RoleModel.findByRoleId(roleId);
dicEvent = dicEvent.filter(cur => { // 筛选适合等级
let { suitLevel } = cur;
@@ -268,8 +269,7 @@ export async function refreshEvent(num: number, roleId: string, roleName: string
*
* @param positionStr
*/
function randomPosition(positionStr: string, prePoint: Array<number>, curPoint: Array<number>) {
let positionArr = decodeStr('point', positionStr, '&');
function randomPosition(positionArr: number[], prePoint: Array<number>, curPoint: Array<number>) {
let range = positionArr.filter(point => {return !prePoint.includes(point) && !curPoint.includes(point)});
if(range.length == 0) { // 如果位置总数不够就不管prePoint里的
range = positionArr.filter(point => {return !curPoint.includes(point)});

View File

@@ -1,10 +1,12 @@
import { GachaData, Floor } from "../domain/activityField/gachaField";;
import { GachaData, Floor, GachaResult, Hope } from "../domain/activityField/gachaField";;
import { ActivityModelType, ActivityModel } from "../db/Activity";
import { DicGacha } from "../pubUtils/dictionary/DicGacha";
import { UserGachaType, UserGachaModel } from "../db/UserGacha";
import { shouldRefresh } from "../pubUtils/util";
import { REFRESH_HOUR, GACHA_ID, GACHA_TO_FLOOR } from "../consts";
import { getNextDayByGap } from "../pubUtils/timeUtil";
import { shouldRefresh, getRandEelm, decodeArrayListStr, getRandomWithWeight, decodeIdCntArrayStr } from "../pubUtils/util";
import { REFRESH_HOUR, GACHA_ID, GACHA_TO_FLOOR, GACHA_FLOOR_TYPE, GACHA_CONTENT_TYPE, HERO_QUALITY_TYPE, GACHA_OCCUPY_HID, IT_TYPE, ITID, CONSUME_TYPE, SPECIAL_ATTR } from "../consts";
import { getNextDayByGap, getTodayZeroDate } from "../pubUtils/timeUtil";
import { gameData, getDicGachaFloor } from "../pubUtils/data";
import { RECRUIT } from "../pubUtils/dicParam";
/**
* 获取活动页签里的限时卡池
@@ -39,35 +41,269 @@ export async function getLimitGacha(activityId: number) {
* @param dicGacha
* @param userGacha
*/
export async function refreshFreeCount(dicGacha: DicGacha, userGacha: UserGachaType) {
export async function refreshGacha(dicGacha: DicGacha, userGacha: UserGachaType) {
let { day, count } = dicGacha.free;
if(count > 0) {
if(count <= 0) {
return userGacha;
}
let { roleId, gachaId, refFreeTime } = userGacha;
let { roleId, gachaId, refFreeTime, refHopeTime } = userGacha;
if(shouldRefresh(refFreeTime, new Date(), REFRESH_HOUR, day)) {
let ref = getNextDayByGap(refFreeTime, new Date(), day);
userGacha = await UserGachaModel.refreshFreeCount(roleId, gachaId, 0, ref);
}
if(shouldRefresh(refHopeTime, new Date(), REFRESH_HOUR)) {
let ref = getTodayZeroDate(REFRESH_HOUR);
userGacha = await UserGachaModel.refreshHopeCount(roleId, gachaId, 0, ref);
}
return userGacha
}
/**
* @description 获取保底状态
* @param type 招募类型
* @param gachaId 招募类型
* @param floor 玩家保底
*/
export function getFloorStatus(type: number, floor: Floor[]) {
export function getFloorStatus(gachaId: number, floor: Floor[]) {
let floorMap = new Map<number, number>();
for(let { id, count } of floor) {
floorMap.set(id, count);
}
let dicFloorType = GACHA_TO_FLOOR.get(type);
let dicFloorType = GACHA_TO_FLOOR.get(gachaId);
return dicFloorType.map(id => {
return {
id,
count: floorMap.get(id)||0
}
});
}
/**
* @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 (GACHA_FLOOR_TYPE.GOLD) {
return HERO_QUALITY_TYPE.GOLD;
} else if (GACHA_FLOOR_TYPE.ASSIGN) {
return 0
} else {
return false;
}
}
/**
* 根据contentId获得抽卡结果包括心愿单
* @param contentId dic_zyz_gachaContent的id
* @param lv 玩家等级
* @param hope 玩家心愿单
*/
export function getResultFromContentId(contentId: number, lv: number, hope: Hope[]) {
let dic = gameData.gachaContent.get(contentId);
let { type, param, count } = dic;
if(type == GACHA_CONTENT_TYPE.HERO) {
let pool: number[] = getPoolByHope(hope, param[0]);
let hero = getRandEelm(pool);
let result = new GachaResult(contentId);
result.setHero(hero[0]);
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);
return result;
}
}
/**
* 心愿
* @param hope 玩家数据里的心愿单
* @param qualtiy 武将品质
*/
function 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) {
hopeMap.set(h.id, h);
if(h.hasGet) hasGetHope.push(h.id);
}
let list = decodeArrayListStr(RECRUIT.RECRUIT_WISH_LIST).map(cur => {
let curHope = hopeMap.get(parseInt(cur[0]));
return { id: parseInt(cur[0]), hasGet: curHope?.hasGet||false, weight: parseInt(cur[1]) }
});
let { dic: { id, hasGet } } = getRandomWithWeight(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);
}
}
/**
* 根据品质获得武将池
* @param quality 品质
*/
function getAllHeroByQuality(quality: number) {
if(quality == 0) return [ GACHA_OCCUPY_HID ];
let allHero: number[] = [];
for(let [id, dicHero] of gameData.hero) {
if(dicHero.recruit && dicHero.quality == quality) {
allHero.push(id);
}
}
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;
}
export function transPiece(hid: number) {
let dicHero = gameData.hero.get(hid);
let { pieceId, quality } = dicHero;
let dicPiece = decodeIdCntArrayStr(RECRUIT.RECRUIT_CHANGE_SHARD, 1); // 多少品质对应多少碎片
return { pieceId, count: dicPiece.get(quality.toString()) }
}

View File

@@ -8,11 +8,11 @@ import { calAllHeroCe, pushCalPlayerCe, pushCalAllHeroCe } from './playerCeServi
import { ItemModel } from '../db/Item';
import { STATUS } from '../consts/statusCode';
import { pinus } from 'pinus';
import { addEquips, addBags, addSkins, addFigure, unlockFigure as pubUnlockFigure, createHero as pubCreateHero } from '../pubUtils/itemUtils';
import { addEquips, addBags, addSkins, addFigure, unlockFigure as pubUnlockFigure, createHero as pubCreateHero, createHeroes as pubCreateHeroes } from '../pubUtils/itemUtils';
import { EquipInter, ItemInter, BagInter } from '../pubUtils/interface';
import { gameData } from '../pubUtils/data';
import { uniq, indexOf, findIndex } from 'underscore';
import { HeroModel } from '../db/Hero';
import { HeroModel, HeroUpdate } from '../db/Hero';
import { Figure } from '../domain/dbGeneral';
import { Rank } from './rankService';
import { pushTaskUpdate } from './taskService';
@@ -304,7 +304,34 @@ export async function pushFigureUpdate(roleId: string, sid: string, figureInfo:
}
}
export async function createHero(roleId: string, sid: string, serverId: number, heroInfo) {
/**
* 创建多个武将
* @param roleId
* @param sid
* @param serverId
* @param heroInfo
*/
export async function createHeroes(roleId: string, sid: string, serverId: number, heroInfo: HeroUpdate[]) {
let { heroes, role, figureInfo, calHeroResults, calAllHeroResults, taskPushMessage } = await pubCreateHeroes(roleId, heroInfo);
let r = new Rank(REDIS_KEY.HERO_NUM_RANK, { serverId });
await r.setRankWithRoleInfo(roleId, role.heroNum, role.heroNumUpdatedAt, role);
await pushFigureUpdate(roleId, sid, figureInfo);
for(let calHeroResult of calHeroResults) {
await pushCalPlayerCe(roleId, sid, calHeroResult);
}
for(let calAllHeroResult of calAllHeroResults) {
await pushCalAllHeroCe(roleId, sid, calAllHeroResult);
}
pushTaskUpdate(roleId, sid, null, taskPushMessage);
return heroes;
}
export async function createHero(roleId: string, sid: string, serverId: number, heroInfo: HeroUpdate) {
let { hero, role, figureInfo, calHeroResult, calAllHeroResult, taskPushMessage } = await pubCreateHero(roleId, heroInfo);
let r = new Rank(REDIS_KEY.HERO_NUM_RANK, { serverId });
@@ -312,7 +339,7 @@ export async function createHero(roleId: string, sid: string, serverId: number,
await pushFigureUpdate(roleId, sid, figureInfo);
hero = await pushCalPlayerCe(roleId, sid, calHeroResult);
await pushCalPlayerCe(roleId, sid, calHeroResult);
await pushCalAllHeroCe(roleId, sid, calAllHeroResult);
pushTaskUpdate(roleId, sid, null, taskPushMessage);

View File

@@ -10,6 +10,7 @@ import { BATTLE_REWARD_TYPE, BLUEPRT_CONST } from '../consts';
import { addItems } from './rewardService';
import { BattleBlueprtDropModel } from '../db/BattleBlueprtDrop'
import { RoleModel } from '../db/Role';
import { gameData } from '../pubUtils/data';
export class WarReward {
private roleId: string;
@@ -169,13 +170,12 @@ export class WarReward {
private async randomBlueprt() {
const { lv } = await RoleModel.findByRoleId(this.roleId);
const dicPossibility = getGamedata('dic_blueprt_possibility');
const dicPossibility = gameData.blueprtPossibility;
const result = dicPossibility.find(cur => {return cur.min <= lv && cur.max >= lv});
if(result) {
const dicOdds = decodeStr('possibility', result.possibility);
const {dic: {id}} = getRandomWithWeight(dicOdds);
const {dic: {id}} = getRandomWithWeight(result.possibility);
const blueprtList = getBluePrtByQuality(id);
const gid = getRandomByLen(blueprtList);

View File

@@ -1,8 +1,8 @@
export const IT_TYPE = {
BLUEPRT: 28,
EQUIP_PIECE: 3,
EQUIP: 2,
HERO_PIECE: 7,
BLUEPRT: 28,
EQUIP_PIECE: 40,
HERO_PIECE: 25,
PAPER: 41,
}

View File

@@ -680,5 +680,12 @@ export const GACHA_TO_FLOOR = new Map([
// 抽卡里的卡池道具类型
export enum GACHA_CONTENT_TYPE {
HERO = 1, // 武将 param为武将品质
HERO_PIECE = 2, // 武将碎片 武将品质
BLUEPRT = 3, // 藏宝图 藏宝图品质
JEWEL = 4, // 宝石 宝石等级
TERAPH_MATERIAL = 5, // 强化神像用的材料 材料物品id
SUIT_PAPER = 6, // 套装图纸
}
}
export const GACHA_OCCUPY_HID = 99; // 抽卡里占位的武将

View File

@@ -315,13 +315,15 @@ export const STATUS = {
HERO_NOT_MAX: { code: 30904, simStr: '该武将未升满星' },
// 任务相关 31001-31100
TASK_NOT_REACH_CONDITION: { code: 30900, simStr: '任务不满足条件' },
TASK_HAS_RECEIVED: { code: 30901, simStr: '奖励已领取' },
TASK_NOT_ALL_RECEIVED: { code: 30902, simStr: '任务未领取完' },
TASK_ACTIVE_NOT_ENOUGH: { code: 30903, simStr: '活跃不足' },
TASK_POINT_NOT_ENOUGH: { code: 30904, simStr: '积分不足' },
TASK_BOX_HAS_RECEIVED: { code: 30905, simStr: '奖励已领取' },
TASK_NOT_REACH_CONDITION: { code: 31001, simStr: '任务不满足条件' },
TASK_HAS_RECEIVED: { code: 31002, simStr: '奖励已领取' },
TASK_NOT_ALL_RECEIVED: { code: 31003, simStr: '任务未领取完' },
TASK_ACTIVE_NOT_ENOUGH: { code: 31004, simStr: '活跃不足' },
TASK_POINT_NOT_ENOUGH: { code: 31005, simStr: '积分不足' },
TASK_BOX_HAS_RECEIVED: { code: 31006, simStr: '奖励已领取' },
// 抽卡相关 31101-31200
GACHA_COST_NOT_ENOUGH: { code: 31101, simStr: '招募券不足' },
GACHA_NOT_ASSIGN: { code: 31102, simStr: '请选择武将' },
// 社交相关状态 40000 - 49999
SYS_CHANNEL_AUTH_NOT_ENOUGH: { code: 40000, simStr: '无法在系统频道发送消息' },
UPDATE_PRIVATE_MSG_READ_TIME_ERR: { code: 40001, simStr: '更新私聊阅读时间失败' },

View File

@@ -1,9 +1,39 @@
import BaseModel from './BaseModel';
import { index, getModelForClass, prop, DocumentType, modelOptions } from '@typegoose/typegoose';
import { getCurWeekDate, getTodayZeroDate } from '../pubUtils/timeUtil';
import { Floor, Hope, Turntable } from '../domain/activityField/gachaField';
import { getTodayZeroDate } from '../pubUtils/timeUtil';
import { REFRESH_HOUR } from '../consts';
class Floor {
@prop({ required: true })
id: number; // 保底类型 1-紫将保底 2-金将保底 3-指定将保底
@prop({ required: true })
count: number; // 抽卡次数
}
/**
* @description 心愿单
* @memberof UserGacha
*/
class Hope {
@prop({ required: true })
id: number; // 位置
@prop({ required: true })
hid: number; // 武将id
@prop({ required: true })
hasGet: boolean; // 是否得到
}
/**
* @description 转盘记录
* @memberof UserGacha
*/
class Turntable {
@prop({ required: true })
quality: number; // 品质
@prop({ required: true })
hasGet: boolean; // 是否得到
}
/**
* 玩家抽卡表
**/
@@ -19,12 +49,12 @@ export default class UserGacha extends BaseModel {
gachaId: number; // 抽卡id 1-元宝 2-友情 3-指定 4-限时
@prop({ required: true, default: 0 })
aid: number; // 限时抽卡对应活动id
activityId: number; // 限时抽卡对应活动id
@prop({ required: true, default: 0 })
count: number; // 已抽卡次数
@prop({ required: true, type: (() => Floor)(), default: [] })
@prop({ required: true, type: Floor, default: [], _id: false })
floor: Floor[]; // 已抽卡次数
@prop({ required: true, default: 0 })
@@ -33,13 +63,16 @@ export default class UserGacha extends BaseModel {
@prop({ required: true, default: () => { return getTodayZeroDate(REFRESH_HOUR) } })
refFreeTime: Date; // 免费次数刷新时间
@prop({ required: true, type: (() => Hope)(), default: [] })
@prop({ required: true, type: () => Hope, default: [], _id: false })
hope: Hope[]; // 心愿单
@prop({ required: true, default: () => { return getTodayZeroDate(REFRESH_HOUR) } })
refHopeTime: Date; // 心愿单刷新时间
@prop({ required: true, default: 0 })
point: number; // 积分
@prop({ required: true, type: (() => Turntable)(), default: [] })
@prop({ required: true, type: Turntable, default: [], _id: false })
turntable: Turntable[]; // 转盘
@prop({ required: true, default: 0 })
@@ -50,13 +83,31 @@ export default class UserGacha extends BaseModel {
return rec;
}
public static async findByRole(roleId: string, gachaId: number, activityId: number = 0) {
const doc = new UserGachaModel();
const update = Object.assign(doc.toJSON(), { roleId, gachaId, activityId });
delete update._id;
let rec: UserGachaType = await UserGachaModel.findOneAndUpdate({ roleId, gachaId, activityId }, { $setOnInsert: update }, { new: true, upsert: true }).lean();
return rec;
}
public static async updateInfo(roleId: string, gachaId: number, activityId: number, update: UserGachaParam) {
let rec: UserGachaType = await UserGachaModel.findOneAndUpdate({ roleId, gachaId, activityId }, { $set: update }, { new: true }).lean();
return rec;
}
public static async refreshFreeCount(roleId: string, gachaId: number, aid: number, refFreeTime: Date) {
let rec: UserGachaType = await UserGachaModel.findOneAndUpdate({ roleId, gachaId, aid }, { $set: { freeCount: 0, refFreeTime } }, { new: true }).lean();
return rec;
}
public static async refreshHopeCount(roleId: string, gachaId: number, aid: number, refHopeTime: Date) {
let rec: UserGachaType = await UserGachaModel.findOneAndUpdate({ roleId, gachaId, aid }, { $set: { 'hope.$.hasGet': false, refHopeTime } }, { new: true }).lean();
return rec;
}
}
export const UserGachaModel = getModelForClass(UserGacha);
export interface UserGachaType extends Pick<DocumentType<UserGacha>, keyof UserGacha> { }
export type UserGachaParam = Partial<UserGachaType>;
export type UserGachaParam = Partial<UserGachaType>;

42
shared/db/UserGachaRec.ts Normal file
View File

@@ -0,0 +1,42 @@
import BaseModel from './BaseModel';
import { index, getModelForClass, prop, DocumentType, modelOptions } from '@typegoose/typegoose';
import { GachaResult } from '../domain/activityField/gachaField';
import { genCode } from '../pubUtils/util';
/**
* 玩家抽卡表
**/
@modelOptions({ schemaOptions: { id: false } })
@index({ code: 1 })
export default class UserGachaRec extends BaseModel {
@prop({ required: true })
code: string; // 玩家id
@prop({ required: true })
roleId: string; // 玩家id
@prop({ required: true })
gachaId: number; // 抽卡id 1-元宝 2-友情 3-指定 4-限时
@prop({ required: true, default: 0 })
activityId: number; // 限时抽卡对应活动id
@prop({ required: true, default: 0 })
count: number; // 抽卡次数
@prop({ required: true, type: GachaResult, default: [], _id: false })
result: GachaResult[]; // 结果
public static async createRec(roleId: string, gachaId: number, activityId: number, count: number, result: GachaResult[]) {
let code = genCode(8);
const rec = await UserGachaRecModel.findOneAndUpdate({ code }, { $set: { roleId, gachaId, activityId, count, result } }, { new: true, upsert: true });
return rec;
}
}
export const UserGachaRecModel = getModelForClass(UserGachaRec);
export interface UserGachaRecType extends Pick<DocumentType<UserGachaRec>, keyof UserGachaRec> { }
export type UserGachaRecParam = Partial<UserGachaRecType>;

View File

@@ -5,6 +5,7 @@ import { UserGachaType } from '../../db/UserGacha';
import { getSeconds } from '../../pubUtils/timeUtil';
import { DicGacha } from '../../pubUtils/dictionary/DicGacha';
import { getFloorStatus } from '../../services/gachaService';
import { GACHA_OCCUPY_HID } from '../../consts';
// 抽卡数据
@@ -76,7 +77,7 @@ export class GachaListReturn {
this.gachaId = dicGacha.id;
if(userGacha) {
this.freeCount = dicGacha.free.count - userGacha.freeCount;
this.freeCount = userGacha.freeCount;
this.refFreeTime = getSeconds(userGacha.refFreeTime);
this.count = userGacha.count;
this.floor = getFloorStatus(dicGacha.id, userGacha.floor);
@@ -88,4 +89,43 @@ export class GachaListReturn {
this.floor = getFloorStatus(dicGacha.id, []);
}
}
}
export class GachaResult {
@prop({ required: true })
contentId: number; // 抽卡内容id
@prop({ required: true })
hid: number = 0; // 武将id
@prop({ required: true })
isTransfer: boolean = false; // 是否转换为碎片
@prop({ required: true })
id: number = 0; // 道具id
@prop({ required: true })
count: number = 0; // 道具数量
constructor(contentId: number) {
this.contentId = contentId;
}
setSetPickHero(hid: number) {
if(hid > 0 && this.hid == GACHA_OCCUPY_HID) {
this.hid = hid;
}
}
setHero(hid: number) {
this.hid = hid;
this.count = 1;
}
setItem(id: number, count: number) {
this.id = id;
this.count = count;
}
transferToPiece(id: number, count: number) {
this.isTransfer = true;
this.id = id;
this.count = count;
}
}

View File

@@ -3,8 +3,8 @@ import { dicGoods, blueprt, dicJewel, figureCondition } from "./dictionary/DicGo
import { dicBlueprtCompose } from "./dictionary/DicBlueprtCompose";
import { dicBlueprtPossibility } from "./dictionary/DicBlueprtPossibility";
import { dicDaily } from "./dictionary/DicDaily";
import { dicEvent } from "./dictionary/DicEvent";
import { dicExpedition } from "./dictionary/DicExpedition";
import { dicEvent, dicEventList } from "./dictionary/DicEvent";
import { dicExpedition, DicExpedition } from "./dictionary/DicExpedition";
import { dicExpeditionPoint } from "./dictionary/DicExpeditionPoint";
import { dicFuncSwitch } from "./dictionary/DicFuncSwitch";
import { dicHeroSkill } from "./dictionary/DicHeroSkill";
@@ -66,8 +66,8 @@ import { dicCityActivity } from "./dictionary/DicCityActivity";
import { dicChatAccuse } from "./dictionary/DicChatAccuse";
import { dicCityActivityReward } from "./dictionary/DicCityActivityReward";
import { dicRaceActivity, dicRaceTypes } from './dictionary/DicRaceActivity';
import { GUILDACTIVITY } from "./dicParam";
import { decodeIdCntArrayStr, parseGoodStr, decodeArrayListStr, getRandValueByMinMax } from "./util";
import { GUILDACTIVITY, RECRUIT } from "./dicParam";
import { decodeIdCntArrayStr, parseGoodStr, decodeArrayListStr, getRandValueByMinMax, getRandEelm } from "./util";
import { RACE_EVENT_TYPE } from "../consts";
import { dicShop, dicShopItem } from "./dictionary/DicShop";
import { dicShopList } from "./dictionary/DicShopList";
@@ -77,13 +77,14 @@ import { dicTaskType, taskMap, dicMainTask, dicDailyTask, dicAchievement } from
import { dicMainTaskStage } from "./dictionary/DicMainTaskStage";
import { dicTaskBox } from './dictionary/DicTaskBox';
import { dicGacha } from "./dictionary/DicGacha";
import { dicGachaContent } from "./dictionary/DicGachaContent";
import { dicGachaContent, dicGachaContentHero } from "./dictionary/DicGachaContent";
export const gameData = {
blurprtCompose: dicBlueprtCompose,
blueprtPossibility: dicBlueprtPossibility,
daily: dicDaily,
event: dicEvent,
eventList: dicEventList,
expedition: dicExpedition,
expeditionPoint: dicExpeditionPoint,
funcsSwitch: dicFuncSwitch,
@@ -186,6 +187,7 @@ export const gameData = {
taskBox: dicTaskBox,
gacha: dicGacha,
gachaContent: dicGachaContent,
gachaContentHero: dicGachaContentHero
};
// 在此提供一些原先在gamedata中提供的方法以便更方便获取gameData数据
@@ -532,7 +534,7 @@ function decodeRaceActivityEncounter() {
let eventNum = 0;
for(let [key, value] of map) {
if(value == RACE_EVENT_TYPE.EVENT) eventNum ++;
newMap.set(parseInt(key), parseInt(value));
newMap.set(parseInt(key), value);
}
return { events: newMap, eventNum };
}
@@ -565,4 +567,19 @@ export function getRaceEventItems() {
result.push({ id, count });
}
return result;
}
// 根据保底类型获得保底数量
export function getDicGachaFloor(id: number) {
let map = decodeIdCntArrayStr(RECRUIT.RECRUIT_MUST, 1);
return map.get(id.toString())
}
export function getRandExpedition(cnt = 1) {
let arr: DicExpedition[] = [];
for(let [_id, dicExpedition] of gameData.expedition) {
arr.push(dicExpedition);
}
return getRandEelm(arr, cnt);
}

View File

@@ -36,12 +36,13 @@ const str = readJsonFile(FILENAME.DIC_EVENT);
let arr = JSON.parse(str);
export const dicEvent = new Map<number, DicEvent>();
export const dicEventList = new Array<DicEvent>();
arr.forEach(o => {
o.winReward = parseGoodStr(o.winReward);
o.loseReward = parseGoodStr(o.loseReward);
o.suitLevel = parseSuitLevel(o.suitLevel);
o.movePointArray = parseNumberList(o.movePointArray);
dicEventList.push(o);
dicEvent.set(o.eventID, o);
});

View File

@@ -12,13 +12,13 @@ export interface DicGacha {
// 消耗的招募券
readonly cost: RewardInter[];
// 概率
readonly percent: { id: number, percent: number }[];
readonly percent: { id: number, weight: number }[];
}
const str = readJsonFile(FILENAME.DIC_GACHA);
let arr = JSON.parse(str);
export const dicGacha = new Map<number, DicGacha>();
export const dicGacha = new Map<number, DicGacha>(); // id => dic
arr.forEach(o => {
o.count = parseNumberList(o.count);
o.free = parseFree(o.free);
@@ -41,14 +41,14 @@ function parseFree(str: string) {
}
function parsePercent(str: string) {
let result = new Array<{ id: number, percent: number }>();
let result = new Array<{ id: number, weight: number }>();
if (!str) return result;
let decodeArr = decodeArrayListStr(str);
for (let [id, percent] of decodeArr) {
if (isNaN(parseInt(id)) || isNaN(parseInt(percent))) {
for (let [id, weight] of decodeArr) {
if (isNaN(parseInt(id)) || isNaN(parseInt(weight))) {
throw new Error('data table format wrong');
}
result.push({ id: parseInt(id), percent: parseInt(percent) });
result.push({ id: parseInt(id), weight: parseInt(weight) });
}
return result
}

View File

@@ -1,5 +1,5 @@
import { readJsonFile, parseNumberList } from '../util'
import { FILENAME } from '../../consts'
import { FILENAME, GACHA_CONTENT_TYPE } from '../../consts'
export interface DicGachaContent {
// 内容id
@@ -15,9 +15,14 @@ export interface DicGachaContent {
const str = readJsonFile(FILENAME.DIC_GACHA_CONTENT);
let arr = JSON.parse(str);
export const dicGachaContent = new Map<number, DicGachaContent>();
export const dicGachaContent = new Map<number, DicGachaContent>(); // id => dic
export const dicGachaContentHero = new Map<number, number>(); // quality => dic
arr.forEach(o => {
o.param = parseNumberList(o.param);
if(o.type == GACHA_CONTENT_TYPE.HERO) {
dicGachaContentHero.set(o.param[0], o.id);
}
dicGachaContent.set(o.id, o);
});
arr = undefined;

View File

@@ -27,13 +27,15 @@ export interface DicHero {
readonly baseAbilityArr:Map<number, number>;
readonly baseAbilityUpArr:Map<number, number>;
readonly initialSkin: number;
// 是否可招募
readonly recruit: boolean;
}
const str = readJsonFile(FILENAME.DIC_HERO);
let arr = JSON.parse(str);
type KeysEnum<T> = { [P in keyof Required<T>]: true };
const DicHeroKeys: KeysEnum<DicHero> = {heroId: true, name: true, quality: true, camp: true, jobid: true, skill: true, pieceId: true, initialStars: true, pieceCount: true, baseAbilityArr: true, baseAbilityUpArr: true, initialSkin: true};
const DicHeroKeys: KeysEnum<DicHero> = {heroId: true, name: true, quality: true, camp: true, jobid: true, skill: true, pieceId: true, initialStars: true, pieceCount: true, baseAbilityArr: true, baseAbilityUpArr: true, initialSkin: true, recruit: true};
export const dicMyHeroes = new Array<number>();
export const dicHero = new Map<number, DicHero>();
arr.forEach(o => {
@@ -42,7 +44,7 @@ arr.forEach(o => {
}
o.baseAbilityArr = parseBaseAbilityArr(o);
o.baseAbilityUpArr = parseBaseAbilityUpArr(o);
o.recruit = parseInt(o.recruit) == 1;
dicHero.set(o.heroId, _.pick(o, Object.keys(DicHeroKeys)));
});

View File

@@ -330,12 +330,6 @@ export function getExpeditionById(id: number) {
return expeditionInfo.get(id);
}
export function getRandExpedition(cnt = 1) {
const file = 'dic_expedition';
const data = gamedata['jsons'][file] || [];
return getRandEelm(data, cnt);
}
export function getComBtlSetByQuality(quality: number) {
return comBtlInfo.get(quality);
}
@@ -366,7 +360,7 @@ export function getBossHpByWarId(warId: number) {
let { attribute, dataId, relation, actorId } = hero;
if (relation === 2) {
let attriData = decodeIdCntArrayStr(attribute, 1);
const hp = parseInt(attriData.get('1'));
const hp = attriData.get('1');
if (hp > 0) {
bossHpArr.push({dataId, hp, actorId});
bossHpSum += hp;
@@ -385,7 +379,7 @@ export function getWarIdByBlueprtId(blueprtId: number) {
if (!warId) {
const { specialAttr } = getGoodById(blueprtId);
const attrData = decodeIdCntArrayStr(specialAttr, 1);
warId = parseInt(attrData.get('1'));
warId = attrData.get('1');
blueprtToWar.set(blueprtId, warId);
}
return warId;

View File

@@ -1,6 +1,6 @@
import { HeroModel, HeroUpdate } from '../db/Hero';
import { HeroModel, HeroUpdate, HeroType } from '../db/Hero';
import { ItemModel } from '../db/Item';
import { EquipModel, RandSe, Holes } from './../db/Equip';
import { BagInter, EquipInter } from './interface';
@@ -231,7 +231,7 @@ export async function createHeroes(roleId: string, heroInfos: HeroUpdate[]) {
let heroNum = 0;
let skinIds = new Array<number>();
let conditions = new Array<{type: number, paramHid?: number, paramFavourLv?: number, paramSkinId?: number }>();
let heroes = [], calHeroResults = [], calAllHeroResults = [];
let heroes: HeroType[] = [], calHeroResults = [], calAllHeroResults = [];
for(let heroInfo of heroInfos) {
let curHero = await HeroModel.createHero(heroInfo); heroes.push(curHero);

View File

@@ -189,11 +189,11 @@ export function decodeArrayListStr(str: string) {
*/
export function decodeIdCntArrayStr(str: string, multi: number) {
const strArr = decodeArrayStr(str);
console.log('decodeIdCntArrayStr: ', strArr);
const strMap = new Map();
// console.log('decodeIdCntArrayStr: ', strArr);
const strMap = new Map<string, number>();
strArr.forEach(item => {
const kv = item.split('&');
strMap.set(kv[0], multi ? Math.ceil(parseInt(kv[1]) * multi) : kv[1]);
strMap.set(kv[0], multi ? Math.ceil(parseInt(kv[1]) * multi) : parseInt(kv[1]));
});
return strMap;
}
@@ -219,7 +219,7 @@ export function getRandomIndexByLen(len: number) {
return Math.floor(Math.random() * len);
}
export function getRandomWithWeight(randomList: any) {
export function getRandomWithWeight<T extends { weight: number }>(randomList: T[]): { dic: T, index: number } {
let len = randomList.reduce((pre, cur) => {
return pre + cur.weight || 1;
}, 0);
@@ -312,7 +312,7 @@ export function getRefTime(now = new Date(), checkHour: number, day = 0) {
* @param source 原数组
* @param cnt 返回随机元素个数
*/
export function getRandEelm(source: Array<any> = [], cnt = 1): Array<any> {
export function getRandEelm<T>(source: Array<T> = [], cnt = 1): Array<T> {
if (cnt > source.length || cnt == 0) return [];
if (cnt === source.length) return source;
let idxs = new Set();