net.ts 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. import { NetRoom } from "./NetRoom";
  2. import { NetTeam } from "./NetTeam";
  3. import { WsClient } from "./WsClient";
  4. /**自定义同步游戏变量(只能以r_或p_开头,按帧率同步,重新进入会收到最新的数据)
  5. * 只有第一个参数有效,可加上第一个参数跟第一个参数同类型,用于接收改变前的数据
  6. * t_开头为队伍事件
  7. * r_开头 主机游戏数据 主机权限
  8. * revt_ 开头房间事件
  9. * p_ 开头玩家数据 玩家权限和主机ai权限
  10. * online 玩家在线下线状态
  11. * 尽量拆分数据类型,不要设置过于复杂的数据类型*/
  12. export interface game_protocol extends chsdk.OmitIndex<chsdk.EventsMap> {
  13. t_entry(id: string, location: number, nickName: string): void;//队伍有人进入
  14. t_exit(id: string, location: number, nickName: string): void;//队伍有人退出
  15. t_host(id: string, location: number, nickName: string): void;//队长切换
  16. t_chat(id: string, msg: string, location: number, nickName: string): void;//队伍聊天
  17. t_online(id: string, online: boolean, location: number, nickName: string): void;//队伍玩家在线情况变化
  18. t_ready(id: string, online: boolean, location: number, nickName: string): void;//队伍玩家准备情况变化
  19. t_matchStart(): void;//匹配开始
  20. t_matchCancel(): void;//匹配取消
  21. t_matchSuccess(): void;//匹配成功
  22. t_no_ready(): void;//有玩家未准备
  23. t_closed(): void;//队伍解散(一般用于被踢出事件)
  24. //
  25. r_obj(key: string, data: any, old: any): void;
  26. r_closed(): void;//房间关闭 关闭后 获取 results 结算信息
  27. r_host(id: string, location: number, nickName: string): void;//房主切换
  28. r_chat(id: string, msg: string, location: number, nickName: string): void;//房间聊天
  29. r_online(id: string, online: boolean, location: number, nickName: string): void;//房间玩家在线情况变化
  30. r_finish(id: string, rank: number): void//房间某个玩家完成游戏获得名次
  31. r_state(state: number, old_state?: number): void;//房间游戏状态
  32. p_state(state: number, old_state?: number): void;//玩家游戏状态
  33. finish(rank: number): void;//玩家完成游戏获得名次
  34. online(online: boolean): void//玩家断线重连消息
  35. }
  36. /**网络状态*/
  37. interface ws_event_protocol {
  38. Connecting(): void;//连接中
  39. Reconnecting(reconnectCount: number): void;//重新连接中
  40. Connected(): void;//进入大厅连接成功
  41. Reconnected(): void;//重连成功
  42. Error(err: any | string): void;//错误消息
  43. Closing(): void;//关闭连接中
  44. Closed(event: CloseEvent): void;//连接已关闭
  45. }
  46. function isArrayBuffer(it: unknown): it is ArrayBuffer {
  47. try {
  48. return it instanceof ArrayBuffer
  49. } catch (_) {
  50. return false
  51. }
  52. }
  53. const HEARTBEAT_INTERVAL = 1000; //每秒发送一次 Ping
  54. const HEARTBEAT_TIMEOUT = 30000; //30 秒的断线超时
  55. const DIRTYDATACHECK_INTERVAL = 200;//每200毫秒同步一次自定义游戏变量
  56. const EVTCHECK_INTERVAL = 66;//每66毫秒同步一次自定义游戏事件
  57. const RECONNECT_COUNT = 10;//默认重连次数
  58. const RECONNECT_INTERVAL = 2000;//默认重连间隔
  59. const PING = 'ping';
  60. const PONG = 'pong';
  61. //
  62. const S2C_LOGIN_SUCCESS = 's2c_login_success';
  63. const S2C_TEAM_SUCCESS = 's2c_team_success';
  64. const S2C_JOIN_TEAM_FAIL = 's2c_join_team_fail';
  65. const S2C_TEAM_USER_JOIN = 's2c_team_user_join';
  66. const S2C_TEAM_USER_EXIT = 's2c_team_user_exit';
  67. const S2C_TEAM_CHANGE_HOST = 's2c_team_change_host';
  68. const S2C_USER_RECONNECT = 's2c_user_reconnect';
  69. const S2C_USER_DISCONNECT = 's2c_user_disconnect';
  70. const S2C_MATCH = 's2c_match';
  71. const S2C_MATCH_SUCCESS = 's2c_match_success';
  72. const S2C_MATCH_CANCEL = 's2c_match_cancel';
  73. const S2C_ROOM_GAMEDATA_CHANGE = 's2c_room_gameData_change';
  74. const S2C_USER_GAMEDATA_CHANGE = 's2c_user_gameData_change';
  75. const S2C_ROOM_CHANGE_HOST = 's2c_room_change_host';
  76. const S2C_ROOM_CLOSED = 's2c_room_closed';
  77. const S2C_TALK = 's2c_talk';
  78. const S2C_FINISH = 's2c_finish';
  79. const S2C_SETTLEMENT = 's2c_settlement';
  80. const S2C_READY = 's2c_ready';
  81. const S2C_NOT_READY = 's2c_not_ready';
  82. //
  83. const C2S_UPDATE_USER_GAMEDATA = 'c2s_update_user_gameData';
  84. const C2S_UPDATE_ROOM_GAMEDATA = 'c2s_update_room_gameData';
  85. const C2S_SET_TEAM = 'c2s_set_team';
  86. const C2S_JOIN_TEAM = 'c2s_join_team';
  87. const C2S_EXIT_TEAM = 'c2s_exit_team';
  88. const C2S_CLOSE_ROOM = 'c2s_close_room';
  89. const C2S_ROOM_EXIT = 'c2s_room_exit';
  90. const C2S_MESSAGE = 'c2s_message';
  91. const C2S_MESSAGE_TO_HOST = 'c2s_message_to_host';
  92. const C2S_MESSAGE_TO_OTHER = 'c2s_message_without_self';
  93. const C2S_TALK = 'c2s_talk';
  94. const C2S_BACKED_RETURN = 'c2s_backed_return';
  95. const C2S_BACKED = 'c2s_to_backed';
  96. const C2S_FINISH = 'c2s_finish';
  97. //
  98. const Ver: string = '0.1.8';
  99. /**客户端*/
  100. export class ch_net<GD extends game_protocol = game_protocol> {
  101. /**连接状态事件*/
  102. public state = chsdk.get_new_event<ws_event_protocol>();
  103. private _ws: WsClient | null = null;
  104. private _url?: string | null;
  105. private _ca?: string;
  106. private reconnection: boolean;
  107. private reconnectCount: number;
  108. private reconnectInterval: number;
  109. private timeoutInterval: number;
  110. private gameDataInterval: number;
  111. private gameEvtInterval: number;
  112. private timeout: any | null = null;
  113. private json: boolean = false;
  114. private heartbeatInterval: any | null = null; // 心跳定时器
  115. private dirtyDataCheckInterval: any | null = null; //变量同步定时器
  116. private evtCheckInterval: any | null = null; //事件同步定时器
  117. private _currentReconnectCount: number = 0;
  118. private _pingTime: number = 0;
  119. private _ping: number = 0;//ping值
  120. private _callbacks: { [id: string]: (result: any) => void } = {};
  121. private _waitSends: Array<string | ArrayBufferLike | Blob | ArrayBufferView> = [];
  122. private _new_resolve<T>(id: string): Promise<T> { return new Promise(resolve => this._callbacks[id] = resolve); }
  123. private _lv: { LowerRank: number, Rank: number, Star: number } = { LowerRank: 0, Rank: 0, Star: 0 };
  124. public get ownLevel(): { LowerRank: number, Rank: number, Star: number } { return this._lv };
  125. private _do_resolve<T>(id: string, data: T): boolean {
  126. const resolveCallback = this._callbacks[id];
  127. if (!resolveCallback) return false;
  128. resolveCallback(data);
  129. delete this._callbacks[id];
  130. return true;
  131. }
  132. private _team: NetTeam<GD> | null;
  133. private _room: NetRoom<GD> | null;
  134. public get dataInterval(): number { return this.gameDataInterval * 0.001 };
  135. public get evtInterval(): number { return this.gameEvtInterval * 0.001 };
  136. public get isActive(): boolean { return this._ws && this._ws.isActive };
  137. /**队伍信息*/
  138. public get team(): NetTeam<GD> {
  139. return this._team;
  140. }
  141. /**是否在队伍中*/
  142. public get inTeam(): boolean {
  143. return this._team != null && this._team.own != null;
  144. }
  145. /**房间信息*/
  146. public get room(): NetRoom<GD> {
  147. return this._room;
  148. }
  149. /**是否在房间中*/
  150. public get inRoom(): boolean {
  151. return this._room != null && !this._room.cloosed;
  152. }
  153. /**
  154. * 创建一个客户端
  155. */
  156. constructor() {
  157. this._waitSends = [];
  158. this._callbacks = {};
  159. }
  160. /**初始化
  161. * @param url - 连接的地址 'ws://localhost:3000'
  162. * @param options - 可选的配置选项对象,支持以下属性:
  163. * - **ca**: (可选) CA 证书字符串,用于 SSL 连接的验证。
  164. * - **reconnection**: (可选) 指示是否启用重连机制,默认为 `true`。
  165. * - **reconnectCount**: (可选) 在连接失败后重连的最大次数,默认值为 `10`。
  166. * - **reconnectInterval**: (可选) 每次重连之间的间隔时间,以毫秒为单位,默认值为 `2000` (2 秒)。
  167. * - **timeoutInterval**: (可选) 心跳断线时间,以毫秒为单位,默认值为 `30000` (30 秒)。
  168. * - **gameEvtInterval**:(可选) 游戏事件同步间隔,默认值为 `66` (0.06秒 1秒钟15次)。
  169. * - **gameDataInterval**:(可选) 游戏变量同步间隔,默认值为 `200` (0.2秒 1秒钟5次)。
  170. * - **json**: (可选) 是否用json序列化否则用messagepck 默认为 `false`。
  171. * @example
  172. * 自定义消息
  173. interface message extends message_protocol {
  174. useItem(userId: string, otherId: string, itemId: number): void//使用道具
  175. }
  176. 自定义数据
  177. export interface gd extends game_data {
  178. r_lv(lv: number);//关卡信息
  179. r_time(time: number);//关卡时间
  180. }
  181. export const net = ch.get_new_net<gd, message>();
  182. const url = chsdk.getUrl('/handle').replace(chsdk.getUrl('/handle').split(':')[0], 'ws');
  183. const token = chsdk.getToken();
  184. net.init(url,{query: `token=${token}`});
  185. */
  186. public init(url: string, options?: { query?: string, ca?: string, reconnection?: boolean, reconnectCount?: number, reconnectInterval?: number, timeoutInterval?: number, gameEvtInterval?: number, gameDataInterval?: number, json?: boolean }) {
  187. this._url = url;
  188. this._url = `${this._url}${options?.query?.length ? '?' + options.query : ''}`;
  189. this._ca = options?.ca;
  190. this.reconnection = options?.reconnection ?? true;
  191. this.reconnectCount = options?.reconnectCount ?? RECONNECT_COUNT;
  192. this.reconnectInterval = options?.reconnectInterval ?? RECONNECT_INTERVAL;
  193. this.timeoutInterval = options?.timeoutInterval ?? HEARTBEAT_TIMEOUT;
  194. this.gameDataInterval = options?.gameDataInterval ?? DIRTYDATACHECK_INTERVAL;
  195. this.gameEvtInterval = options?.gameEvtInterval ?? EVTCHECK_INTERVAL;
  196. this.json = options?.json ?? false;
  197. this._ws = new WsClient(this._url, this._ca);
  198. this._ws.onConnected = this._onConnected.bind(this);
  199. this._ws.onError = this._onError.bind(this);
  200. this._ws.onClosing = this._onClosing.bind(this);
  201. this._ws.onClosed = this._onClosed.bind(this);
  202. this._ws.onMessage = this._onMessage.bind(this);
  203. const isbuffer = typeof ArrayBuffer !== 'undefined';
  204. console.log('ch_net 初始化 ver:', Ver, isbuffer, this.json, this._url);
  205. //
  206. chsdk.sdk_event.on('hide', this.on_hide, this);
  207. chsdk.sdk_event.on('show', this.on_show, this);
  208. }
  209. /**主动断开连接*/
  210. public dispose(): void {
  211. this.state.clearAll();
  212. this._clear_team();
  213. this._clear_room();
  214. this._callbacks = {};
  215. this._waitSends.length = 0;
  216. this._ws.close(5000, 'client disconnect');
  217. chsdk.sdk_event.off('hide', this.on_hide, this);
  218. chsdk.sdk_event.off('show', this.on_show, this);
  219. }
  220. private on_hide(): void {
  221. if (!this.inRoom) return;
  222. this.send_data(C2S_BACKED);
  223. }
  224. private on_show(): void {
  225. if (!this.inRoom) return;
  226. this.send_data(C2S_BACKED_RETURN);
  227. }
  228. private encode(msg: any): any {
  229. return this.json ? JSON.stringify(msg) : msgpack.encode(msg);
  230. }
  231. private decode(medata: any): any {
  232. return isArrayBuffer(medata) ? msgpack.decode(new Uint8Array(medata)) : JSON.parse(medata);
  233. }
  234. private send_data(type: string, data?: any): void {
  235. this._send(this.encode(data ? { type: type, data: data } : { type: type }));
  236. }
  237. /**开始连接,返回null为成功,否则为错误提示*/
  238. public async connect(): Promise<string | null> {
  239. if (!this._ws) {
  240. chsdk.log.error('not init');
  241. return 'not init';
  242. }
  243. if (!this._ws.connect()) return 'no netconnect';
  244. this.state.emit(this.state.key.Connecting);
  245. return this._new_resolve('connect');
  246. }
  247. /**主动重新连接*/
  248. public reconnect(): string | null {
  249. if (!this._ws) {
  250. chsdk.log.error('not init');
  251. return 'not init';
  252. }
  253. this._currentReconnectCount = 1;
  254. this.do_reconnect();
  255. }
  256. private do_reconnect(): void {
  257. if (!this._ws.connect()) return;
  258. this.state.emit(this.state.key.Reconnecting, this._currentReconnectCount);
  259. }
  260. /**玩家创建队伍,返回null为成功,否则为错误提示*/
  261. public async creat_team(player_count: number = 4): Promise<string | null> {
  262. if (!this._ws) return 'no netconnect';
  263. if (this.inTeam) return null;
  264. this.send_data(C2S_SET_TEAM, player_count);
  265. return this._new_resolve('creat_team');
  266. }
  267. /**玩家进入队伍,返回null为成功,否则为错误提示*/
  268. public async join_team(password: string): Promise<string | null> {
  269. if (!this._ws) return 'no netconnect';
  270. if (this.inTeam) return null;
  271. this.send_data(C2S_JOIN_TEAM, { password: password });
  272. return this._new_resolve('join_team');
  273. }
  274. /**玩家退出队伍,返回null为成功,否则为错误提示*/
  275. public async exit_team(): Promise<string | null> {
  276. if (!this._ws) return 'no netconnect';
  277. if (!this.inTeam) return 'not in team';
  278. if (this.inRoom) return 'in game';
  279. this.send_data(C2S_EXIT_TEAM);
  280. return this._new_resolve('exit_team');
  281. }
  282. /**主机结束游戏关闭房间,成功后收到房间关闭事件*/
  283. public close_room(): void {
  284. if (!this._ws) return;
  285. if (!this.inRoom) return;
  286. if (!this.room.isHost) return;
  287. this.send_data(C2S_CLOSE_ROOM);
  288. }
  289. /**玩家退出房间 ,返回null为成功,否则为错误提示,退出成功后收到房间关闭事件*/
  290. public async exit_room(): Promise<string | null> {
  291. if (!this._ws) return 'no netconnect';
  292. if (!this.inRoom) return 'not in room';
  293. this.send_data(C2S_ROOM_EXIT);
  294. return this._new_resolve('exit_room');
  295. }
  296. private _clear_team(): void {
  297. this._team?.dispose();
  298. this._team = null;
  299. }
  300. private _clear_room(): void {
  301. if (!this._room) return;
  302. this._room?.dispose();
  303. this._room = null;
  304. }
  305. private _send(data: Parameters<WebSocket['send']>[0]) {
  306. if (!this._ws) return;
  307. if (this._ws.isActive) {
  308. this._ws.send(data);
  309. } else {
  310. this._waitSends.push(data);
  311. }
  312. }
  313. private _updatePlayerStatus(id: string, online: boolean) {
  314. if (this.inTeam) (this._team as any).updatePlayerStatus(id, online);
  315. if (this.inRoom) (this._room as any).updatePlayerStatus(id, online);
  316. }
  317. //
  318. private _onMessage(msg: MessageEvent): void {
  319. this.resetHeartbeat();
  320. try {
  321. const { data: medata } = msg;
  322. const data = this.decode(medata);
  323. if (data.type !== PONG) chsdk.log.log('receive', JSON.stringify(data));
  324. switch (data.type) {
  325. case PONG:
  326. this._ping = chsdk.date.now() - this._pingTime;
  327. chsdk.date.updateServerTime(data.data);
  328. //chsdk.log.log("pong:", this._ping, data.data, chsdk.date.now());
  329. break;
  330. case S2C_LOGIN_SUCCESS:
  331. this._lv = data.data;
  332. if (this._do_resolve('connect', null)) {
  333. this.state.emit(this.state.key.Connected);
  334. } else {
  335. this.state.emit(this.state.key.Reconnected);
  336. }
  337. this._callbacks = {};
  338. this.startHeartbeat();
  339. while (this._waitSends.length) {
  340. if (!this._ws) break;
  341. const data = this._waitSends.shift();
  342. if (data) this._ws.send(data);
  343. }
  344. break;
  345. case S2C_TEAM_SUCCESS:
  346. if (this._team) this._clear_team();
  347. this._team = new NetTeam<GD>(data.data, this.send_data.bind(this));
  348. if (!this._do_resolve('creat_team', null)) this._do_resolve('join_team', null);
  349. break;
  350. case S2C_JOIN_TEAM_FAIL:
  351. this._do_resolve('join_team', data.data);
  352. break;
  353. case S2C_TEAM_USER_JOIN:
  354. const join_user = data.data;
  355. join_user.status = true;
  356. (this._team as any).addPlayer({ key: join_user.userKey, data: join_user }, true);
  357. break;
  358. case S2C_TEAM_USER_EXIT:
  359. const esit_id = data.data;
  360. if (esit_id === this._team.own.Id) {
  361. (this._team as any).closed();
  362. this._clear_team();
  363. this._do_resolve('exit_team', null);
  364. } else {
  365. (this._team as any).removePlayer(esit_id);
  366. }
  367. break;
  368. case S2C_TEAM_CHANGE_HOST:
  369. (this._team as any).changeHost(data.data);
  370. break;
  371. case S2C_ROOM_CHANGE_HOST:
  372. (this._room as any).changeHost(data.data);
  373. break;
  374. case S2C_USER_RECONNECT:
  375. this._updatePlayerStatus(data.data, true);
  376. break;
  377. case S2C_USER_DISCONNECT:
  378. this._updatePlayerStatus(data.data, false);
  379. break;
  380. case S2C_MATCH:
  381. (this._team as any).match_start();
  382. break;
  383. case S2C_MATCH_CANCEL:
  384. (this._team as any).match_cancel();
  385. break;
  386. case S2C_NOT_READY:
  387. (this._team as any).not_ready();
  388. break;
  389. case S2C_READY:
  390. (this._team as any).updatePlayerReady(data.data, true);
  391. break;
  392. case S2C_MATCH_SUCCESS:
  393. this._clear_room();
  394. const roomData = data.data.roomData;
  395. const pData = data.data.playerData;
  396. this._room = new NetRoom<GD>(roomData, pData);
  397. if (this._team) (this._team as any).match_success();
  398. break;
  399. case S2C_ROOM_GAMEDATA_CHANGE:
  400. (this._room as any).server_change(data.data);
  401. break;
  402. case S2C_USER_GAMEDATA_CHANGE:
  403. const p = this._room.getPlayer(data.data.userKey);
  404. if (!p) break;
  405. (p as any).server_change(data.data.data);
  406. break;
  407. case S2C_ROOM_CLOSED:
  408. (this._room as any).closed(data.data);
  409. if (this.inTeam) {
  410. const list = this.room.all;
  411. for (let i = 0; i < list.length; i++) {
  412. const rp = list[i];
  413. if (rp.isAI) continue;
  414. const p = this.team.getPlayer(rp.Id);
  415. if (!p) continue;
  416. (p as any).set_level(rp.level, rp.rank, rp.score, rp.totalRank);
  417. }
  418. }
  419. this._do_resolve('exit_room', null);
  420. break;
  421. case S2C_TALK:
  422. if (this.inRoom) {
  423. (this._room as any).onChat(data.data.userKey, data.data.msg);
  424. } else if (this.inTeam) {
  425. (this._team as any).onChat(data.data.userKey, data.data.msg);
  426. }
  427. break;
  428. case S2C_FINISH:
  429. (this._room as any).finish(data.data.userKey, data.data.rank);
  430. break;
  431. case S2C_SETTLEMENT:
  432. (this._room as any).settlement(data.data);
  433. break;
  434. case C2S_MESSAGE:
  435. if (Array.isArray(data.data)) {
  436. for (let i = 0; i < data.data.length; i++) {
  437. const d = data.data[i];
  438. (this._room as any)._onEvt(d.type, d.data);
  439. }
  440. } else {
  441. chsdk.log.log('no def type:', data.data);
  442. //(this.receive as any)._emit(data.type, data.data);
  443. }
  444. break;
  445. default:
  446. break;
  447. }
  448. } catch (error) {
  449. chsdk.log.warn(error)
  450. this.state.emit(this.state.key.Error, error);
  451. }
  452. }
  453. private _onConnected(evt: any): void {
  454. if (this.timeout) clearTimeout(this.timeout);
  455. this._currentReconnectCount = 0;
  456. this.timeout = setTimeout(() => {
  457. this._ws.close();
  458. chsdk.log.error("Closing connection.");
  459. }, 3000);
  460. }
  461. private _onError(err: any): void {
  462. chsdk.log.log('err', err);
  463. this.state.emit(this.state.key.Error, err);
  464. }
  465. private _onClosing(): void {
  466. chsdk.log.log('closing');
  467. this.state.emit(this.state.key.Connecting);
  468. }
  469. private _onClosed(event: CloseEvent): void {
  470. chsdk.log.log("WebSocket connection closed", event);
  471. this.stopHeartbeat();
  472. if (!this.reconnection || (event.reason && event.code > 4000)) {
  473. this.state.emit(this.state.key.Closed, event);
  474. this._do_resolve('connect', event);
  475. chsdk.log.log("WebSocket connection closed", event);
  476. return;
  477. }
  478. if (this._currentReconnectCount < this.reconnectCount) {
  479. this._currentReconnectCount += 1;
  480. chsdk.log.log(`Reconnecting... Attempt ${this._currentReconnectCount}`);
  481. setTimeout(() => { this.do_reconnect() }, this.reconnectInterval)
  482. } else {
  483. chsdk.log.error("Max reconnect attempts reached. Giving up.");
  484. this.state.emit(this.state.key.Closed, event);
  485. this._do_resolve('connect', event);
  486. }
  487. }
  488. private startHeartbeat() {
  489. this.heartbeatInterval = setInterval(() => {
  490. this.sendHeartbeat();
  491. }, HEARTBEAT_INTERVAL);
  492. this.dirtyDataCheckInterval = setInterval(() => {
  493. this.dirtyDataCheck();
  494. }, this.gameDataInterval);
  495. this.evtCheckInterval = setInterval(() => {
  496. this.evtCheck();
  497. }, this.gameEvtInterval);
  498. }
  499. private resetHeartbeat() {
  500. if (this.timeout) clearTimeout(this.timeout);
  501. this.timeout = setTimeout(() => {
  502. chsdk.log.error("Heartbeat timeout. Closing connection.");
  503. this._ws.close();
  504. }, this.timeoutInterval);
  505. }
  506. private sendHeartbeat() {
  507. this._pingTime = chsdk.date.now();
  508. this.send_data(PING, this._ping);
  509. }
  510. private stopHeartbeat() {
  511. if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
  512. if (this.timeout) clearTimeout(this.timeout);
  513. if (this.dirtyDataCheckInterval) clearInterval(this.dirtyDataCheckInterval);
  514. if (this.evtCheckInterval) clearInterval(this.evtCheckInterval);
  515. }
  516. private evtCheck() {
  517. if (this.inRoom) {
  518. (this.room as any).doSendChat((msg: string) => {
  519. if (!msg) return;
  520. this.send_data(C2S_TALK, msg);
  521. });
  522. (this.room as any).doSendMode((mode0, mode1, mode2) => {
  523. if (mode0.length > 0) this.send_data(C2S_MESSAGE, mode0);
  524. if (mode1.length > 0) this.send_data(C2S_MESSAGE_TO_HOST, mode1);
  525. if (mode2.length > 0) this.send_data(C2S_MESSAGE_TO_OTHER, mode2);
  526. });
  527. const ps = this.room.all;
  528. for (let i = 0; i < ps.length; i++) {
  529. const p = ps[i];
  530. (p as any).doFinishGame(() => { this.send_data(C2S_FINISH, p.Id); });
  531. }
  532. } else if (this.inTeam) {
  533. (this.team as any).doSendChat((msg: string) => {
  534. if (!msg) return;
  535. this.send_data(C2S_TALK, msg);
  536. });
  537. }
  538. }
  539. private dirtyDataCheck() {
  540. if (!this.inRoom) return;
  541. (this.room.own as any).doDirtyData((dirty) => {
  542. this.send_data(C2S_UPDATE_USER_GAMEDATA, { userKey: this.room.own.Id, data: dirty });
  543. })
  544. if (!this.room.isHost) return;
  545. (this._room as any).doDirtyData((dirty) => {
  546. this.send_data(C2S_UPDATE_ROOM_GAMEDATA, dirty);
  547. })
  548. const ais = this.room.all;
  549. for (let i = 0; i < ais.length; i++) {
  550. const ai = ais[i];
  551. (ai as any).doDirtyData((dirty) => {
  552. this.send_data(C2S_UPDATE_USER_GAMEDATA, { userKey: ai.Id, data: dirty });
  553. })
  554. }
  555. }
  556. /**
  557. * 分享队伍信息,邀请进队
  558. * @param extend (可选)分享数据
  559. * @param title 分享显示标题
  560. * @param imageUrl 分享显示图片
  561. */
  562. public shareNetTeam<T extends { [key: string]: any }>(title?: string, imageUrl?: string, extend?: T) {
  563. if (!this.inTeam) return;
  564. chsdk.shareAppMessage(title, '', imageUrl, JSON.stringify({ type: 'NetTeam', pw: this.team.Password, extends: extend }));
  565. }
  566. /**
  567. *获取从好友分享的net数据进入游戏的数据
  568. */
  569. public getShareNetTeam<T extends { [key: string]: any }>(): { password: string, extend?: T } {
  570. const query = chsdk.getQuery();
  571. if (!query) return null;
  572. const message = query.message;
  573. if (!message) return null;
  574. const data = JSON.parse(message);
  575. if (!data) return null;
  576. if (data.type != 'NetTeam') return null;
  577. query.message = null;
  578. return { password: data.pw, extend: data.extends as T };
  579. }
  580. }