diff --git a/game-server/app/servers/battle/handler/pvpHandler.ts b/game-server/app/servers/battle/handler/pvpHandler.ts index 0a2ab0e9e..2b9c82bd1 100644 --- a/game-server/app/servers/battle/handler/pvpHandler.ts +++ b/game-server/app/servers/battle/handler/pvpHandler.ts @@ -276,7 +276,7 @@ export class PvpHandler { let myRank = await r.getMyRank({ roleId }); result.setMyRank(myRank); - await checkTaskInPvpEnd(serverId, roleId, sid, isSuccess, pvpDefense.heroScores); + await checkTaskInPvpEnd(serverId, roleId, sid, isSuccess, pvpDefense.heroScores, myRank); if(hisWinStreakNum < pvpDefense.hisWinStreakNum) { await unlockFigure(sid, roleId, [{ type: FIGURE_UNLOCK_CONDITION.PVP_WIN_SERIES, paramWinStreakNum: pvpDefense.hisWinStreakNum }]); } diff --git a/game-server/app/servers/connector/handler/entryHandler.ts b/game-server/app/servers/connector/handler/entryHandler.ts index d2e58fd70..1f2cb8bbe 100644 --- a/game-server/app/servers/connector/handler/entryHandler.ts +++ b/game-server/app/servers/connector/handler/entryHandler.ts @@ -20,11 +20,6 @@ import Counter from '../../../db/Counter'; import { getExpByLv } from '../../../pubUtils/data'; import { reportTAUserSet } from '../../../services/sdkService'; import { saveLoginAndOutLog } from '../../../pubUtils/logUtil'; -import { JewelModel } from '../../../db/Jewel'; -import { ItemModel } from '../../../db/Item'; -import { SkinModel } from '../../../db/Skin'; -import { HeroParam } from '../../../domain/roleField/hero'; -import { getAp } from '../../../services/actionPointService'; import { sendMessageToAllWithSuc } from '../../../services/pushService'; export default function (app: Application) { @@ -68,7 +63,7 @@ export class EntryHandler { } } let serverName = this.app.getServerId(); - await roleLogin(role.roleId, user.userCode, serverName, user.pkgName, role.createTime); // 保存在线用户 + await roleLogin(role.roleId, user.userCode, serverName, user.pkgName, role.createTime, role.serverId); // 保存在线用户 await this.addSession(user, role, session); saveLoginAndOutLog(LOG_TYPE.LOGIN, session); diff --git a/game-server/app/servers/guild/remote/guildActivityRemote.ts b/game-server/app/servers/guild/remote/guildActivityRemote.ts index a6a877db2..7d5bee28c 100644 --- a/game-server/app/servers/guild/remote/guildActivityRemote.ts +++ b/game-server/app/servers/guild/remote/guildActivityRemote.ts @@ -18,6 +18,7 @@ export class GuildActivityRemote { private currentTime: number = Date.now(); private setTime: number = Date.now(); + private isAuctionPopUp: boolean = false; /** * 从systimer服分发到guild各个服,发送排行榜数据 @@ -36,6 +37,10 @@ export class GuildActivityRemote { public async guildActivityEnd(aid: number) { try { await settleGuildActivityReward(aid); + this.isAuctionPopUp = true; + setTimeout(() => { // 军团活动结束的15分钟内就不会再弹出了 + this.isAuctionPopUp = false; + }, 15 * 60 * 1000); } catch(e) { errlogger.error(`remote ${__filename} \n ${e.stack}`); } @@ -147,4 +152,12 @@ export class GuildActivityRemote { errlogger.error(`remote ${__filename} \n ${e.stack}`); } } + + public getAuctionPopUpShow() { + try { + return this.isAuctionPopUp; + } catch(e) { + errlogger.error(`remote ${__filename} \n ${e.stack}`); + } + } } \ No newline at end of file diff --git a/game-server/app/servers/role/handler/equipHandler.ts b/game-server/app/servers/role/handler/equipHandler.ts index bd6e3d58a..0ca4c57ad 100644 --- a/game-server/app/servers/role/handler/equipHandler.ts +++ b/game-server/app/servers/role/handler/equipHandler.ts @@ -250,7 +250,7 @@ export class EquipHandler { let oldEplace = hero.ePlace||[]; let curEquip = oldEplace.find(cur => cur.id == ePlaceId); if(!curEquip) return resResult(STATUS.EQUIP_NOT_FIND); - + let incEquipStarSum = 0; let equipUpdate = { star: curEquip.star, starStage: curEquip.starStage, @@ -275,6 +275,7 @@ export class EquipHandler { if(isEnough) { equipUpdate.star = nextEquipStar.star; equipUpdate.starStage = 0; + incEquipStarSum += equipUpdate.star - curEquip.star; } } } else { // 升小点,包括一键升到满小点和升一个小点 @@ -298,9 +299,11 @@ export class EquipHandler { ePlace: newEplace, consumes: addConsumeToHero(hero.consumes, consumes) } - await calculateCeWithHero(HERO_SYSTEM_TYPE.EQUIP_STAR, roleId, serverId, sid, hid, update, { ePlaceId }); - await checkTaskInEquipStarUp(serverId, roleId, sid, oldEplace, newEplace, ePlaceId, hid, isUpStar); + console.log('### incEquipStarSum', incEquipStarSum) + let { curRole } = await calculateCeWithHero(HERO_SYSTEM_TYPE.EQUIP_STAR, roleId, serverId, sid, hid, update, { ePlaceId, roleIncUpdate: { equipStarSum: incEquipStarSum } }); + + await checkTaskInEquipStarUp(serverId, roleId, sid, oldEplace, newEplace, ePlaceId, hid, isUpStar, curRole.equipStarSum); pushEquipStarMax(roleId, roleName, serverId, hid, newEquip, isUpStar); const curHero = { diff --git a/game-server/app/services/activity/popUpShopService.ts b/game-server/app/services/activity/popUpShopService.ts index c1703a34d..3817b286e 100644 --- a/game-server/app/services/activity/popUpShopService.ts +++ b/game-server/app/services/activity/popUpShopService.ts @@ -1,4 +1,4 @@ -import { ACTIVITY_TYPE, ITEM_CHANGE_REASON, POP_UP_SHOP_CONDITION_TYPE, STATUS } from '../../consts'; +import { ACTIVITY_TYPE, ITEM_CHANGE_REASON, POP_UP_SHOP_CONDITION_TYPE, PUSH_BATCH, PUSH_INTERVAL, STATUS } from '../../consts'; import { ActivityModel, ActivityModelType } from '../../db/Activity'; import { PopShopItem, PopUpConditionParamInter, PopUpShopData, PopUpShopPackage, PopUpShopItemShow, } from '../../domain/activityField/popUpShopField'; import { RewardParam } from '../../domain/activityField/rewardField'; @@ -9,6 +9,9 @@ import moment = require('moment'); import { getActivitiesByType, getActivityById, pushActivities, pushActivityInter, pushSingleActivity } from './activityService'; import { RoleModel, RoleType } from '../../db/Role'; import { HeroType } from '../../db/Hero'; +import { pinus } from 'pinus'; +import { getRandEelm, getRandSingleEelm } from '../../pubUtils/util'; +import { getAllOnlineRoles } from '../redisService'; /** * 玩家活动数据 @@ -44,12 +47,40 @@ export async function checkPopUpConditionInCreateHero(serverId: number, roleId: return await checkPopUpConditions(serverId, roleId, conditions); } -export async function checkPopUpCondition(serverId: number, roleId: string, conditionType: POP_UP_SHOP_CONDITION_TYPE, param: PopUpConditionParamInter) { - return await checkPopUpConditions(serverId, roleId, [{ conditionType, param }]); +export async function checkPopUpCondition(serverId: number, roleId: string, conditionType: POP_UP_SHOP_CONDITION_TYPE, param: PopUpConditionParamInter, sid?: string) { + return await checkPopUpConditions(serverId, roleId, [{ conditionType, param }], sid); +} + +export async function checkPopUpConditionInEntry(serverId: number, roleId: string, sid: string) { + let guilds = pinus.app.getServersByType('guild'); + let guild = getRandSingleEelm(guilds); + let isAuctionPopUpShow = await pinus.app.rpc.guild.guildActivityRemote.getAuctionPopUpShow.toServer(guild.id); + if(isAuctionPopUpShow) { + await checkPopUpCondition(serverId, roleId, POP_UP_SHOP_CONDITION_TYPE.AUCTION, {}, sid); + } +} + +export async function checkPopUpConditionWhenGuildActivityEnd() { + let allOnlineUsers = await getAllOnlineRoles(); + let n = Math.ceil(allOnlineUsers.length / PUSH_BATCH); // 一共多少批 + + let i = -1; + let interval = setInterval(async () => { + if (++i < n) { + console.log('****', i) + let users = allOnlineUsers.slice(i * PUSH_BATCH, (i + 1) * PUSH_BATCH - 1); + console.log('****', users) + for(let { serverId, roleId, sid } of users) { + await checkPopUpCondition(serverId, roleId, POP_UP_SHOP_CONDITION_TYPE.AUCTION, {}, sid); + } + } else { + clearInterval(interval); + } + }, PUSH_INTERVAL); } // 弹出礼包任务 -export async function checkPopUpConditions(serverId: number, roleId: string, conditions: { conditionType: POP_UP_SHOP_CONDITION_TYPE, param: PopUpConditionParamInter }[]) { +export async function checkPopUpConditions(serverId: number, roleId: string, conditions: { conditionType: POP_UP_SHOP_CONDITION_TYPE, param: PopUpConditionParamInter }[], sid?: string) { let activities = await getActivitiesByType(serverId, ACTIVITY_TYPE.POP_UP_SHOP); let pushData: pushActivityInter[] = []; for(let { activityId, type: activityType } of activities) { @@ -63,15 +94,23 @@ export async function checkPopUpConditions(serverId: number, roleId: string, con continue; } let items = pkg.getItemsByCondition(param); - console.log('##### 4', items, items.length) if(items.length > 0) { - let popUpShopRec = await ActivityPopUpShopModel.createRecord({ - serverId, activityId, roleId, id: pkg.id, - ...pkg.getEffectTime(), - items: items.map(item => new PopUpShopItem(item)) - }); - let result = pkg.pushPackage(popUpShopRec); - pushItems.push(...result); + let map = new Map(); + for(let item of items) { + if(!map.has(item.param)) { + map.set(item.param, []); + } + map.get(item.param).push(item); + } + for(let [_, items] of map) { + let popUpShopRec = await ActivityPopUpShopModel.createRecord({ + serverId, activityId, roleId, id: pkg.id, + ...pkg.getEffectTime(), + items: items.map(item => new PopUpShopItem(item)) + }); + let result = pkg.pushPackage(popUpShopRec); + pushItems.push(...result); + } } } } @@ -81,7 +120,7 @@ export async function checkPopUpConditions(serverId: number, roleId: string, con } } console.log('##### 6', pushData.length) - return await pushActivities(pushData, roleId); + return await pushActivities(pushData, roleId, sid); } diff --git a/game-server/app/services/playerCeService.ts b/game-server/app/services/playerCeService.ts index 47e44e71c..c6c2e75f4 100644 --- a/game-server/app/services/playerCeService.ts +++ b/game-server/app/services/playerCeService.ts @@ -38,12 +38,14 @@ interface Param { schoolHid?: number, preSchoolHid?: number, skinId?: number, + roleUpdate?: RoleUpdate, + roleIncUpdate?: RoleUpdate } export async function calculateCeWithHero(type: HERO_SYSTEM_TYPE, roleId: string, serverId: number, sid: string, hid: number, heroUpdate: HeroUpdate, param: Param = {}) { let heroUpdates = new Map(); heroUpdates.set(hid, heroUpdate); - let { heroes, curRole } = await calculateCes(type, roleId, serverId, sid, heroUpdates, {}, {}, param); + let { heroes, curRole } = await calculateCes(type, roleId, serverId, sid, heroUpdates, param.roleUpdate||{}, param.roleIncUpdate||{}, param); let curHero = heroes.find(cur => cur.hid == hid); return { heroes, curHero, curRole } } diff --git a/game-server/app/services/redisService.ts b/game-server/app/services/redisService.ts index 0aaa8bebb..2f6d14f60 100644 --- a/game-server/app/services/redisService.ts +++ b/game-server/app/services/redisService.ts @@ -311,9 +311,8 @@ export async function clearChannelServers() { * @param userCode user表唯一字符串标识 * @param sid connector服的那个sid */ -export async function roleLogin(roleId: string, userCode: string, sid: string, pkgName: string, createTime: number) { - let param = { userCode, sid, pkgName, createTime }; - return await redisClient().hsetAsync(REDIS_KEY.ONLINE_USERS, roleId, JSON.stringify(param)); +export async function roleLogin(roleId: string, userCode: string, sid: string, pkgName: string, createTime: number, serverId: number) { + return await redisClient().hsetAsync(REDIS_KEY.ONLINE_USERS, roleId, `${userCode}|${sid}|${pkgName}|${createTime}|${serverId}`); } /** @@ -343,13 +342,12 @@ export async function getRoleOnlineInfo(roleId: string) { let str = await redisClient().hgetAsync(REDIS_KEY.ONLINE_USERS, roleId); if(str) { try { - let result = JSON.parse(str); + let result = str.split('|'); + let [userCode, sid, pkgName, createTime, serverId] = result; return { - isOnline: true, - userCode: result.userCode, - sid: result.sid, - pkgName: result.pkgName, - createtime: result.createTime + isOnline: true, userCode, sid, pkgName, + createtime: parseInt(createTime), + serverId: parseInt(serverId) } } catch(e) { return { isOnline: false } @@ -375,15 +373,15 @@ export async function getRoleCreateTime(roleId: string, role?: RoleType) { * 获得所有在线的玩家 */ export async function getAllOnlineRoles() { - const client: Redis.RedisClient = pinus.app.get('redis'); let allRoles = await redisClient().hgetallAsync(REDIS_KEY.ONLINE_USERS); - let result = new Array<{roleId: string, userCode: string, sid: string, pkgName: string}>(); + let result = new Array<{roleId: string, userCode: string, sid: string, pkgName: string, serverId: number}>(); for(let roleId in allRoles) { try{ - let param = JSON.parse(allRoles[roleId]); - if(param) { - result.push({ roleId, userCode: param.userCode, sid: param.sid, pkgName: param.pkgName }); - } + let param = allRoles[roleId].split('|'); + let [userCode, sid, pkgName, _createTime, serverId] = param; + + result.push({ roleId, userCode, sid, pkgName, serverId: parseInt(serverId) }); + } catch(e) { continue; } diff --git a/game-server/app/services/task/taskService.ts b/game-server/app/services/task/taskService.ts index a7ce439ca..c09c139d9 100644 --- a/game-server/app/services/task/taskService.ts +++ b/game-server/app/services/task/taskService.ts @@ -1,7 +1,7 @@ import { RoleModel, RoleType } from '../../db/Role'; import { pinus, FrontendOrBackendSession } from 'pinus'; import { resResult, shouldRefresh } from '../../pubUtils/util'; -import { STATUS, TASK_TYPE, TASK_FUN_TYPE, SHOP_REFRESH_TYPE, WAR_TYPE, PUSH_ROUTE } from '../../consts'; +import { STATUS, TASK_TYPE, TASK_FUN_TYPE, SHOP_REFRESH_TYPE, WAR_TYPE, PUSH_ROUTE, POP_UP_SHOP_CONDITION_TYPE } from '../../consts'; import { TaskParamInter, TaskListReturn } from '../../domain/roleField/task'; import { EPlace, HeroType } from '../../db/Hero'; import { HeroScores } from '../../db/PvpHistoryOpp'; @@ -16,7 +16,7 @@ import { getActivities } from '../activity/activityService'; import { CheckTask } from './taskObj'; import { getEquipById } from '../equipService'; import { JewelType } from '../../db/Jewel'; -import { checkPopUpConditionInCreateHero } from '../activity/popUpShopService'; +import { checkPopUpCondition, checkPopUpConditionInCreateHero, checkPopUpConditionInEntry } from '../activity/popUpShopService'; import { sendMessageToUserWithSuc } from '../pushService'; export async function checkTaskWithRoles(serverId: number, roleId: string, sid: string, taskType: TASK_TYPE, roles: RoleType[]) { @@ -46,6 +46,7 @@ export async function checkTaskInEntry(serverId: number, roleId: string, sid: st task.setParam(TASK_TYPE.LOGIN_SUM, {}); task.setParam(TASK_TYPE.LOGIN_SERIES, {}); await task.saveAndPush(sid); + checkPopUpConditionInEntry(serverId, roleId, sid); } export async function checkTaskInCreateHero(serverId: number, roleId: string, sid: string, heroNum: number, heroes: HeroType[]) { @@ -122,6 +123,10 @@ export async function checkTaskInGuildTrain(serverId: number, roleId: string, si task.setParam(TASK_TYPE.BATTLE_EXPEDITION, { warId, count: 1 }); task.setParam(TASK_TYPE.BATTLE_MAIN_ELITE, { warId, count: 1 }); await task.saveAndPush(sid); + let dicWar = gameData.war.get(warId); + if(dicWar && dicWar.warType == WAR_TYPE.NORMAL) { + checkPopUpCondition(serverId, roleId, POP_UP_SHOP_CONDITION_TYPE.MAIN_BATTLE, { warId }, sid); + } } @@ -157,12 +162,15 @@ export async function checkTaskInComBattleStart(roleStatus: RoleStatus[], capId: } } -export async function checkTaskInPvpEnd(serverId: number, roleId: string, sid: string, isSuccess: boolean, heroScores: HeroScores[]) { +export async function checkTaskInPvpEnd(serverId: number, roleId: string, sid: string, isSuccess: boolean, heroScores: HeroScores[], myRank: number) { let task = new CheckTask(serverId, roleId); task.setParam(TASK_TYPE.PVP_WIN, { isSuccess }); task.setParam(TASK_TYPE.PVP_WIN_SERIES, { isSuccess }); task.setParam(TASK_TYPE.PVP_HERO_SCORE, { heroScores }); await task.saveAndPush(sid); + if(myRank) { + checkPopUpCondition(serverId, roleId, POP_UP_SHOP_CONDITION_TYPE.PVP, { rank: myRank }, sid); + } } export async function checkTaskInGacha(serverId: number, roleId: string, sid: string, count: number, heroes: HeroType[]) { @@ -207,7 +215,7 @@ export async function checkTaskInPutStone(serverId: number, roleId: string, sid: await task.saveAndPush(sid); } -export async function checkTaskInEquipStarUp(serverId: number, roleId: string, sid: string, oldEplace: EPlace[], newEplace: EPlace[], ePlaceId: number, hid: number, isUpStar: boolean) { +export async function checkTaskInEquipStarUp(serverId: number, roleId: string, sid: string, oldEplace: EPlace[], newEplace: EPlace[], ePlaceId: number, hid: number, isUpStar: boolean, equipStarSum: number) { let task = new CheckTask(serverId, roleId); if(isUpStar) { let { oldEquip, newEquip } = getEquipById(oldEplace, newEplace, ePlaceId); @@ -216,6 +224,9 @@ export async function checkTaskInEquipStarUp(serverId: number, roleId: string, s } task.setParam(TASK_TYPE.EQUIP_STAR_UP_CNT, { hid, ePlaceId }); await task.saveAndPush(sid); + if(isUpStar) { + checkPopUpCondition(serverId, roleId, POP_UP_SHOP_CONDITION_TYPE.EQUIP_STAR, { equipStar: equipStarSum }, sid); + } } export async function checkTaskInEquipQualityUp(serverId: number, roleId: string, sid: string, oldEplace: EPlace[], newEplace: EPlace[], ePlaceId: number, hid: number, isUpQuality: boolean) { diff --git a/game-server/app/services/timeTaskService.ts b/game-server/app/services/timeTaskService.ts index ff2758e79..991ba80fe 100644 --- a/game-server/app/services/timeTaskService.ts +++ b/game-server/app/services/timeTaskService.ts @@ -37,6 +37,7 @@ import { getActivitiesByType } from './activity/activityService'; import { ActivityGroupModel } from '../db/ActivityGroup'; import { sendMessageToServer } from './pushService'; import { resResult } from '../pubUtils/util'; +import { checkPopUpConditionWhenGuildActivityEnd } from './activity/popUpShopService'; const PER_SECOND = 1 * 1000; const PER_DAY = 24 * 60 * 60; @@ -297,6 +298,7 @@ export async function gateActivityEnd() { guildActEndJobId.cancel(); guildActEndJobId = undefined; } + checkPopUpConditionWhenGuildActivityEnd(); } // 每10秒下发一次的任务 @@ -325,6 +327,7 @@ export async function cityActivityEnd() { guildActEndJobId.cancel(); guildActEndJobId = undefined; } + checkPopUpConditionWhenGuildActivityEnd(); } // 每10秒下发一次的任务 @@ -348,6 +351,7 @@ export async function raceActivityEnd() { guildActEndJobId.cancel(); guildActEndJobId = undefined; } + checkPopUpConditionWhenGuildActivityEnd(); } // 每10秒下发一次的任务 diff --git a/shared/consts/constModules/activityConst.ts b/shared/consts/constModules/activityConst.ts index e4400a220..35569b8b1 100644 --- a/shared/consts/constModules/activityConst.ts +++ b/shared/consts/constModules/activityConst.ts @@ -178,6 +178,10 @@ export enum POP_UP_SHOP_CONDITION_TYPE { GET_HERO_BY_QUALITY = 2, // 获得某种品质的武将 GACHA_RES_NOT_ENOUGH = 3, // 点击抽卡资源不足 TERAPH_RES_NOT_ENOUGH = 4, // 神兽强化材料不足 + MAIN_BATTLE = 5, // 通关主线 + PVP = 6, // pvp + EQUIP_STAR = 7, // 装备精炼 + AUCTION = 8, // 拍卖行 } export enum POP_UP_SHOP_REFRESH_TIME_TYPE { diff --git a/shared/consts/constModules/sysConst.ts b/shared/consts/constModules/sysConst.ts index 3d1cd487a..6209bda6b 100644 --- a/shared/consts/constModules/sysConst.ts +++ b/shared/consts/constModules/sysConst.ts @@ -25,6 +25,7 @@ export const REF_CIRCLE_MAIL_TIME = 6; // 统一一天刷新定期邮件时间 export const PUSH_BATCH = 100; // 推送分批人数 export const PUSH_INTERVAL = 5 * 1000; // 分批时间,5秒一批 + export enum TIME_OUTPUT_TYPE { DATE = 1, STAMP_10 = 2, diff --git a/shared/db/Role.ts b/shared/db/Role.ts index c25b0ad80..bb5354b0e 100644 --- a/shared/db/Role.ts +++ b/shared/db/Role.ts @@ -180,6 +180,8 @@ export default class Role extends BaseModel { payRecord: PayRecord[]; // 支付记录 @prop({ required: true, default: 0 }) jewelCount: number; // 装备数量 + @prop({ required: true }) + equipStarSum: number; // 装备上的星级的数量 @prop({ required: true, default: 0 }) coin: number; // 总铜钱 diff --git a/shared/domain/activityField/popUpShopField.ts b/shared/domain/activityField/popUpShopField.ts index 0d7d1776e..cbc2d2458 100644 --- a/shared/domain/activityField/popUpShopField.ts +++ b/shared/domain/activityField/popUpShopField.ts @@ -307,6 +307,7 @@ export class PopUpShopPackage { // 礼包,购买以此为单位 export class PopShopItem { id: number; // 档位唯一id,升降档的时候按照id顺序排,升档id+,降档id- + param: string; conditionParam: number[]; // 条件类型的参数,填法根据conditionType表判断 price: number; // 商品价格 productID: string; // 商品id支付时使用 @@ -325,6 +326,7 @@ export class PopShopItem { constructor(data: PopUpShopItemInDb, parent: PopUpShopPackage) { this.id = data.id; + this.param = data.conditionParam; this.conditionParam = parseNumberList(data.conditionParam); this.price = data.price; this.productID = data.productID; @@ -361,6 +363,14 @@ export class PopShopItem { case POP_UP_SHOP_CONDITION_TYPE.GACHA_RES_NOT_ENOUGH: case POP_UP_SHOP_CONDITION_TYPE.TERAPH_RES_NOT_ENOUGH: return true; + case POP_UP_SHOP_CONDITION_TYPE.MAIN_BATTLE: + return param.warId == this.conditionParam[0]; + case POP_UP_SHOP_CONDITION_TYPE.PVP: + return param.rank <= this.conditionParam[0]; + case POP_UP_SHOP_CONDITION_TYPE.EQUIP_STAR: + return param.equipStar >= this.conditionParam[0]; + case POP_UP_SHOP_CONDITION_TYPE.AUCTION: + return true; default: return false; } @@ -425,6 +435,9 @@ export interface PopUpConditionParamInter { oldLv?: number; newLv?: number; quality?: number; + warId?: number; + rank?: number; + equipStar?: number; }