feat(服务器): 修改服务器自动开服策略

This commit is contained in:
luying
2023-04-25 13:04:30 +08:00
parent af1f6b9d8c
commit 906449be60
14 changed files with 121 additions and 128 deletions

View File

@@ -235,7 +235,7 @@ async function treatStartLogic(app: _pinus.Application) {
async function treatStartMainten(app: _pinus.Application) {
if(app.getServerType() == 'systimer') {
// 维护信息
await timeTaskService.initMaintenance(null, true);
await timeTaskService.initMaintenance(undefined, true);
} else {
await timeTaskService.initMaintenanaceInOtherServers();
}

View File

@@ -25,6 +25,7 @@ import { checkServerIsOpen, getIpLocation } from '../../../services/roleService'
import { leaveRaceActivityToRemote } from '../../../services/guildActivity/guildActivityService';
import { getHiddenData } from '../../../services/dataService';
import { leaveCity } from '../../../services/gvg/gvgBattleService';
import { autoCreateServerWhenRoleInit } from '../../../services/serverService';
export default function (app: Application) {
new HandlerService(app, {});
@@ -73,6 +74,7 @@ export class EntryHandler {
if (!role) {
return resResult(STATUS.ROLE_NOT_FOUND);
}
console.log('#### needCount 0', role.needCount)
let serverName = this.app.getServerId();
await roleLogin(role.roleId, user.userCode, serverName, role.createTime, role.serverId, role.lv, role.topLineupCe); // 保存在线用户
@@ -91,6 +93,8 @@ export class EntryHandler {
}
let todayZeroPoint = getZeroPoint();
console.log('####### needCount', role.needCount)
if(role.needCount) autoCreateServerWhenRoleInit(serverId);
// console.log('****** entry after', Date.now());
return resResult(STATUS.SUCCESS, { roleId: role.roleId, todayZeroPoint });

View File

@@ -6,7 +6,7 @@ import { RegionModel, RegionType } from '../../../db/Region';
import { getDicServerName } from '../../../pubUtils/data';
import { Maintenance, ServerlistModel, ServerlistUpdate } from '../../../db/Serverlist';
import { nowSeconds, getFutureTime, getPastTime } from '../../../pubUtils/timeUtil';
import { calHiddenData, createNewServer } from '../../../services/gmService';
import { calHiddenData } from '../../../services/gmService';
import { isNumber } from 'util';
import { MarqueeModel } from '../../../db/Marquee';
import { setApiIsCloseToRemote } from '../../../services/chatService';
@@ -16,6 +16,7 @@ import { HiddenDataByIdModel, HiddenDataByIdModelTypeParam } from '../../../db/H
import { isDevelopEnv } from '../../../services/utilService';
import { redisClient } from "../../../services/redisService";
import { REDIS_KEY } from "../../../consts";
import { createNewServer } from '../../../services/serverService';
export default function (app: Application) {
return new GmHandler(app);
@@ -40,7 +41,6 @@ export class GmHandler {
let update = params.getUpdateParam(oldRegion);
region = await RegionModel.updateRegion(params.id, update, uid);
}
await pinus.app.rpc.systimer.systimerRemote.initAutoCreateServer.broadcast(region);
return resResult(STATUS.SUCCESS);
}

View File

@@ -1,5 +1,5 @@
import { Application, ChannelService } from 'pinus';
import { guildActivityStart, gateActivityEnd, cityActivityEnd, raceActivityEnd, guildActivitySchedule, auctionSchedule, initMaintenance, stopMaintenance, initAutoCreateServer, addMailsToSchedule, updateTimeLimitRank, setLadderCountDown, cancelLadderCountDown, initSumSchedule, setPvpSeasonSchedule, initGVGConfigSchedule, gvgBattleStartSchedule, gvgBattleEndSchedule } from '../../../services/timeTaskService';
import { guildActivityStart, gateActivityEnd, cityActivityEnd, raceActivityEnd, guildActivitySchedule, auctionSchedule, initMaintenance, stopMaintenance, addMailsToSchedule, updateTimeLimitRank, setLadderCountDown, cancelLadderCountDown, initSumSchedule, setPvpSeasonSchedule, initGVGConfigSchedule, gvgBattleStartSchedule, gvgBattleEndSchedule } from '../../../services/timeTaskService';
import PvpDefenseType from '../../../db/PvpDefense';
import { DicGuildActivity } from '../../../pubUtils/dictionary/DicGuildActivity';
import { reloadResources } from '../../../pubUtils/data';
@@ -172,14 +172,6 @@ export class SystimerRemote {
}
}
public initAutoCreateServer(region: RegionType) {
try {
return initAutoCreateServer(region);
} catch(e) {
errlogger.error(`remote ${__filename} \n ${e.stack}`);
}
}
public async addMailsToSchedule(mails: MailType[], groupMails: GroupMailType[], serverMails: ServerMailType[]) {
try {
await addMailsToSchedule(mails, groupMails, serverMails);

View File

@@ -188,28 +188,6 @@ export function getServerMainten(serverId: number) {
return maintenServers.get(serverId);
}
export async function createNewServer(region: RegionType ,serverId: number, params: CreateServerParam, uid?: number) {
let newServer = await ServerlistModel.newServer(params, region, serverId, uid);
if(params.openMail) await sendOpenServerMail('openMail', params.openMail, newServer, uid);
if(params.circleMail) await sendOpenServerMail('circleMail', params.circleMail, newServer, uid);
if(params.activityGroupId && params.activityGroupId.length > 0) {
await ActivityGroupModel.addServerToGroupData(newServer.id, params.activityGroupId);
let activityGroups = await ActivityGroupModel.findByServerId(newServer.id);
await pinus.app.rpc.activity.activityRemote.addServerToGroup.broadcast(params.activityGroupId, [newServer.id]);
let aids = activityGroups.reduce((pre, cur) => [...pre, ...cur.activities], []);
let activities = await ActivityModel.findActivityByIds(aids);
let timeLimitRanks = activities.filter(cur => cur.type == ACTIVITY_TYPE.TIME_LIMIT_RANK);
if(timeLimitRanks.length > 0) {
pinus.app.rpc.systimer.systimerRemote.updateTimeLimitRank.broadcast(timeLimitRanks);
}
}
await RegionModel.newServer(region.id, newServer);
await redisClient().hsetAsync(REDIS_KEY.SERVER, `${newServer.id}`, `${newServer.name}`);
await redisClient().hsetAsync(REDIS_KEY.SERVER_OPEN_TIME, `${newServer.id}`, `${newServer.openTime}`);
}
function getDicRMB(productID: string) {
let dic = dicRMB.get(productID);
if(!dic) return null

View File

@@ -1,11 +1,18 @@
import { pinus } from "pinus";
import { uniq } from "underscore";
import { GVG_SERVER_TYPE, SERVER_GROUP_FUN_TYPE } from "../consts";
import { ACTIVITY_TYPE, GVG_SERVER_TYPE, REDIS_KEY, SERVER_GROUP_FUN_TYPE } from "../consts";
import { ServerGroupModel } from "../db/ServerGroup";
import { ServerlistModel } from "../db/Serverlist";
import { nowSeconds } from "../pubUtils/timeUtil";
import { getServerTypeByTime } from "./gvg/gvgService";
import { getAllServerCreateTime } from "./redisService";
import { getAllServerCreateTime, redisClient } from "./redisService";
import moment = require("moment");
import { RegionModel, RegionType } from "../db/Region";
import { errlogger } from "../util/logger";
import { CreateServerParam } from "../domain/backEndField/params";
import { sendOpenServerMail } from "./gmService";
import { ActivityGroupModel } from "../db/ActivityGroup";
import { ActivityModel } from "../db/Activity";
export async function setServerGroup() {
const servers = await ServerlistModel.findByEnv(pinus.app.get('env'));
@@ -100,4 +107,65 @@ export async function getPVPServerGroup() {
map.get(groupId).push(serverId);
}
return map
}
export async function autoCreateServerSchedule() {
console.log('******* autoCreateServerSchedule *******')
let region = await RegionModel.findRegionByEnv(pinus.app.get('env'));
if(!region) {
return errlogger.error('create new server region not found');
}
let time = moment().format('HH:mm');
console.log('#### time', time)
if(region.stategy && region.stategy.type == 1 && region.stategy.timers.indexOf(time) != -1) {
let latestServer = await ServerlistModel.findByServerId(region.latestServerUniqId);
if(!latestServer || latestServer.playerCnt >= region.stategy.maxPlayerCnt) {
let params = new CreateServerParam();
params.setByRegionStategy(region, nowSeconds())
await createNewServer(region, (latestServer?.serverId||0) + 1, params);
}
}
}
export async function autoCreateServerWhenRoleInit(serverId: number) {
console.log('******* autoCreateServerWhenRoleInit *******')
let server = await ServerlistModel.incRoleCnt(serverId);
if(!server) {
return errlogger.error('server not found');
}
let region = await RegionModel.findRegionByEnv(pinus.app.get('env'));
if(!region) {
return errlogger.error('create new server region not found');
}
if(region.stategy && region.stategy.type == 2 && region.latestServerUniqId == server.id && server.playerCnt >= region.stategy.maxPlayerCnt) {
let params = new CreateServerParam();
params.setByRegionStategy(region, nowSeconds())
await createNewServer(region, server.serverId + 1, params);
}
}
export async function createNewServer(region: RegionType ,serverId: number, params: CreateServerParam, uid?: number) {
console.log('******* createNewServer *******')
let newServer = await ServerlistModel.newServer(params, region, serverId, uid);
if(!newServer) return;
if(params.openMail) await sendOpenServerMail('openMail', params.openMail, newServer, uid);
// if(params.circleMail) await sendOpenServerMail('circleMail', params.circleMail, newServer, uid);
if(params.activityGroupId && params.activityGroupId.length > 0) {
await ActivityGroupModel.addServerToGroupData(newServer.id, params.activityGroupId);
let activityGroups = await ActivityGroupModel.findByServerId(newServer.id);
await pinus.app.rpc.activity.activityRemote.addServerToGroup.broadcast(params.activityGroupId, [newServer.id]);
let aids = activityGroups.reduce((pre, cur) => [...pre, ...cur.activities], []);
let activities = await ActivityModel.findActivityByIds(aids);
let timeLimitRanks = activities.filter(cur => cur.type == ACTIVITY_TYPE.TIME_LIMIT_RANK);
if(timeLimitRanks.length > 0) {
pinus.app.rpc.systimer.systimerRemote.updateTimeLimitRank.broadcast(timeLimitRanks);
}
}
await RegionModel.newServer(region.id, newServer);
await redisClient().hsetAsync(REDIS_KEY.SERVER, `${newServer.id}`, `${newServer.name}`);
await redisClient().hsetAsync(REDIS_KEY.SERVER_OPEN_TIME, `${newServer.id}`, `${newServer.openTime}`);
}

View File

@@ -13,7 +13,7 @@ import { sendEndMsgToAllServer, sendGuildActivityStatus, setPreDayActiveData, in
import { sendUngotDividendJob, startGuildAuction, startWorldAuction, stopAuction } from './auctionService';
import { DicGuildActivity } from '../pubUtils/dictionary/DicGuildActivity';
import { dispatch } from '../util/dispatcher';
import { createNewServer, initMarquee, setServerMainten } from './gmService';
import { initMarquee, setServerMainten } from './gmService';
import moment = require('moment');
import { reportOneOnline } from './authenticateService';
import { GVG, LADDER, PVP } from '../pubUtils/dicParam';
@@ -23,7 +23,7 @@ import { Maintenance, ServerlistModel, ServerlistType } from '../db/Serverlist';
import { createMarqueeMsg, pushMarqueeMsg } from './sysChatService';
import { RegionModel, RegionType } from '../db/Region';
import { CreateServerParam } from '../domain/backEndField/params';
import { infologger } from '../util/logger';
import { infologger, errlogger } from '../util/logger';
import { MailModel, MailType } from '../db/Mail';
import { GroupMailModel, GroupMailType } from '../db/GroupMail';
import { ServerMailModel, ServerMailType } from '../db/ServerMail';
@@ -51,6 +51,7 @@ import { getActivityById } from './activity/activityService';
import { MonthlyFundData } from "../domain/activityField/monthlyFundField";
import { RewardInter } from '../pubUtils/interface';
import { stringToRewardInter } from './activity/giftPackageService';
import { autoCreateServerSchedule } from './serverService';
const PER_SECOND = 1 * 1000;
const PER_DAY = 24 * 60 * 60;
@@ -684,49 +685,10 @@ export async function stopMaintenance(batchCode: string, serverIds: number[]) {
// —————————————— 维护 end —————————————— //
// —————————————— 自动开服 —————————————— //
export async function initAutoCreateServer(region?: RegionType) {
if(!region) region = await RegionModel.findRegionByEnv(pinus.app.get('env'));
if(region && region.stategy && region.stategy.isOpen) {
for(let timer of region.stategy.timers) {
setStategyTimer(timer, region)
}
} else {
if(scheduledJobs[`autoServer${SERVER_TIMER.FIVE_HALF}`]) scheduledJobs[`autoServer${SERVER_TIMER.FIVE_HALF}`].cancel();
if(scheduledJobs[`autoServer${SERVER_TIMER.TEN_HALF}`]) scheduledJobs[`autoServer${SERVER_TIMER.FIVE_HALF}`].cancel();
if(scheduledJobs[`autoServer${SERVER_TIMER.FIFTEEN_HALF}`]) scheduledJobs[`autoServer${SERVER_TIMER.FIVE_HALF}`].cancel();
if(scheduledJobs[`autoServer${SERVER_TIMER.NINETEEN_HALF}`]) scheduledJobs[`autoServer${SERVER_TIMER.FIVE_HALF}`].cancel();
}
export async function initAutoCreateServer() {
scheduleJob('autoServer', '0 0 */1 * * ?', autoCreateServerSchedule)
}
function setStategyTimer(timerId: number, region: RegionType) {
if(scheduledJobs[`autoServer${timerId}`]) scheduledJobs[`autoServer${timerId}`].cancel();
let cron = '';
switch(timerId) {
case SERVER_TIMER.FIVE_HALF:
cron = '0 30 5 * * *'; break;
case SERVER_TIMER.TEN_HALF:
cron = '0 30 10 * * *'; break;
case SERVER_TIMER.FIFTEEN_HALF:
cron = '0 30 15 * * *'; break;
case SERVER_TIMER.NINETEEN_HALF:
cron = '0 30 19 * * *'; break;
}
if(cron) {
scheduleJob(`autoServer${timerId}`, cron, () => {
autoCreateServerSchedule(region);
})
}
}
async function autoCreateServerSchedule(region: RegionType) {
console.log('******* createNewServer *******')
let latestServer = await ServerlistModel.findByServerId(region.latestServerUniqId);
if(!latestServer || latestServer.playerCnt >= region.stategy.maxPlayerCnt) {
let params = new CreateServerParam();
params.setByRegionStategy(region, nowSeconds())
await createNewServer(region, (latestServer?.serverId||0) + 1, params);
}
}
// —————————————— 自动开服 end —————————————— //
// —————————————— 活动 start —————————————— //
@@ -1012,11 +974,11 @@ export async function gvgBattleEndSchedule() {
// —————————————— gvg end —————————————— //
async function initPushMsgSchedule() {
scheduleJob('sendAfkPlayers', '0 0 19 * * ?', async () => {
scheduleJob('sendDinner', '0 0 19 * * ?', async () => {
// pushClientMsg(SDK_PUSH_MSG_TYPE.AFK_ATTENTION);
pushClientMsg(SDK_PUSH_MSG_TYPE.AP_DINNER);
});
scheduleJob('sendAfkPlayers', '0 0 12 * * ?', async () => {
scheduleJob('sendLunch', '0 0 12 * * ?', async () => {
pushClientMsg(SDK_PUSH_MSG_TYPE.AP_LUNCH);
});
}

View File

@@ -25,6 +25,7 @@ module.exports = () => {
}
}
console.log('### options', options)
return proxy(options)(ctx, next);
};
};

View File

@@ -23,25 +23,6 @@ export default (appInfo: EggAppInfo) => {
pw: ''
};
let regions = [ // 大区数据
{ id: 1, env: 'development', name: "测试", domain: 'http://127.0.0.1:9000' },
];
config.regions = regions;
let httpProxy: any = {};
for(let { env, domain } of regions) {
httpProxy[`/api/${env}/`] = {
target: domain,
changeOrigin: true,
secure: true,
pathRewrite: function(path) {
console.log('proxy', path, path.replace(`/api/${env}/`, '/api/'))
return path.replace(`/api/${env}/`, '/api/')
}
}
}
config.proxy = httpProxy;
// the return config will combines to EggAppConfig
return {
...defaultConfig(appInfo),

View File

@@ -360,6 +360,9 @@ export default class Role extends BaseModel {
@prop({ required: true, select: false })
cancelCloseTime: number;
@prop({ required: true })
needCount: boolean;
public static async findAllByUid(uid: number, getters = false, virtuals = true) {
const role: RoleType[] = await RoleModel.find({ 'userInfo.uid': uid }).select('roleId roleName serverId head frame spine heads frames spines lv updatedAt createTime +closeTime').lean({ getters, virtuals });
return role;
@@ -368,7 +371,7 @@ export default class Role extends BaseModel {
public static async findByUidAndSetTime(uid: number, serverId: number, ip: string, ipLocation: string, select?: string, getters = false) {
const curSec = nowSeconds();
// 为了计算累计登录此处查询new为false
const role: RoleType = await RoleModel.findOneAndUpdate({ 'userInfo.uid': uid, serverId }, { $set: { loginTime: curSec, quitTime: curSec, ip, ipLocation } }, { new: false }).select(select).lean({ getters, virtuals: true });
const role: RoleType = await RoleModel.findOneAndUpdate({ 'userInfo.uid': uid, serverId }, { $set: { loginTime: curSec, quitTime: curSec, ip, ipLocation, needCount: false } }, { new: false }).select(select).lean({ getters, virtuals: true });
return role;
}
@@ -408,7 +411,7 @@ export default class Role extends BaseModel {
const user = await User.findUserByUid(uid);
if (!user) return null;
const doc = new RoleModel();
const update = Object.assign(doc.toJSON(), roleInfo, { userInfo: user, serverType: user.serverType, serverId, shushuMark: distinctId });
const update = Object.assign(doc.toJSON(), roleInfo, { userInfo: user, serverType: user.serverType, serverId, shushuMark: distinctId, needCount: true });
delete update._id;
const role: RoleType = await RoleModel.findOneAndUpdate({ 'userInfo.uid': uid, serverId }, update, { upsert: true, new: true }).lean(lean);
return role;

View File

@@ -59,32 +59,28 @@ export default class ServerStategy {
@prop({ required: true, default: 0 })
maxPlayerCnt: number; // 人数上限
@prop({ required: true, default: 0, type: Number })
timers: number[]; // 开服时间点
@prop({ required: true, default: 0 })
type: number; // 类型 1-定时 2-人数到了既时
@prop({ required: true, default: 0, type: String })
timers: string[]; // 开服时间点
@prop({ required: true, type: Number })
activityGroupId: number[]; // 选择活动组
@prop({ required: true, type: GMMail, _id: false })
openMail: GMMail;
@prop({ required: true, type: GMMail, _id: false })
circleMail: GMMail;
@prop({ required: true, default: 0 })
stopRegisterTime: number; // 关闭注册时间
constructor(stategy: ServerStategyUpdate & { hasOpenMail: boolean, hasCircleMail: boolean}) {
constructor(stategy: ServerStategyUpdate & { hasOpenMail: boolean}) {
this.isOpen = stategy.isOpen;
this.maxPlayerCnt = stategy.maxPlayerCnt;
this.type = stategy.type;
this.timers = stategy.timers;
this.activityGroupId = stategy.activityGroupId;
this.stopRegisterTime = stategy.stopRegisterTime;
if(stategy.hasCircleMail) {
this.circleMail = stategy.circleMail;
} else {
this.circleMail = null;
}
if(stategy.hasOpenMail) {
this.openMail = stategy.openMail;
} else {

View File

@@ -5,6 +5,7 @@ import { CreateServerParam } from '../domain/backEndField/params';
import { RegionType } from './Region';
import { nowSeconds } from '../pubUtils/timeUtil';
import { getDicServerName } from '../pubUtils/data';
import { genCode } from '../pubUtils/util';
export class Maintenance {
@@ -66,6 +67,9 @@ export default class Serverlist extends BaseModel {
// @prop({ required: true, type: Number })
// activityGroupId: number[]; // 活动组
@prop({ required: true })
generateCode: string; // 生成编号
public get status() {
let now = nowSeconds();
if (now < this.openTime) {
@@ -137,16 +141,20 @@ export default class Serverlist extends BaseModel {
let id = regionId * 10000 + serverId;
const doc = new ServerlistModel();
let generateCode = genCode(10)
const update = Object.assign(doc.toJSON(), {
id, regionId, env, prefix,
serverId, name, groupId, groupName, activityGroupId,
openTime, stopRegisterTime,
createdBy: uid, updatedBy: uid
createdBy: uid, updatedBy: uid, generateCode
});
// 旧服修改状态
await ServerlistModel.updateMany({ env, serverStatus: SERVER_STATUS.NEW }, { $set: { serverStatus: SERVER_STATUS.HOT } })
let server: ServerlistType = await ServerlistModel.findOneAndUpdate({ id }, { $setOnInsert: update }, { new: true, upsert: true }).lean({ getters: true, virtuals: true });
return server;
if(server.generateCode == generateCode) {
// 旧服修改状态
await ServerlistModel.updateMany({ env, serverStatus: SERVER_STATUS.NEW, id: { $lt: id } }, { $set: { serverStatus: SERVER_STATUS.HOT } })
return server;
}
return null;
}
public static async findByEnv(env?: string, isReview = false) {
@@ -180,7 +188,7 @@ export default class Serverlist extends BaseModel {
// }
public static async incRoleCnt(serverId: number) {
let server: ServerlistType = await ServerlistModel.findOneAndUpdate({ id: serverId }, { $inc: { playerCnt: 1 } });
let server: ServerlistType = await ServerlistModel.findOneAndUpdate({ id: serverId }, { $inc: { playerCnt: 1 } }, { new: true }).lean();
return server;
}

View File

@@ -1,4 +1,4 @@
import { GIFT_GENERATE_TYPE, GM_MAIL_TYPE, MAIL_TIME_TYPE, SERVER_TIMER } from "../../consts";
import { GIFT_GENERATE_TYPE, GM_MAIL_TYPE, MAIL_TIME_TYPE } from "../../consts";
import { isArray, isNumber, isString } from 'underscore';
import ServerStategy, { GMMail } from "../../db/ServerStategy";
import { RegionType } from "../../db/Region";
@@ -85,15 +85,16 @@ export class UpdateRegionParams {
addressType: number = 0;
maxPlayerCnt: number = 0;
timers: SERVER_TIMER[] = [];
type: number = 0;
timers: string[] = [];
activityGroupId: number[] = [];
openMail?: GMMail;
circleMail?: GMMail;
// circleMail?: GMMail;
stopRegisterTime: number = 0;
isOpen: boolean = false;
hasOpenMail: boolean = false;
hasCircleMail: boolean = false;
// hasCircleMail: boolean = false;
constructor(obj: UpdateRegionParams) {
for(let key in obj) {
@@ -182,8 +183,8 @@ export class CreateServerParam {
activityGroupId: number[] = [];
hasOpenMail: boolean = false;
openMail?: GMMail;
hasCircleMail: boolean = false;
circleMail?: GMMail;
// hasCircleMail: boolean = false;
// circleMail?: GMMail;
stopRegisterTime: number = 0;
constructor(obj?: any) {
@@ -201,8 +202,8 @@ export class CreateServerParam {
this.activityGroupId = region.stategy.activityGroupId;
this.hasOpenMail = !!region.stategy.openMail;
this.openMail = region.stategy.openMail;
this.hasCircleMail = !!region.stategy.circleMail;
this.circleMail = region.stategy.circleMail;
// this.hasCircleMail = !!region.stategy.circleMail;
// this.circleMail = region.stategy.circleMail;
this.stopRegisterTime = region.stategy.stopRegisterTime;
}
@@ -214,7 +215,7 @@ export class CreateServerParam {
return false
}
if(this.hasOpenMail && !this.openMail) return false;
if(this.hasCircleMail && !this.circleMail) return false;
// if(this.hasCircleMail && !this.circleMail) return false;
return true;
}
}

View File

@@ -338,7 +338,6 @@ export default class Auth extends Service {
const role = await RoleModel.createRole(uid, serverId, { roleId, code, roleName: "默认玩家名", seqId, lv: DEFAULT_LV, exp: (getExpByLv(DEFAULT_LV - 1) || { sum: 0 }).sum || 0 }, distinctId);
if (role) {
await ServerlistModel.incRoleCnt(serverId);
if(server.isReview) { // 审核服跳关卡
await this.skipPrologueWhenReview(roleId);
}