import { Level, Ranks, SeasonInfo, transUserDataform, transUserExtraDataform, UserRank } from "./NetBase"; import { NetRoom } from "./NetRoom"; import { NetTeam } from "./NetTeam"; import { TTWsClient } from "./TTWsClient"; import { WsClient } from "./WsClient"; import { WXWsClient } from "./WXWsClient"; /**自定义同步游戏变量(只能以r_或p_开头,按帧率同步,重新进入会收到最新的数据) * 只有第一个参数有效,可加上第一个参数跟第一个参数同类型,用于接收改变前的数据 * t_开头为队伍事件 * r_开头 主机游戏数据 主机权限 * revt_ 开头房间事件 * p_ 开头玩家数据 玩家权限和主机ai权限 * online 玩家在线下线状态 * 尽量拆分数据类型,不要设置过于复杂的数据类型*/ type protocol = { extra?: Record } & chsdk.OmitIndex export interface game_protocol extends protocol { t_entry(id: string, location: number, nickName: string): void;//队伍有人进入 t_exit(id: string, location: number, nickName: string): void;//队伍有人退出 t_host(id: string, location: number, nickName: string): void;//队长切换 t_chat(id: string, msg: string, location: number, nickName: string): void;//队伍聊天 t_online(id: string, online: boolean, location: number, nickName: string): void;//队伍玩家在线情况变化 t_ready(id: string, online: boolean, location: number, nickName: string): void;//队伍玩家准备情况变化 t_matchStart(): void;//匹配开始 t_matchCancel(): void;//匹配取消 t_matchSuccess(): void;//匹配成功 t_no_ready(): void;//有玩家未准备 t_closed(): void;//队伍解散(一般用于被踢出事件) // r_obj(key: string, data: any, old: any): void; r_closed(): void;//房间关闭 关闭后 获取 results 结算信息 r_host(id: string, location: number, nickName: string): void;//房主切换 r_chat(id: string, msg: string, location: number, nickName: string): void;//房间聊天 r_online(id: string, online: boolean, location: number, nickName: string): void;//房间玩家在线情况变化 r_finish(id: string, rank: number): void//房间某个玩家完成游戏获得名次 r_exit(id: string, location: number, nickName: string): void;//房间某个玩家离开 r_state(state: number, old_state?: number): void;//房间游戏状态 // p_obj(key: string, data: any, old: any): void; p_state(state: number, old_state?: number): void;//玩家游戏状态 p_extra(): void;//扩展数据改变 exit(): void;//玩家离开房间 finish(rank: number): void;//玩家完成游戏获得名次 online(online: boolean): void;//玩家断线重连消息 extra: Record; } /**网络状态*/ interface ws_event_protocol { Connecting(): void;//连接中 Reconnecting(reconnectCount: number): void;//重新连接中 Connected(): void;//进入大厅连接成功 Reconnected(): void;//重连成功 Error(err: any | string): void;//错误消息 Closing(): void;//关闭连接中 Closed(event: string): void;//连接已关闭 NextSeason(): void;//进入下一个赛季 } interface options { /**(可选)query连接token等数据*/query?: string, /**(可选) CA 证书字符串,用于 SSL 连接的验证*/ca?: string, /**(可选) 指示是否启用重连机制,默认为 `true`*/ reconnection?: boolean, /**(可选) 在连接失败后重连的最大次数,默认值为 `10`*/ reconnectCount?: number, /**(可选) 每次重连之间的间隔时间,以毫秒为单位,默认值为 `2000`*/ reconnectInterval?: number, /**(可选) 心跳断线时间,以毫秒为单位,默认值为 `30000`*/ timeoutInterval?: number, /**(可选) 游戏事件同步间隔,默认值为 `66` (0.06秒 1秒钟15次)*/ gameEvtInterval?: number, /**(可选) 游戏变量同步间隔,默认值为 `200` (0.2秒 1秒钟5次)*/ gameDataInterval?: number, /**(可选) 是否用json序列化否则用messagepck 默认为 `false`*/ json?: boolean } function isArrayBuffer(it: unknown): it is ArrayBuffer { try { return it instanceof ArrayBuffer } catch (_) { return false } } type JsonPrimitive = string | number | boolean | null; type Serializable = T extends JsonPrimitive ? T : T extends Date | Function | Symbol | undefined ? never : T extends Array ? SerializableArray : T extends object ? SerializableObject : never; type SerializableArray = Array>; type SerializableObject = { [K in keyof T]: Serializable; }; // const HEARTBEAT_INTERVAL = 1000; //每秒发送一次 Ping const HEARTBEAT_TIMEOUT = 80000; //80 秒的断线超时 const DIRTYDATACHECK_INTERVAL = 200;//每200毫秒同步一次自定义游戏变量 const EVTCHECK_INTERVAL = 66;//每66毫秒同步一次自定义游戏事件 const RECONNECT_COUNT = 10;//默认重连次数 const RECONNECT_INTERVAL = 2000;//默认重连间隔 const PING = 'ping'; const PONG = 'pong'; // const S2C_LOGIN_SUCCESS = 's2c_login_success'; const S2C_TEAM_SUCCESS = 's2c_team_success'; const S2C_JOIN_TEAM_FAIL = 's2c_join_team_fail'; const S2C_TEAM_USER_JOIN = 's2c_team_user_join'; const S2C_TEAM_USER_EXIT = 's2c_team_user_exit'; const S2C_TEAM_CHANGE_HOST = 's2c_team_change_host'; const S2C_USER_RECONNECT = 's2c_user_reconnect'; const S2C_USER_DISCONNECT = 's2c_user_disconnect'; const S2C_MATCH = 's2c_match'; const S2C_MATCH_SUCCESS = 's2c_match_success'; const S2C_MATCH_CANCEL = 's2c_match_cancel'; const S2C_ROOM_GAMEDATA_CHANGE = 's2c_room_gameData_change'; const S2C_USER_GAMEDATA_CHANGE = 's2c_user_gameData_change'; const S2C_ROOM_CHANGE_HOST = 's2c_room_change_host'; const S2C_ROOM_CLOSED = 's2c_room_closed'; const S2C_TALK = 's2c_talk'; const S2C_EXIT_ROOM = 's2c_exit_room'; const S2C_FINISH = 's2c_finish'; const S2C_READY = 's2c_ready'; const S2C_NOT_READY = 's2c_not_ready'; const S2C_RECEIVE_REWARD = 's2c_receive_reward'; const S2C_SEASON = 's2c_season'; const S2C_SET_USER_DATA_EXTRA = 's2c_set_user_data_extra'; const S2C_RANK_DATA = 's2c_rank_data'; // const C2S_MESSAGE = 'c2s_message'; const C2S_SET_TEAM = 'c2s_set_team'; const C2S_JOIN_TEAM = 'c2s_join_team'; const C2S_EXIT_TEAM = 'c2s_exit_team'; const C2S_CLOSE_ROOM = 'c2s_close_room'; const C2S_ROOM_EXIT = 'c2s_room_exit'; const C2S_BACKED_RETURN = 'c2s_backed_return'; const C2S_BACKED = 'c2s_to_backed'; const C2S_RECEIVE_REWARD = 'c2s_receive_reward'; const C2S_SEASON = 'c2s_season'; const C2S_SET_USER_DATA_EXTRA = 'c2s_set_user_data_extra'; const C2S_RANK_DATA = 'c2s_rank_data'; // const Ver: string = '0.6.2'; /**客户端*/ export class ch_net { /**连接状态事件*/ public state = chsdk.get_new_event(); private _ws: WsClient | TTWsClient | WXWsClient | null = null; private _url?: string | null; private _ca?: string; private reconnection: boolean; private reconnectCount: number; private reconnectInterval: number; private timeoutInterval: number; private gameDataInterval: number; private gameEvtInterval: number; private timeout: any | null = null; private season_timeout: any | null = null; private json: boolean = false; private heartbeatInterval: any | null = null; // 心跳定时器 private dirtyDataCheckInterval: any | null = null; //变量同步定时器 private evtCheckInterval: any | null = null; //事件同步定时器 private _currentReconnectCount: number = 0; private _pingTime: number = 0; private _ping: number = 0;//ping值 private _callbacks: { [id: string]: (result: any) => void } = {}; private _waitSends: Array = [];//| ArrayBufferLike | Blob | ArrayBufferView private _new_resolve(id: string): Promise { return new Promise(resolve => this._callbacks[id] = resolve); } private _lv: Level = { LowerRank: 0, Rank: 0, Star: 0 }; private _userKey: string; private _userDataExtra: GD['extra'] = null; /**玩家扩展数据 */ public get userDataExtra(): GD['extra'] { return this._userDataExtra; }; /**获取玩家扩展数据某个字段*/ public getUserDataExtraField(key: K): GD['extra'][K] { return this._userDataExtra?.[key]; } /**自己的段位信息 * @returns {Object}段位信息对象 */ public get ownLevel(): Level { return this._lv }; private _seasonInfo: any; /** * 赛季信息 包含当前赛季的基本信息以及排名历史。 * @returns {SeasonInfo} 赛季信息对象 */ public get seasonInfo(): SeasonInfo { return this._seasonInfo }; private _do_resolve(id: string, data: T): boolean { const resolveCallback = this._callbacks[id]; if (!resolveCallback) return false; resolveCallback(data); delete this._callbacks[id]; return true; } private _team: NetTeam | null; private _room: NetRoom | null; public get dataInterval(): number { return this.gameDataInterval * 0.001 }; public get evtInterval(): number { return this.gameEvtInterval * 0.001 }; public get isActive(): boolean { return this._ws && this._ws.isActive }; /**队伍信息*/ public get team(): NetTeam { return this._team; } /**是否在队伍中*/ public get inTeam(): boolean { return this._team != null && this._team.own != null; } /**房间信息*/ public get room(): NetRoom { return this._room; } /**是否在房间中*/ public get inRoom(): boolean { return this._room != null && !this._room.cloosed; } /** * 创建一个客户端 */ constructor() { this._waitSends = []; this._callbacks = {}; } /**初始化 * @param url - 连接的地址 'ws://localhost:3000' * @param options - 可选的配置选项对象,支持以下属性: * @example * 自定义消息 interface message extends message_protocol { useItem(userId: string, otherId: string, itemId: number): void//使用道具 } 自定义数据 export interface gd extends game_data { r_lv(lv: number);//关卡信息 r_time(time: number);//关卡时间 } export const net = ch.get_new_net(); const url = chsdk.getUrl('/handle').replace(chsdk.getUrl('/handle').split(':')[0], 'ws'); const token = chsdk.getToken(); net.init(url,{query: `token=${token}`}); */ public init(url: string, options?: options) { this._url = url; this._url = `${this._url}${options?.query?.length ? '?' + options.query : ''}`; this._ca = options?.ca; this.reconnection = options?.reconnection ?? true; this.reconnectCount = options?.reconnectCount ?? RECONNECT_COUNT; this.reconnectInterval = options?.reconnectInterval ?? RECONNECT_INTERVAL; this.timeoutInterval = options?.timeoutInterval ?? HEARTBEAT_TIMEOUT; this.gameDataInterval = options?.gameDataInterval ?? DIRTYDATACHECK_INTERVAL; this.gameEvtInterval = options?.gameEvtInterval ?? EVTCHECK_INTERVAL; this.json = options?.json ?? false; this._ws = new WsClient(this._url, this._ca); //if (chsdk.get_pf() === chsdk.pf.wx) this._ws = new WXWsClient(this._url, this._ca); //if (chsdk.get_pf() === chsdk.pf.tt) this._ws = new TTWsClient(this._url, this._ca); this._ws.onConnected = this._onConnected.bind(this); this._ws.onError = this._onError.bind(this); this._ws.onClosing = this._onClosing.bind(this); this._ws.onClosed = this._onClosed.bind(this); this._ws.onMessage = this._onMessage.bind(this); const isbuffer = typeof ArrayBuffer !== 'undefined'; console.log('ch_net 初始化 ver:', Ver, isbuffer, this.json, this._url); // chsdk.sdk_event.on('hide', this.on_hide, this); chsdk.sdk_event.on('show', this.on_show, this); } /**模拟断线*/ public SimulatedDisconnection(): void { this._ws.close(); } /**主动断开连接*/ public dispose(): void { this.state.clearAll(); this._clear_team(); this._clear_room(); this._callbacks = {}; this._waitSends.length = 0; this._ws.close(5000, 'client disconnect'); chsdk.sdk_event.off('hide', this.on_hide, this); chsdk.sdk_event.off('show', this.on_show, this); } private on_hide(): void { if (!this.inRoom) return; this.send_data(C2S_BACKED); } private on_show(): void { if (!this.inRoom) return; this.send_data(C2S_BACKED_RETURN); } private encode(msg: any): string | ArrayBuffer { return this.json ? JSON.stringify(msg) : msgpack.encode(msg).buffer; } private decode(medata: any): any { return this.json ? JSON.parse(medata) : msgpack.decode(new Uint8Array(medata)); } private send_data(type: string, data?: any): void { //console.log(type, data); const d = this.encode(data ? { type: type, data: data } : { type: type }); //console.log(d); this._send(d); } /**开始连接,返回null为成功,否则为错误提示*/ public async connect(): Promise { if (!this._ws) { chsdk.log.error('not init'); return 'not init'; } if (!this._ws.connect()) return 'no netconnect'; this.state.emit(this.state.key.Connecting); return this._new_resolve('connect'); } /**主动重新连接*/ public reconnect(): string | null { if (!this._ws) { chsdk.log.error('not init'); return 'not init'; } this._currentReconnectCount = 1; this.do_reconnect(); } private do_reconnect(): void { this._userKey = null; this.state.emit(this.state.key.Reconnecting, this._currentReconnectCount); this._ws.connect(); } /**玩家创建队伍,返回null为成功,否则为错误提示*/ public async creat_team(player_count: number = 4): Promise { if (!this._ws) return 'No network connection'; if (this.inTeam) return null; this.send_data(C2S_SET_TEAM, player_count); return this._new_resolve('creat_team'); } /**玩家进入队伍,返回null为成功,否则为错误提示*/ public async join_team(password: string): Promise { if (!this._ws) return 'No network connection'; if (this.inTeam) return null; this.send_data(C2S_JOIN_TEAM, { password: password }); return this._new_resolve('join_team'); } /**玩家退出队伍,返回null为成功,否则为错误提示*/ public async exit_team(): Promise { try { if (!this._ws) return 'No network connection'; if (!this.inTeam) return 'Not in team'; if (this.inRoom) return 'in game'; this.send_data(C2S_EXIT_TEAM); return this._new_resolve('exit_team'); } catch (error) { return 'Unexpected error occurred'; } } /**主机结束游戏关闭房间,成功后收到房间关闭事件*/ public close_room(): void { if (!this._ws) return; if (!this.inRoom) return; if (!this.room.isHost) return; this.send_data(C2S_CLOSE_ROOM); } /**玩家退出房间 ,返回null为成功,否则为错误提示,退出成功后收到房间关闭事件*/ public async exit_room(): Promise { try { if (!this._ws) return 'No network connection'; if (!this.inTeam) return 'Not in team'; if (!this.inRoom) return 'Not in room'; this.send_data(C2S_ROOM_EXIT); return this._new_resolve('exit_room'); } catch (error) { return 'Unexpected error occurred'; } } private _cache_rank: Ranks | null = null; private _cache_rank_time: number = 0; /**获取排行榜行信息 如果是string,就是错误信息,否则为排行榜信息*/ public async getRankData(): Promise> { if (chsdk.date.now() - this._cache_rank_time >= 30000) { this._cache_rank = null; } if (this._cache_rank) return this._cache_rank; if (!this._ws) return 'No network connection'; this.send_data(C2S_RANK_DATA); return this._new_resolve('get_rank_data'); } /**强制转化成用护排行类型*/ public asUserRankType = UserRank>(data: any): T { return data as T; } /**领取赛季奖励 ,返回null为成功,否则为错误提示*/ public async receive_reward(season: number): Promise { if (!this._ws) return 'No network connection'; this.send_data(C2S_RECEIVE_REWARD, season); return this._new_resolve('receive_reward'); } /**主动刷新赛季信息,返回null为成功,否则为错误提示*/ public async update_season(): Promise { if (!this._ws) return 'No network connection'; this.send_data(C2S_SEASON); return this._new_resolve('update_season'); } /**更新设置玩家整个扩展数据,返回null为成功,否则为错误提示*/ public async setUserExtra(data: GD['extra'] & SerializableObject): Promise { if (!this._ws) return 'No network connection'; this._userDataExtra = data; this.send_data(C2S_SET_USER_DATA_EXTRA, JSON.stringify(data)); if (this.inTeam) (this.team.own as any).set_userExtra(this._userDataExtra); if (this.inRoom) (this.room.own as any).set_userExtra(this._userDataExtra); return this._new_resolve('set_user_extra'); } /**更新设置玩家扩展数据某个字段的值 */ public async setUserExtraField(key: K, value: GD['extra'][K] & SerializableObject): Promise { this._userDataExtra ??= {}; this._userDataExtra[key] = value; return this.setUserExtra(this._userDataExtra); } // private _send(data: string | ArrayBuffer) { if (!this._ws) return; if (this._ws.isActive) { this._ws.send(data); } else { this._waitSends.push(data); } } // private _clear_team(): void { this._team?.dispose(); this._team = null; } private _clear_room(): void { this._room?.dispose(); this._room = null; } private _temp_team: any | null; private _team_info(data?: any | null) { this._clear_team(); if (!this._userKey) { this._temp_team = data; } else if (this._temp_team || data) { data ??= this._temp_team; this._team = new NetTeam(this._userKey, data, this.send_data.bind(this)); if (!this._do_resolve('creat_team', null)) this._do_resolve('join_team', null); this._temp_team = null; } } private _temp_match_data: any | null; private _match_success(data?: any | null): void { this._clear_room(); if (!this._userKey) { this._temp_match_data = data; } else if (this._temp_match_data || data) { data ??= this._temp_match_data; const roomData = data.roomData; const pData = data.playerData; this._room = new NetRoom(this._userKey, roomData, pData, this.send_data.bind(this)); if (this._team) (this._team as any).match_success(); this._temp_match_data = null; } } private _updatePlayerStatus(id: string, online: boolean) { if (this.inTeam) (this._team as any).updatePlayerStatus(id, online); if (this.inRoom) (this._room as any).updatePlayerStatus(id, online); } // private _onMessage(msg: string | ArrayBuffer): void { this.resetHeartbeat(); try { const data = this.decode(msg); if (data.type !== PONG) chsdk.log.log('receive', JSON.stringify(data)); switch (data.type) { case PONG: this._ping = chsdk.date.now() - this._pingTime; chsdk.date.updateServerTime(data.data); //chsdk.log.log("pong:", this._ping, data.data, chsdk.date.now()); break; case S2C_LOGIN_SUCCESS: this._lv = data.data.level; this._userKey = data.data.userKey; this._seasonInfo = data.data.seasonInfo; this._userDataExtra = transUserExtraDataform(data.data.userDataExtra); this._team_info(); this._match_success(); // if (this._do_resolve('connect', null)) { this.state.emit(this.state.key.Connected); } else { this.state.emit(this.state.key.Reconnected); } this._callbacks = {}; this.startHeartbeat(); while (this._waitSends.length) { if (!this._ws) break; const data = this._waitSends.shift(); if (data) this._ws.send(data); } break; case S2C_TEAM_SUCCESS: this._team_info(data.data); break; case S2C_JOIN_TEAM_FAIL: this._do_resolve('join_team', data.data); break; case S2C_TEAM_USER_JOIN: const join_user = data.data; join_user.status = true; (this._team as any).addPlayer({ key: join_user.userKey, data: join_user }, true); break; case S2C_TEAM_USER_EXIT: const esit_id = data.data; if (esit_id === this._team.own.Id) { (this._team as any).closed(); this._clear_team(); this._do_resolve('exit_team', null); } else { (this._team as any).removePlayer(esit_id); } break; case S2C_TEAM_CHANGE_HOST: if (this.inTeam) (this._team as any).changeHost(data.data); break; case S2C_ROOM_CHANGE_HOST: if (this.inRoom) (this._room as any).changeHost(data.data); break; case S2C_USER_RECONNECT: this._updatePlayerStatus(data.data, true); break; case S2C_USER_DISCONNECT: this._updatePlayerStatus(data.data, false); break; case S2C_MATCH: (this._team as any).match_start(); break; case S2C_MATCH_CANCEL: (this._team as any).match_cancel(); break; case S2C_NOT_READY: (this._team as any).not_ready(); break; case S2C_READY: (this._team as any).updatePlayerReady(data.data, true); break; case S2C_MATCH_SUCCESS: this._match_success(data.data); break; case S2C_ROOM_GAMEDATA_CHANGE: (this._room as any).server_change(data.data); break; case S2C_USER_GAMEDATA_CHANGE: const p = this._room.getPlayer(data.data.userKey); if (!p) break; (p as any).server_change(data.data.data); break; case S2C_ROOM_CLOSED: (this._room as any).closed(data.data); const list = this.room.all; for (let i = 0; i < list.length; i++) { const rp = list[i]; if (rp.isAI) continue; if (rp.isOwn) { this._lv.LowerRank = rp.level.LowerRank; this._lv.Rank = rp.level.Rank; this._lv.Star = rp.level.Star; } if (this.inTeam) { const p = this.team.getPlayer(rp.Id); if (!p) continue; (p as any).set_level(rp.level, rp.rank, rp.score, rp.totalRank); } } this._do_resolve('exit_room', null); break; case S2C_TALK: if (this.inRoom) { (this._room as any).onChat(data.data.userKey, data.data.msg); } else if (this.inTeam) { (this._team as any).onChat(data.data.userKey, data.data.msg); } break; case S2C_FINISH: (this._room as any).finish(data.data.userKey, data.data.rank); break; case S2C_EXIT_ROOM: (this._room as any).exit(data.data); break; case S2C_RECEIVE_REWARD: this._do_resolve('receive_reward', null); break; case S2C_SEASON: this._seasonInfo = data.data; this._do_resolve('update_season', null); this.state.emit('NextSeason'); break; case S2C_SET_USER_DATA_EXTRA: this._do_resolve('set_user_extra', null); break; case S2C_RANK_DATA: this._cache_rank_time = chsdk.date.now(); const r = data.data; const rr = r.ownRank; const ownRank: UserRank = rr; ownRank.UserData = transUserDataform(rr.UserData); ownRank.UserDataExtra = transUserExtraDataform(rr.UserDataExtra); const elementsRank: UserRank[] = []; for (let i = 0; i < r.elementsRank.length; i++) { const rr = r.elementsRank[i]; rr.UserData = transUserDataform(rr.UserData); rr.UserDataExtra = transUserExtraDataform(rr.UserDataExtra); elementsRank.push(rr); } this._cache_rank = { list: elementsRank, own: ownRank }; this._do_resolve('get_rank_data', this._cache_rank); break; case C2S_MESSAGE: if (Array.isArray(data.data)) { if (this.inRoom) { for (let i = 0; i < data.data.length; i++) { const d = data.data[i]; (this._room as any)._onEvt(d.type, d.data); } } } else { chsdk.log.log('no def type:', data.data); //(this.receive as any)._emit(data.type, data.data); } break; default: break; } } catch (error) { chsdk.log.warn(error) this.state.emit(this.state.key.Error, error); } } private _onConnected(evt: any): void { if (this.timeout) clearTimeout(this.timeout); this._currentReconnectCount = 0; this.timeout = setTimeout(() => { chsdk.log.warn("Closing connection whit no replay logoin!"); this._ws.close(); }, 3000); } private _onError(err: any): void { chsdk.log.log('err', err); this.state.emit(this.state.key.Error, err); } private _onClosing(): void { chsdk.log.log('closing'); this.state.emit(this.state.key.Closing); } private _onClosed(code: number, reason: string): void { code ??= 0; reason ??= ''; chsdk.log.log("WebSocket connection closed", code, reason); this.stopHeartbeat(); if (!this.reconnection || (reason && code > 4000)) { this.state.emit(this.state.key.Closed, reason); this._do_resolve('connect', reason); chsdk.log.log("WebSocket connection closed", reason); return; } if (this._currentReconnectCount < this.reconnectCount) { this._currentReconnectCount += 1; chsdk.log.log(`Reconnecting... Attempt ${this._currentReconnectCount}`); setTimeout(() => { this.do_reconnect() }, this.reconnectInterval) } else { this._clear_team(); this._clear_room(); chsdk.log.error("Max reconnect attempts reached. Giving up."); this.state.emit(this.state.key.Closed, reason); this._do_resolve('connect', reason); } } private startHeartbeat() { this.heartbeatInterval = setInterval(() => { this.sendHeartbeat(); }, HEARTBEAT_INTERVAL); this.dirtyDataCheckInterval = setInterval(() => { this.dirtyDataCheck(); }, this.gameDataInterval); this.evtCheckInterval = setInterval(() => { this.evtCheck(); }, this.gameEvtInterval); if (this.season_timeout) clearTimeout(this.season_timeout); this.season_timeout = setTimeout(() => { this.update_season(); }, this.seasonInfo.SeasonEndTime - chsdk.date.now() + Math.floor(Math.random() * 2000)); } private resetHeartbeat() { if (this.timeout) clearTimeout(this.timeout); this.timeout = setTimeout(() => { chsdk.log.error("Heartbeat timeout. Closing connection."); this._ws.close(); }, this.timeoutInterval); } private sendHeartbeat() { this._pingTime = chsdk.date.now(); this.send_data(PING, this._ping); } private stopHeartbeat() { if (this.heartbeatInterval) clearInterval(this.heartbeatInterval); if (this.timeout) clearTimeout(this.timeout); if (this.dirtyDataCheckInterval) clearInterval(this.dirtyDataCheckInterval); if (this.evtCheckInterval) clearInterval(this.evtCheckInterval); if (this.season_timeout) clearTimeout(this.season_timeout); } private evtCheck() { if (this.inRoom) { (this.room as any).doSendChat(); } else if (this.inTeam) { (this.team as any).doSendChat(); } } private dirtyDataCheck() { if (!this.inRoom) return; this.room.doSendDirty(); } /** * 分享队伍信息,邀请进队 * @param extend (可选)分享数据 * @param title 分享显示标题 * @param imageUrl 分享显示图片 */ public shareNetTeam(title?: string, imageUrl?: string, extend?: T) { if (!this.inTeam) return; chsdk.shareAppMessage(title, '', imageUrl, JSON.stringify({ type: 'NetTeam', pw: this.team.Password, extends: extend })); } /** *获取从好友分享的net数据进入游戏的数据 */ public getShareNetTeam(): { password: string, extend?: T } { const query = chsdk.getQuery(); if (!query) return null; const message = query.message; if (!message) return null; const data = JSON.parse(message); if (!data) return null; if (data.type != 'NetTeam') return null; query.message = null; return { password: data.pw, extend: data.extends as T }; } }