From 04cecc2d5da7a7d4e7e20055717236f8b73c6771 Mon Sep 17 00:00:00 2001 From: luying Date: Thu, 16 Mar 2023 17:25:31 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(=E6=B4=BB=E5=8A=A8):=20?= =?UTF-8?q?=E8=8A=82=E6=97=A5=E6=B4=BB=E5=8A=A8-=E7=81=AB=E7=A5=9E?= =?UTF-8?q?=E7=A5=AD=E7=A5=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../servers/activity/handler/forgeHandler.ts | 117 +++++++++++++++ .../activity/handler/refreshShopHandler.ts | 2 +- .../activity/handler/refreshTaskHandler.ts | 4 +- .../servers/activity/handler/signInHandler.ts | 2 +- .../app/services/activity/activityService.ts | 6 + .../app/services/activity/forgeService.ts | 42 ++++++ .../services/activity/giftPackageService.ts | 13 +- game-server/app/services/checkParam.ts | 17 +++ game-server/app/services/connectorService.ts | 5 +- .../app/services/role/rewardService.ts | 42 +++++- game-server/app/services/role/util.ts | 10 +- shared/consts/constModules/activityConst.ts | 2 + shared/consts/constModules/chatConst.ts | 1 + shared/consts/constModules/itemConst.ts | 2 + shared/consts/constModules/selectConst.ts | 6 +- shared/consts/constModules/sysConst.ts | 2 + shared/consts/statusCode.ts | 5 + shared/db/ActivityForge.ts | 65 +++++++++ shared/db/ActivityItem.ts | 85 +++++++++++ shared/domain/activityField/forgeField.ts | 134 ++++++++++++++++++ .../domain/activityField/refreshShopField.ts | 3 + shared/domain/activityField/rewardField.ts | 1 + shared/pubUtils/interface.ts | 2 + .../resource/jsons/dic_zyz_activityType.json | 12 ++ 24 files changed, 566 insertions(+), 14 deletions(-) create mode 100644 game-server/app/servers/activity/handler/forgeHandler.ts create mode 100644 game-server/app/services/activity/forgeService.ts create mode 100644 shared/db/ActivityForge.ts create mode 100644 shared/db/ActivityItem.ts create mode 100644 shared/domain/activityField/forgeField.ts diff --git a/game-server/app/servers/activity/handler/forgeHandler.ts b/game-server/app/servers/activity/handler/forgeHandler.ts new file mode 100644 index 000000000..a6dab3dd0 --- /dev/null +++ b/game-server/app/servers/activity/handler/forgeHandler.ts @@ -0,0 +1,117 @@ +import { Application, BackendSession, HandlerService, } from 'pinus'; +import { resResult } from '../../../pubUtils/util'; +import { FIRST_GIFT_STATE, ITEM_CHANGE_REASON, STATUS } from '../../../consts'; +import { addReward, stringToConsumeParam, stringToRewardParam } from '../../../services/activity/giftPackageService'; +import { getMetialStr, getPlayerForgeData, getPlayerForgeDataShow } from '../../../services/activity/forgeService'; +import { ActivityForgeModel } from '../../../db/ActivityForge'; +import { handleCost } from '../../../services/role/rewardService'; + +export default function (app: Application) { + new HandlerService(app, {}); + return new ForgeHandler(app); +} + +export class ForgeHandler { + constructor(private app: Application) { + } + + /** + * @description 获取火神祭祀活动数据 + * @param {{ activityId: number}} msg + * @param {BackendSession} session + * @memberof ForgeHandler + */ + async getForgeActivity(msg: { activityId: number }, session: BackendSession) { + const { activityId } = msg; + const roleId = session.get('roleId'); + const serverId = session.get('serverId'); + + let playerData = await getPlayerForgeDataShow(activityId, serverId, roleId); + + if (!playerData) return resResult(STATUS.ACTIVITY_MISSING); + + return resResult(STATUS.SUCCESS, playerData); + } + + /** + * @description 铸造 + * @param {{ activityId: number, id: number, material: {id: number, count: number}[]}} msg + * @param {BackendSession} session + * @memberof ForgeHandler + */ + async build(msg: { activityId: number, id: number, material: {id: number, count: number}[] }, session: BackendSession) { + const { activityId, id, material } = msg; + const roleId = session.get('roleId'); + const roleName = session.get('roleName'); + const sid = session.get('sid'); + const serverId = session.get('serverId'); + + let playerData = await getPlayerForgeData(activityId, serverId, roleId); + if (!playerData) return resResult(STATUS.ACTIVITY_MISSING); + + let materialCnt = material.reduce((pre, cur) => pre + cur.count, 0); + if(materialCnt <= 0) return resResult(STATUS.ACTIVITY_MATERIAL_COUNT_NOT_ZERO); + + let manual = playerData.findManual(id); + if(!manual) return resResult(STATUS.ACTIVITY_MANUAL_NOT_FOUND); + // 解锁日期 + if(manual.dayIndex > playerData.todayIndex) return resResult(STATUS.ACTIVITY_MANUAL_DAY_LOCK); + + // 铸造次数 + if(manual.buildCnt >= manual.freeCnt + manual.buyCnt) return resResult(STATUS.ACTIVITY_BUILD_COUNT); + // 配比是否正确 + let isSuccess = manual.checkMaterial(material); + // 扣材料 + let costResult = await handleCost(roleId, sid, material, ITEM_CHANGE_REASON.ACT_FORGE_BUILD); + if(!costResult) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH); + // 保存数据 + let buildResult = await ActivityForgeModel.build(serverId, activityId, roleId, playerData.roundIndex, id, { todayIndex: playerData.todayIndex, isSuccess, material: getMetialStr(material) }); + // 更新数据 + manual.setPlayerData(buildResult, playerData.todayIndex); + let activityGoods = undefined; + if(isSuccess) { + let { goods } = await addReward(roleId, roleName, sid, serverId, stringToRewardParam(manual.reward), ITEM_CHANGE_REASON.ACT_FORGE_BUILD); + activityGoods = goods; + } + + return resResult(STATUS.SUCCESS, { + isSuccess, + curManual: manual, + activityGoods + }); + } + + /** + * @description 购买次数 + * @param {{ activityId: number, id: number, count: number}} msg + * @param {BackendSession} session + * @memberof ForgeHandler + */ + async buyCnt(msg: { activityId: number, id: number, count: number }, session: BackendSession) { + const { activityId, id, count } = msg; + const roleId = session.get('roleId'); + const roleName = session.get('roleName'); + const sid = session.get('sid'); + const serverId = session.get('serverId'); + + let playerData = await getPlayerForgeData(activityId, serverId, roleId); + if (!playerData) return resResult(STATUS.ACTIVITY_MISSING); + + let manual = playerData.findManual(id); + if(!manual) return resResult(STATUS.ACTIVITY_MANUAL_NOT_FOUND); + // 解锁日期 + if(manual.buyCnt >= manual.maxBuyCnt) return resResult(STATUS.ACTIVITY_BUY_CNT_MAX); + // 扣材料 + let costResult = await handleCost(roleId, sid, stringToConsumeParam(playerData.consume), ITEM_CHANGE_REASON.ACT_FORGE_BUILD); + if(!costResult) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH); + + // 保存数据 + let buildResult = await ActivityForgeModel.buyCnt(serverId, activityId, roleId, playerData.roundIndex, id, count); + // 更新数据 + manual.setPlayerData(buildResult, playerData.todayIndex); + + return resResult(STATUS.SUCCESS, { + curManual: manual + }); + } +} diff --git a/game-server/app/servers/activity/handler/refreshShopHandler.ts b/game-server/app/servers/activity/handler/refreshShopHandler.ts index e659b8734..8771bd27d 100644 --- a/game-server/app/servers/activity/handler/refreshShopHandler.ts +++ b/game-server/app/servers/activity/handler/refreshShopHandler.ts @@ -76,7 +76,7 @@ export class RefreshShopHandler { let consumeResult = await handleCost(roleId, sid, consume, ITEM_CHANGE_REASON.BUY_REFRESH_SHOP); if (!consumeResult) return resResult(STATUS.ROLE_MATERIAL_NOT_ENOUGH); - let rewardArray = stringToRewardParam(item.reward).map(cur => ({...cur, count: cur.count * count })); + let rewardArray = stringToRewardParam(item.reward, playerData.nextRefreshTime).map(cur => ({...cur, count: cur.count * count })); let result = await addReward(roleId, roleName, sid, serverId, rewardArray, ITEM_CHANGE_REASON.BUY_REFRESH_SHOP); await ActivityRefreshShopModel.addRecord(activityId, roleId, roundIndex, pageIndex, id, count); diff --git a/game-server/app/servers/activity/handler/refreshTaskHandler.ts b/game-server/app/servers/activity/handler/refreshTaskHandler.ts index 4e9ac3d89..fb5a7b417 100644 --- a/game-server/app/servers/activity/handler/refreshTaskHandler.ts +++ b/game-server/app/servers/activity/handler/refreshTaskHandler.ts @@ -70,7 +70,7 @@ export class RefreshTaskHandler { await ActivityRefreshTaskModel.addReceiveRecord(serverId, activityId, roleId, roundIndex, pageIndex, id, dailyItemData.getRefTime(), 1); - let rewardParamArr: Array = stringToRewardParam(dailyItemData.reward); + let rewardParamArr: Array = stringToRewardParam(dailyItemData.reward, playerData.nextRefreshTime); let result = await addReward(roleId, roleName, sid, serverId, rewardParamArr, ITEM_CHANGE_REASON.REFRESH_TASK_REWARD) //获得点数 @@ -110,7 +110,7 @@ export class RefreshTaskHandler { await ActivityRefreshTaskPointModel.addReceiveRecord(serverId, activityId, roleId, playerData.consumePoint); - let rewardParamArr: Array = stringToRewardParam(playerData.reward); + let rewardParamArr: Array = stringToRewardParam(playerData.reward, playerData.nextRefreshTime); let result = await addReward(roleId, roleName, sid, serverId, rewardParamArr, ITEM_CHANGE_REASON.REFRESH_TASK_EXCHANGE) playerData.exchangePoint += playerData.consumePoint; diff --git a/game-server/app/servers/activity/handler/signInHandler.ts b/game-server/app/servers/activity/handler/signInHandler.ts index 208e833f5..3d8c0aa89 100644 --- a/game-server/app/servers/activity/handler/signInHandler.ts +++ b/game-server/app/servers/activity/handler/signInHandler.ts @@ -101,7 +101,7 @@ export class SignInHandler { await ActivitySignInModel.addSignInRecord(activityId, roleId, roundIndex, dayIndex); - let rewardParamArr: Array = stringToRewardParam(signinItemData.reward); + let rewardParamArr: Array = stringToRewardParam(signinItemData.reward, playerData.nextRefreshTime); let result = await addReward(roleId, roleName, sid, serverId, rewardParamArr, ITEM_CHANGE_REASON.SIGNIN) return resResult(STATUS.SUCCESS, Object.assign(result, { diff --git a/game-server/app/services/activity/activityService.ts b/game-server/app/services/activity/activityService.ts index 660b8a9c5..7953a650d 100644 --- a/game-server/app/services/activity/activityService.ts +++ b/game-server/app/services/activity/activityService.ts @@ -41,6 +41,7 @@ import { getPopNoticeData } from './popNoticeService'; import { _getActivities, _getActivitiesByServerId, _getActivitiesByType, _getActivityById } from './activityRemoteService'; import { getGroupShopDataShow } from './groupShopService'; import { getBindPhoneDataShow } from './bindPhoneService'; +import { getPlayerForgeDataShow } from './forgeService'; /** * 获取活动数据 @@ -232,6 +233,11 @@ export async function getActivity(serverId: number, roleId: string, uid: number, activityData = await getBindPhoneDataShow(activityId, roleId, serverId, uid); break } + case ACTIVITY_TYPE.FORGE: + { + activityData = await getPlayerForgeDataShow(activityId, serverId, roleId); + break + } default: { console.log('未知活动类型.........', activityType) break; diff --git a/game-server/app/services/activity/forgeService.ts b/game-server/app/services/activity/forgeService.ts new file mode 100644 index 000000000..4707d5997 --- /dev/null +++ b/game-server/app/services/activity/forgeService.ts @@ -0,0 +1,42 @@ +import { ActivityForgeModel } from "../../db/ActivityForge"; +import { ForgeData } from "../../domain/activityField/forgeField"; +import { getRoleCreateTime, getServerCreateTime } from "../redisService"; +import { getActivityById } from "./activityService"; + +/** + * 玩家活动数据 + * + * @param {number} serverId 区Id + * @param {number} activityId 活动Id + * @param {string} roleId 角色Id + * + */ +export async function getPlayerForgeData(activityId: number, serverId: number, roleId: string) { + let activityData = await getActivityById(activityId); + let createTime = await getRoleCreateTime(roleId); + let serverTime = await getServerCreateTime(serverId); + let playerData = new ForgeData(activityData, createTime, serverTime); + let playerRecords = await ActivityForgeModel.findData(serverId, activityId, playerData.roundIndex, roleId); + playerData.setPlayerRecords(playerRecords); + return playerData; +} + +/** + * 玩家活动数据 + * + * @param {number} serverId 区Id + * @param {number} activityId 活动Id + * @param {string} roleId 角色Id + * + */ +export async function getPlayerForgeDataShow(activityId: number, serverId: number, roleId: string) { + let playerData = await getPlayerForgeData(activityId, serverId, roleId); + if(playerData && playerData.canShow && playerData.canShow()) { + return playerData.getShowResult(); + } + return null +} + +export function getMetialStr(material: { id: number, count: number }[]) { + return material.map(({id, count}) => `${id}&${count}`).join('|') +} \ No newline at end of file diff --git a/game-server/app/services/activity/giftPackageService.ts b/game-server/app/services/activity/giftPackageService.ts index 0a33f7806..35ce47e13 100644 --- a/game-server/app/services/activity/giftPackageService.ts +++ b/game-server/app/services/activity/giftPackageService.ts @@ -14,6 +14,7 @@ import { recordGuildFund } from './timeLimitRankService'; import { filterGoods, isGoodsHidden, isHeroHidden } from '../dataService'; import { DicGiftPackagePlan } from '../../pubUtils/dictionary/DicGiftPackagePlan'; import { Floor, GiftPackageFloorModel } from '../../db/GiftPackageFloor'; +import { isNumber } from 'underscore'; @@ -127,7 +128,7 @@ export function rewardItemData(reward: Array) { heroes.push({ hid: obj.id, count: obj.count }) break; case ACTIVITY_RESOURCES_TYPE.GOODS: - goods.push({ id: obj.id, count: obj.count }) + goods.push({ id: obj.id, count: obj.count, expireTime: obj.expireTime }) break; case ACTIVITY_RESOURCES_TYPE.GIFTPACKAGE://配置成礼包会立刻兑换,配置成物品会把礼包放入背包中 let goodData = gameData.goods.get(obj.id);//礼包物品 @@ -274,15 +275,19 @@ async function randomSelectedData(pool: DicGiftPackagePlan[], roleId: string, gi } //数据格式转换'类型&id&数量|类型&id&数量|' ->> Array 活动奖励 -export function stringToRewardParam(rewardStr: string): Array { - let result = new Array<{ type: number, id: number, count: number }>(); +export function stringToRewardParam(rewardStr: string, expireTime?: number): Array { + let result = new Array<{ type: number, id: number, count: number, expireTime?: number }>(); if (!rewardStr) return result; let decodeArr = decodeArrayListStr(rewardStr); for (let [type, id, count] of decodeArr) { if (isNaN(parseInt(type)) || isNaN(parseInt(id)) || isNaN(parseInt(count))) { continue; } - result.push({ type: parseInt(type), id: parseInt(id), count: parseInt(count) }); + if(expireTime && isNumber(expireTime)) { + result.push({ type: parseInt(type), id: parseInt(id), count: parseInt(count), expireTime: Math.floor(expireTime/1000) }); + } else { + result.push({ type: parseInt(type), id: parseInt(id), count: parseInt(count) }); + } } return result } diff --git a/game-server/app/services/checkParam.ts b/game-server/app/services/checkParam.ts index 9feb1b477..a5e4782f6 100644 --- a/game-server/app/services/checkParam.ts +++ b/game-server/app/services/checkParam.ts @@ -119,6 +119,7 @@ export function checkRouteParam(route: string, msg: any) { case 'activity.yuanbaoShopHandler.getShopActivity': case 'activity.groupShopHandler.getGroupShopPage': case 'activity.groupShopHandler.leaveGroupShopPage': + case 'activity.forgeHandler.getForgeActivity': { if(!checkNaturalNumbers(msg.activityId)) return false; break; @@ -425,6 +426,22 @@ export function checkRouteParam(route: string, msg: any) { if(!checkNaturalNumbers(activityId, price, itemId, buyCnt)) return false; break; } + case 'activity.forgeHandler.build': + { + let { activityId, id, material } = msg; + if(!checkNaturalNumbers(activityId, id)) return false; + if(!checkNaturalArray(material)) return false; + for(let { id, count } of material) { + if(!checkNaturalNumbers(id, count)) return false + } + break; + } + case 'activity.forgeHandler.buyCnt': + { + let { activityId, id, count } = msg; + if(!checkNaturalNumbers(activityId, id, count)) return false; + break; + } case "battle.barrageHandler.getBarrageList": { if(!checkNaturalStrings(msg.rid)) return false; diff --git a/game-server/app/services/connectorService.ts b/game-server/app/services/connectorService.ts index db06b6d24..fcf692dab 100644 --- a/game-server/app/services/connectorService.ts +++ b/game-server/app/services/connectorService.ts @@ -8,7 +8,7 @@ import { getCurTask, getPvpTask } from './task/taskService'; import { RoleType } from '../db/Role'; import { Application, FrontendOrBackendSession, pinus, RpcClient } from 'pinus'; import { getRandEelmWithWeight, resResult } from '../pubUtils/util'; -import { STATUS, PUSH_BATCH, PUSH_INTERVAL, CONSUME_TYPE, HERO_SELECT, ENTERY_ROLE_PICK, JEWEL_SELECT, ITEM_SELECT, SKIN_SELECT, PUSH_ROUTE, ARTIFACT_SELECT } from '../consts'; +import { STATUS, PUSH_BATCH, PUSH_INTERVAL, CONSUME_TYPE, HERO_SELECT, ENTERY_ROLE_PICK, JEWEL_SELECT, ITEM_SELECT, SKIN_SELECT, PUSH_ROUTE, ARTIFACT_SELECT, ACTIVITYITEM_SELECT } from '../consts'; import { getAllShopList } from './shopService'; import { getGeneralRank, getRankFirstReward } from './rankService'; import { getFriendList, getApplyList } from './friendService'; @@ -51,6 +51,7 @@ import { dispatch } from '../pubUtils/dispatcher'; import { PvpDataReturn } from '../domain/battleField/pvp'; import { getHiddenData } from './dataService'; import { ArtifactModel } from '../db/Artifact'; +import { ActivityItemModel } from '../db/ActivityItem'; /** * init: 初始的时候是否推送 true-推 false-不推 @@ -125,6 +126,7 @@ export async function getModuleData(type: string, data: { role: RoleType, sessio let items = await ItemModel.findbyRole(role.roleId, ITEM_SELECT.ENTRY); let skins = await SkinModel.findbyRole(role.roleId, SKIN_SELECT.ENTRY); let artifacts = await ArtifactModel.findbyRole(role.roleId, ARTIFACT_SELECT.ENTRY); + let activityItems = await ActivityItemModel.findbyRole(role.roleId, ACTIVITYITEM_SELECT.ENTRY); role['heros'] = heros.map(hero => new HeroParam(hero)); role['jewels'] = jewels; @@ -134,6 +136,7 @@ export async function getModuleData(type: string, data: { role: RoleType, sessio role['apJson'] = apJson; role['ipLocation'] = role.fixedIpLocation||role.ipLocation||'未知'; role['artifacts'] = artifacts; + role['activityItems'] = activityItems; if (!role.showLineup) role.showLineup = role.topLineup.map(cur => cur.hid); role.heads = role.heads.filter(cur => cur.status); diff --git a/game-server/app/services/role/rewardService.ts b/game-server/app/services/role/rewardService.ts index d031188ea..04a07449d 100644 --- a/game-server/app/services/role/rewardService.ts +++ b/game-server/app/services/role/rewardService.ts @@ -23,10 +23,11 @@ import { calculateCeWithHero, calculateCeWithRole } from '../playerCeService'; import { sendMessageToUserWithSuc } from '../pushService'; import { filterGoods } from '../dataService'; import { ArtifactModel, ArtifactModelType, ArtifactModelUpdate } from '../../db/Artifact'; +import { ActivityItemModel } from '../../db/ActivityItem'; export async function handleCost(roleId: string, sid: string, goods: Array, reason: ITEM_CHANGE_REASON) { - let { items, jewels, gold, coin, artifacts } = sortItems(goods, HANDLE_REWARD_TYPE.COST); + let { items, jewels, gold, coin, artifacts, activityItems } = sortItems(goods, HANDLE_REWARD_TYPE.COST); let jewelSeqIds = jewels.map(cur => cur.seqId); let resJewels: JewelType[] = []; let artifactSeqIds = artifacts.map(cur => cur.seqId); @@ -59,6 +60,13 @@ export async function handleCost(roleId: string, sid: string, goods: Array ({...cur, reason })) }, sid); saveItemChangeLog(roleId, result, reason); } + //检查并修改道具 + if (activityItems.length > 0) { + let { hasError, result } = await ActivityItemModel.decreaseActivityItems(roleId, activityItems); + if (hasError) return false; + sendMessageToUserWithSuc(roleId, PUSH_ROUTE.ACTIVITY_ITEM_UPDATE, { goods: result.map(cur => ({...cur, reason })) }, sid); + saveItemChangeLog(roleId, result, reason); + } //删除装备 if (resJewels.length > 0) { @@ -136,8 +144,8 @@ export async function handleCost(roleId: string, sid: string, goods: Array, reason: ITEM_CHANGE_REASON) { goods = filterGoods(goods, obj => obj.id, roleId, reason); - let { items, jewels, gold, coin, ap, skins, figures, artifacts } = sortItems(goods, HANDLE_REWARD_TYPE.RECEIVE); - let showItems: { id: number, seqId?: number, count: number, isBag?: boolean }[] = []; + let { items, jewels, gold, coin, ap, skins, figures, artifacts, activityItems } = sortItems(goods, HANDLE_REWARD_TYPE.RECEIVE); + let showItems: { id: number, seqId?: number, count: number, isBag?: boolean, expireTime?: number }[] = []; let role = await RoleModel.findByRoleId(roleId); // 1. 装备处理 if(jewels.length > 0) { @@ -283,6 +291,19 @@ export async function addItems(roleId: string, roleName: string, sid: string, go } } + // 7. 活动道具处理 + if(activityItems.length > 0) { + let { items: itemInfos } = await addActivityItems(roleId, roleName, activityItems, reason); + for (let item of activityItems) { + showItems.push({ id: item.id, count: item.count, expireTime: item.expireTime }); + } + //背包除去装备推送 + if (!!itemInfos.length) { + sendMessageToUserWithSuc(roleId, PUSH_ROUTE.ACTIVITY_ITEM_UPDATE, { goods: itemInfos }, sid); + saveItemChangeLog(roleId, itemInfos, reason); + } + + } return showItems; } @@ -439,6 +460,21 @@ export async function addBag(roleId: string, roleName: string, data: { id: numbe return { id: item.id, count: item.count, inc: count, reason }; } +export async function addActivityItems(roleId: string, roleName: string, datas: { id: number, count: number, expireTime?: number }[], reason: number) { + let items: { id: number, count: number, inc: number, expireTime: number }[] = []; + for(let data of datas) { + let item = await addActivityItem(roleId, roleName, data, reason); + items.push(item) + } + return { items } +} + +export async function addActivityItem(roleId: string, roleName: string, data: { id: number, count: number, expireTime?: number }, reason: number) { + let { id, count, expireTime } = data; + let { name: itemName, itid } = gameData.goods.get(id); + let item = await ActivityItemModel.increaseActivityItem(roleId, id, count, { roleId, roleName, itemName, id, expireTime }); + return { id: item.id, count: item.count, inc: count, expireTime: item.expireTime, reason }; +} export async function addJewels(roleId: string, roleName: string, jewels: { id: number, }[], reason: number) { let jewelInfo: jewelUpdate[] = []; diff --git a/game-server/app/services/role/util.ts b/game-server/app/services/role/util.ts index 866fc5206..814a83700 100644 --- a/game-server/app/services/role/util.ts +++ b/game-server/app/services/role/util.ts @@ -12,6 +12,7 @@ export function sortItems(goods: ItemInter[], handleType: HANDLE_REWARD_TYPE) { let ap: number = 0; let skins: number[] = []; let figures: number[] = []; + let activityItems: { id: number, count: number, expireTime?: number }[] = []; // 可叠加道具 for(let good of goods) { if(good.count == 0) continue; @@ -85,11 +86,18 @@ export function sortItems(goods: ItemInter[], handleType: HANDLE_REWARD_TYPE) { artifacts.push({ seqId: good.seqId }); } } + } else if (table == ITEM_TABLE.ACTIVITY_ITEM) { // 活动道具,限时,可堆叠 + let index = activityItems.findIndex(cur => cur.id == good.id); + if(index >= 0) { + activityItems[index].count += good.count; + } else { + activityItems.push({ id: good.id, count: good.count, expireTime: good.expireTime }); + } } } - return { items, jewels, gold, coin, ap, skins, figures, artifacts } + return { items, jewels, gold, coin, ap, skins, figures, artifacts, activityItems } } export function getGoldEventProperties(inc: number, count: number, reason: ITEM_CHANGE_REASON) { diff --git a/shared/consts/constModules/activityConst.ts b/shared/consts/constModules/activityConst.ts index 515443e74..0051c2aba 100644 --- a/shared/consts/constModules/activityConst.ts +++ b/shared/consts/constModules/activityConst.ts @@ -59,6 +59,8 @@ export enum ACTIVITY_TYPE { SHOP = 44, // 限时商店 GUIDE_GACHA = 45, // 500抽 POP_NOTICE = 46, // 打脸公告 + MINI_GAME = 47, // 小游戏 + FORGE = 48, // 火神祭祀 GROUP_SHOP = 49, // 团购 BIND_PHONE = 50, // 绑定手机号 } diff --git a/shared/consts/constModules/chatConst.ts b/shared/consts/constModules/chatConst.ts index 8d7ec8de8..aeb94c73e 100644 --- a/shared/consts/constModules/chatConst.ts +++ b/shared/consts/constModules/chatConst.ts @@ -171,6 +171,7 @@ export const PUSH_ROUTE = { HERO_SKIN_CHANGE: 'onHeroSkinChange', HERO_UPDATE: 'onHeroUpdate', ITEM_UPDATE: 'onItemUpdate', + ACTIVITY_ITEM_UPDATE: 'onActivityItemUpdate', JEWEL_DEL: 'onJewelDel', JEWEL_ADD: 'onJewelAdd', ARTIFACT_DEL: 'onArtifactDel', diff --git a/shared/consts/constModules/itemConst.ts b/shared/consts/constModules/itemConst.ts index 406dbf613..c0970d220 100644 --- a/shared/consts/constModules/itemConst.ts +++ b/shared/consts/constModules/itemConst.ts @@ -98,6 +98,7 @@ export const ITEM_TABLE = { SKIN: 'skin', JEWEL: 'jewel', ARTIFACT: 'artifact', + ACTIVITY_ITEM: 'activityItem', } const itid_array = [ @@ -153,6 +154,7 @@ const itid_array = [ { id: 63, name: '代金券', table: 'item', type: CONSUME_TYPE.VOUCHER }, { id: 64, name: '宝物', table: 'artifact' }, { id: 65, name: '宝物通用材料', table: 'item', type: CONSUME_TYPE.ARTIFACT_GENERAL }, + { id: 66, name: '活动限时材料', table: 'activityItem' }, ]; export const ITID = new Map(); diff --git a/shared/consts/constModules/selectConst.ts b/shared/consts/constModules/selectConst.ts index cb2986877..00ffc2b60 100644 --- a/shared/consts/constModules/selectConst.ts +++ b/shared/consts/constModules/selectConst.ts @@ -57,7 +57,7 @@ export enum FRIEND_SHIP_SELECT { GET_FRIEND_VALUE = 'friendValue friendLv' } -export const ENTERY_ROLE_PICK = ['roleId', 'roleName', 'serverId', 'ce', 'topLineupCe', 'coin', 'lv', 'exp', 'vLv', 'gold', 'heros', 'jewels', 'artifacts', 'consumeGoods', 'title', 'teraphs', 'showLineup', 'heads', 'head', 'frames', 'frame', 'spines', 'spine', 'hasGuild', 'guildCode', 'todayZeroPoint', 'apJson', 'skins', 'totalPay', 'guide', 'hasInit', 'renameCnt', 'totalCost', 'guildName', 'isVip', 'createTime', 'ipLocation']; +export const ENTERY_ROLE_PICK = ['roleId', 'roleName', 'serverId', 'ce', 'topLineupCe', 'coin', 'lv', 'exp', 'vLv', 'gold', 'heros', 'jewels', 'artifacts', 'consumeGoods', 'title', 'teraphs', 'showLineup', 'heads', 'head', 'frames', 'frame', 'spines', 'spine', 'hasGuild', 'guildCode', 'todayZeroPoint', 'apJson', 'skins', 'totalPay', 'guide', 'hasInit', 'renameCnt', 'totalCost', 'guildName', 'isVip', 'createTime', 'ipLocation', 'activityItems']; export enum SURVEY_SELECT { FIND = '-__v -_id -surveyName -roleIndex -reward -mailContent -receivedRole -createdAt -updatedAt' @@ -66,3 +66,7 @@ export enum SURVEY_SELECT { export enum ARTIFACT_SELECT { ENTRY = '-_id -__v -roleId -roleName -createdAt -updatedAt -status' } + +export enum ACTIVITYITEM_SELECT { + ENTRY = '-_id -__v -roleId -roleName -itemName -createdAt -updatedAt' +} diff --git a/shared/consts/constModules/sysConst.ts b/shared/consts/constModules/sysConst.ts index 5b71033f6..b36c8d61a 100644 --- a/shared/consts/constModules/sysConst.ts +++ b/shared/consts/constModules/sysConst.ts @@ -1140,6 +1140,8 @@ export enum ITEM_CHANGE_REASON { GVG_REVIVE = 174, // gvg复活队伍 GVG_USE_ITEM = 175, // gvg使用连弩 ARTIFACT_LV_RETURN = 176, // 宝物继承等级返还 + ACT_FORGE_BUILD = 177, // 火神祭祀锻造 + ACT_FORGE_HELP = 178, // 火神祭祀失败补助 } export enum TA_EVENT { diff --git a/shared/consts/statusCode.ts b/shared/consts/statusCode.ts index f17b57063..1b3cc572a 100644 --- a/shared/consts/statusCode.ts +++ b/shared/consts/statusCode.ts @@ -644,6 +644,11 @@ export const STATUS = { ACTIVITY_BIND_ERR: { code: 50042, simStr: '清先绑定手机' }, ACTIVITY_BIND_RECEIVED: { code: 50043, simStr: '奖励已领取过' }, ACTIVITY_HAS_BIND: { code: 50044, simStr: '已绑定' }, + ACTIVITY_MANUAL_NOT_FOUND: { code: 50045, simStr: '图谱未找到' }, + ACTIVITY_MANUAL_DAY_LOCK: { code: 50046, simStr: '该图谱今天未解锁' }, + ACTIVITY_BUILD_COUNT: { code: 50047, simStr: '铸造次数已满' }, + ACTIVITY_BUY_CNT_MAX: { code: 50048, simStr: '该图谱购买次数已达上限' }, + ACTIVITY_MATERIAL_COUNT_NOT_ZERO: { code: 50049, simStr: '材料数量不可为0' }, // GM后台相关状态 60000 - 69999 GM_ERR_PASSWORD: { code: 60001, simStr: '账号或密码错误' }, diff --git a/shared/db/ActivityForge.ts b/shared/db/ActivityForge.ts new file mode 100644 index 000000000..beb7fc224 --- /dev/null +++ b/shared/db/ActivityForge.ts @@ -0,0 +1,65 @@ +import BaseModel from './BaseModel'; +import { index, getModelForClass, prop, DocumentType } from '@typegoose/typegoose'; + +/** + * 火神祭祀 +*/ + +class ForgeRecord { + @prop({ required: true }) + todayIndex: number; + + @prop({ required: true }) + material: string; + + @prop({ required: true }) + isSuccess: boolean; +} + +@index({ roleId: 1, activityId: 1 }) + +export default class Activity_Forge extends BaseModel { + @prop({ required: true }) + serverId: number; // 服Id + + @prop({ required: true }) + activityId: number; // 活动Id + + @prop({ required: true }) + roundIndex: number; // 活动Id + + @prop({ required: true }) + roleId: string; // 用户Id + + @prop({ required: true }) + manualId: number; // 图谱id + + @prop({ required: true }) + buildCnt: number; // 铸造次数 + + @prop({ required: true }) + buyCnt: number; // 购买次数 + + @prop({ required: true, type: ForgeRecord, _id: false }) + record: ForgeRecord[]; // 铸造记录 + + public static async findData(serverId: number, activityId: number, roundIndex: number, roleId: string) { + let result: ActivityForgeModelType[] = await ActivityForgeModel.find({ serverId, roleId, activityId, roundIndex }).lean(); + return result; + } + + public static async build(serverId: number, activityId: number, roleId: string, roundIndex: number, manualId: number, record: ForgeRecord) { + let result: ActivityForgeModelType = await ActivityForgeModel.findOneAndUpdate({ serverId, roleId, activityId, roundIndex, manualId }, { $inc: { buildCnt: record.isSuccess? 1: 0 }, $push: { record } }, { new: true, upsert: true }).lean(); + return result; + } + + public static async buyCnt(serverId: number, activityId: number, roleId: string, roundIndex: number, manualId: number, count: number) { + let result: ActivityForgeModelType = await ActivityForgeModel.findOneAndUpdate({ serverId, roleId, activityId, manualId, roundIndex }, { $inc: { buyCnt: count } }, { new: true, upsert: true }).lean(); + return result; + } +} + +export const ActivityForgeModel = getModelForClass(Activity_Forge); + +export interface ActivityForgeModelType extends Pick, keyof Activity_Forge> { } +export type ActivityForgeModelTypeParam = Partial; // 将所有字段变成可选项 \ No newline at end of file diff --git a/shared/db/ActivityItem.ts b/shared/db/ActivityItem.ts new file mode 100644 index 000000000..87d4028b7 --- /dev/null +++ b/shared/db/ActivityItem.ts @@ -0,0 +1,85 @@ +import BaseModel from './BaseModel'; +import { index, getModelForClass, prop, DocumentType, modelOptions } from '@typegoose/typegoose'; +import { BAG } from '../pubUtils/dicParam'; +import { nowSeconds } from '../pubUtils/timeUtil'; + +@index({ roleId: 1, id: 1 }) +@modelOptions({ schemaOptions: { id: false } }) +export default class ActivityItem extends BaseModel { + @prop({ required: true, default: '' }) + roleId: string; // 角色 id + @prop({ required: true, default: '' }) + roleName: string; // 角色名称 + + @prop({ required: true, default: '' }) + id: number; // 道具 id + @prop({ required: true, default: '' }) + itemName: string; // 道具名称 + + @prop({ required: true, default: 0 }) + expireTime: number; // 限时 + + @prop({ required: true }) + count: number; // 道具数量 + + + public static async findbyRole(roleId: string, select = '') { + const items: ActivityItemType[] = await ActivityItemModel.find({ roleId, count: {$gte: 0}, expireTime: { $gte: nowSeconds() } }).select(select).lean(); + return items; + } + + public static async findbyRoleAndIds(roleId: string, ids: Array, lean = true) { + const items: ActivityItemType[] = await ActivityItemModel.find({ roleId, id: { $in: ids }, count: {$gte: 0}, expireTime: { $gte: nowSeconds() } }).select('id count expireTime').lean(lean); + return items; + } + + public static async findbyRoleAndGid(roleId: string, id: number, lean = true) { + const items: ActivityItemType = await ActivityItemModel.findOne({ roleId, id }).select('id count expireTime').lean(lean); + return items; + } + + public static async increaseActivityItem(roleId: string, id: number, count: number, itemInfo: { roleId: string, roleName: string, id: number, itemName: string, expireTime?: number }) { + const doc = new ActivityItemModel(); + const setOnInsert = Object.assign(doc.toJSON(), itemInfo); + delete setOnInsert.expireTime; + let items: ActivityItemType = await ActivityItemModel.findOneAndUpdate({ roleId, id, expireTime: { $gte: nowSeconds() } }, { $setOnInsert: setOnInsert, $inc: { count }, $set: { expireTime: itemInfo.expireTime } }, { new: true, upsert: true }).lean(); + if(items.count > BAG.BAG_GOODS_UPLIMITED) { + items = await ActivityItemModel.findOneAndUpdate({ roleId, id, count: { $gte: BAG.BAG_GOODS_UPLIMITED }, expireTime: { $gte: nowSeconds() } }, { $set: { count: BAG.BAG_GOODS_UPLIMITED, expireTime: itemInfo.expireTime } }, { new: true }).lean(); + } + return items; + } + + public static async decreaseActivityItems(roleId: string, items: Array<{ id: number, count: number }>, lean = true) { + let updateActivityItems = new Array<{ id: number, count: number }>(), hasError: boolean = false, result = new Array(); + for (let { id, count } of items) { + let rec: ActivityItemType = await ActivityItemModel.findOneAndUpdate({ roleId, id, count: { $gte: count }, expireTime: { $gte: nowSeconds() } }, { $inc: { count: -1 * count } }, { new: true }).lean(lean); + if (!!rec) { + let index = result.findIndex(cur => cur.id == rec.id); + if (index != -1) { + result[index].count = rec.count; + result[index].inc += -count; + } else { + result.push({ id: rec.id, count: rec.count, expireTime: rec.expireTime, inc: -count }); + } + updateActivityItems.push({ id, count }); + } else { + hasError = true; break; + } + } + if (hasError) { // 数量不足 + for (let { id, count } of updateActivityItems) { + await ActivityItemModel.findOneAndUpdate({ roleId, id, expireTime: { $gte: nowSeconds() } }, { $inc: { count: -count } }, { new: true }).lean(lean); + } + return { hasError: true } + } else { + return { hasError: false, result } + } + } +} + + +export const ActivityItemModel = getModelForClass(ActivityItem); + +export interface ActivityItemType extends Pick, keyof ActivityItem> { + id: number; +}; diff --git a/shared/domain/activityField/forgeField.ts b/shared/domain/activityField/forgeField.ts new file mode 100644 index 000000000..3fad6d9f9 --- /dev/null +++ b/shared/domain/activityField/forgeField.ts @@ -0,0 +1,134 @@ +// 节日活动 - 火神祭祀 +import { ActivityModelType } from '../../db/Activity'; +import { ActivityForgeModelType } from '../../db/ActivityForge'; +import { parseGoodStr } from '../../pubUtils/util'; +import { ActivityBase } from './activityField'; + +// 后台格式 + +interface ForgeManualInDb { + id: number; // 图谱id + name: string; // 图谱名 + imageName: string; // 图片 + dayIndex: number; // 第几天解锁 + material: string; // 配方比例id&count + totalMaterialCnt: number; // 最大材料数量 + quality: number; // 品质 + reward: string; // 铸造成功可获得的奖励,id&count + freeCnt: number; // 可免费铸造次数(总) + maxBuyCnt: number; // 最大可购买次数(总) +} + +interface ForgeHintInDb { + failCnt: number; + hintType: number; // 1-提示总额 2-提示材料多了还是少了 3-材料具体配比提示 +} + +interface ForgeDataInDb { + manuals: ForgeManualInDb[]; // 图谱 + consume: string; // 购买铸造次数消耗的元宝 + hint: ForgeHintInDb[]; // 失败提示法 +} + +// 商品数据 +export class ForgeManual { + id: number; // 图谱id + name: string; // 图谱名 + imageName: string; // 图片 + dayIndex: number; // 第几天解锁 + material: string; // id&count,正确的配比 + reward: string; // 铸造成功可获得的奖励,id&count + freeCnt: number; // 可免费铸造次数 + maxBuyCnt: number; // 今天可以购买的次数 + totalMaterialCnt: number; // 最大材料数量 + quality: number; // 品质 + + buildCnt: number = 0; // 已经铸造了的次数 buildCnt < freeCnt + buyCnt + buyCnt: number = 0; // 今天已购买次数 + failCnt: number = 0; // 猜错的次数 + + hintType: number = 0; // 应该给的提示类型 1-总数提示 2-材料多了少了的提示 3-具体配比提示 + + constructor(data: ForgeManualInDb) { + this.id = data.id; + this.name = data.name; + this.imageName = data.imageName; + this.dayIndex = data.dayIndex; + this.material = data.material; + this.reward = data.reward; + this.freeCnt = data.freeCnt; + this.maxBuyCnt = data.maxBuyCnt; + this.totalMaterialCnt = data.totalMaterialCnt; + this.quality = data.quality; + } + + public setPlayerData(playerData: ActivityForgeModelType, todayIndex: number) { + this.buildCnt = playerData.buildCnt||0; + this.buyCnt = playerData.buyCnt||0; + let todayFailRecord = playerData.record?.filter(cur => cur.todayIndex == todayIndex && cur.isSuccess == false)||[]; + this.failCnt = todayFailRecord.length; + } + + public calHintType(hintDic: ForgeHintInDb[]) { + for(let { failCnt, hintType } of hintDic) { + if(this.failCnt < failCnt) { + this.hintType = hintType; break; + } + } + } + + public checkMaterial(playerMaterial: { id: number, count: number }[]) { + let configMaterial = parseGoodStr(this.material); + for(let { id, count } of configMaterial) { + let playerCount = playerMaterial.find(cur => cur.id == id)?.count||0; + if(playerCount != count) return false; + } + for(let { id, count } of playerMaterial) { + let configCount = configMaterial.find(cur => cur.id == id)?.count||0; + if(configCount != count) return false; + } + return true; + } +} + +export class ForgeData extends ActivityBase { + manuals: ForgeManual[] = []; // 图谱 + consume: string; // 购买铸造次数的消耗 + hint: ForgeHintInDb[]; + + constructor(activityData: ActivityModelType, createTime: number, serverTime: number) { + super(activityData, createTime, serverTime) + this.initData(activityData.data) + } + + public initData(data: string): void { + let dataObj: ForgeDataInDb = JSON.parse(data); + if(!dataObj) return; + + this.hint = dataObj.hint||[]; + this.consume = dataObj.consume||''; + for(let data of (dataObj.manuals||[])) { + this.manuals.push(new ForgeManual(data)); + } + } + + public setPlayerRecords(playerData: ActivityForgeModelType[]) { + for(let data of playerData) { + let manual = this.manuals.find(cur => cur.id == data.manualId); + if(manual) manual.setPlayerData(data, this.todayIndex); + manual.calHintType(this.hint); + } + } + + public findManual(id: number) { + return this.manuals.find(cur => cur.id == id); + } + + public getShowResult() { + return { + ...this.getBaseKeys(), + manuals: this.manuals, + consume: this.consume + } + } +} \ No newline at end of file diff --git a/shared/domain/activityField/refreshShopField.ts b/shared/domain/activityField/refreshShopField.ts index 47312b8e9..6324b33e2 100644 --- a/shared/domain/activityField/refreshShopField.ts +++ b/shared/domain/activityField/refreshShopField.ts @@ -13,6 +13,7 @@ interface RefreshShopDataInDb { interface RefreshShopPageInDb { pageIndex: number; viewcount: number; + preNeedBuyCnt: number; // 添加:上一页需要购买的 name: string; items: RefreshShopItemInDb[] } @@ -88,6 +89,7 @@ export class RefreshShopPage { pageIndex: number; // 第几页 name: string; //名字 viewCount: number; //随机可购买的商品个数 + preNeedBuyCnt: number; // 添加:上一页需要购买的 items: Array = [];//商品列表 constructor(data: RefreshShopPageInDb) { this.pageIndex = data.pageIndex; @@ -95,6 +97,7 @@ export class RefreshShopPage { for (let item of data.items) { this.items.push(new RefreshShopItem(item, data.pageIndex)) } + this.preNeedBuyCnt = data.preNeedBuyCnt; this.viewCount = data.viewcount ? data.viewcount : this.items.length; } } diff --git a/shared/domain/activityField/rewardField.ts b/shared/domain/activityField/rewardField.ts index cd52639e3..823b53e88 100644 --- a/shared/domain/activityField/rewardField.ts +++ b/shared/domain/activityField/rewardField.ts @@ -3,4 +3,5 @@ export interface RewardParam { type: number; id: number; count: number; + expireTime?: number; } \ No newline at end of file diff --git a/shared/pubUtils/interface.ts b/shared/pubUtils/interface.ts index 0c16db178..fdff7c492 100644 --- a/shared/pubUtils/interface.ts +++ b/shared/pubUtils/interface.ts @@ -5,6 +5,7 @@ import { UserGuildType } from "../db/UserGuild"; export interface RewardInter { id: number; count: number; + expireTime?: number; } export interface ItemInter { @@ -14,6 +15,7 @@ export interface ItemInter { type?: number; isPay?: boolean; hid?: number; + expireTime?: number; }; // 百家学宫,布阵武将位置 diff --git a/shared/resource/jsons/dic_zyz_activityType.json b/shared/resource/jsons/dic_zyz_activityType.json index 770f8e111..f4c997df1 100644 --- a/shared/resource/jsons/dic_zyz_activityType.json +++ b/shared/resource/jsons/dic_zyz_activityType.json @@ -251,6 +251,18 @@ "name": "POP_NOTICE", "string": "打脸公告" }, + { + "id": 47, + "activityType": 47, + "name": "MINI_GAME", + "string": "小游戏" + }, + { + "id": 48, + "activityType": 48, + "name": "FORGE", + "string": "火神祭祀" + }, { "id": 49, "activityType": 49,