net.ts 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. import { Level, Ranks, SeasonInfo, transUserDataform, transUserExtraDataform, UserRank } from "./NetBase";
  2. import { NetRoom } from "./NetRoom";
  3. import { NetTeam } from "./NetTeam";
  4. import { TTWsClient } from "./TTWsClient";
  5. import { WsClient } from "./WsClient";
  6. import { WXWsClient } from "./WXWsClient";
  7. /**自定义同步游戏变量(只能以r_或p_开头,按帧率同步,重新进入会收到最新的数据)
  8. * 只有第一个参数有效,可加上第一个参数跟第一个参数同类型,用于接收改变前的数据
  9. * t_开头为队伍事件
  10. * r_开头 主机游戏数据 主机权限
  11. * revt_ 开头房间事件
  12. * p_ 开头玩家数据 玩家权限和主机ai权限
  13. * online 玩家在线下线状态
  14. * 尽量拆分数据类型,不要设置过于复杂的数据类型*/
  15. type protocol = { extra?: Record<string, any> } & chsdk.OmitIndex<chsdk.EventsMap>
  16. export interface game_protocol extends protocol {
  17. t_entry(id: string, location: number, nickName: string): void;//队伍有人进入
  18. t_exit(id: string, location: number, nickName: string): void;//队伍有人退出
  19. t_host(id: string, location: number, nickName: string): void;//队长切换
  20. t_chat(id: string, msg: string, location: number, nickName: string): void;//队伍聊天
  21. t_online(id: string, online: boolean, location: number, nickName: string): void;//队伍玩家在线情况变化
  22. t_ready(id: string, online: boolean, location: number, nickName: string): void;//队伍玩家准备情况变化
  23. t_matchStart(): void;//匹配开始
  24. t_matchCancel(): void;//匹配取消
  25. t_matchSuccess(): void;//匹配成功
  26. t_no_ready(): void;//有玩家未准备
  27. t_closed(): void;//队伍解散(一般用于被踢出事件)
  28. //
  29. r_obj(key: string, data: any, old: any): void;
  30. r_closed(): void;//房间关闭 关闭后 获取 results 结算信息
  31. r_host(id: string, location: number, nickName: string): void;//房主切换
  32. r_chat(id: string, msg: string, location: number, nickName: string): void;//房间聊天
  33. r_online(id: string, online: boolean, location: number, nickName: string): void;//房间玩家在线情况变化
  34. r_finish(id: string, rank: number): void//房间某个玩家完成游戏获得名次
  35. r_exit(id: string, location: number, nickName: string): void;//房间某个玩家离开
  36. r_state(state: number, old_state?: number): void;//房间游戏状态
  37. //
  38. p_obj(key: string, data: any, old: any): void;
  39. p_state(state: number, old_state?: number): void;//玩家游戏状态
  40. p_extra(): void;//扩展数据改变
  41. exit(): void;//玩家离开房间
  42. finish(rank: number): void;//玩家完成游戏获得名次
  43. online(online: boolean): void;//玩家断线重连消息
  44. extra: Record<string, any>;
  45. }
  46. /**网络状态*/
  47. interface ws_event_protocol {
  48. Connecting(): void;//连接中
  49. Reconnecting(reconnectCount: number): void;//重新连接中
  50. Connected(): void;//进入大厅连接成功
  51. Reconnected(): void;//重连成功
  52. Error(err: any | string): void;//错误消息
  53. Closing(): void;//关闭连接中
  54. Closed(event: string): void;//连接已关闭
  55. NextSeason(): void;//进入下一个赛季
  56. }
  57. interface options {
  58. /**(可选)query连接token等数据*/query?: string,
  59. /**(可选) CA 证书字符串,用于 SSL 连接的验证*/ca?: string,
  60. /**(可选) 指示是否启用重连机制,默认为 `true`*/ reconnection?: boolean,
  61. /**(可选) 在连接失败后重连的最大次数,默认值为 `10`*/ reconnectCount?: number,
  62. /**(可选) 每次重连之间的间隔时间,以毫秒为单位,默认值为 `2000`*/ reconnectInterval?: number,
  63. /**(可选) 心跳断线时间,以毫秒为单位,默认值为 `30000`*/ timeoutInterval?: number,
  64. /**(可选) 游戏事件同步间隔,默认值为 `66` (0.06秒 1秒钟15次)*/ gameEvtInterval?: number,
  65. /**(可选) 游戏变量同步间隔,默认值为 `200` (0.2秒 1秒钟5次)*/ gameDataInterval?: number,
  66. /**(可选) 是否用json序列化否则用messagepck 默认为 `false`*/ json?: boolean
  67. }
  68. function isArrayBuffer(it: unknown): it is ArrayBuffer {
  69. try {
  70. return it instanceof ArrayBuffer
  71. } catch (_) {
  72. return false
  73. }
  74. }
  75. type JsonPrimitive = string | number | boolean | null;
  76. type Serializable<T> = T extends JsonPrimitive ? T : T extends Date | Function | Symbol | undefined ? never :
  77. T extends Array<infer U> ? SerializableArray<U> : T extends object ? SerializableObject<T> : never;
  78. type SerializableArray<T> = Array<Serializable<T>>;
  79. type SerializableObject<T extends object> = { [K in keyof T]: Serializable<T[K]>; };
  80. //
  81. const HEARTBEAT_INTERVAL = 1000; //每秒发送一次 Ping
  82. const HEARTBEAT_TIMEOUT = 80000; //80 秒的断线超时
  83. const DIRTYDATACHECK_INTERVAL = 200;//每200毫秒同步一次自定义游戏变量
  84. const EVTCHECK_INTERVAL = 66;//每66毫秒同步一次自定义游戏事件
  85. const RECONNECT_COUNT = 10;//默认重连次数
  86. const RECONNECT_INTERVAL = 2000;//默认重连间隔
  87. const PING = 'ping';
  88. const PONG = 'pong';
  89. //
  90. const S2C_LOGIN_SUCCESS = 's2c_login_success';
  91. const S2C_TEAM_SUCCESS = 's2c_team_success';
  92. const S2C_JOIN_TEAM_FAIL = 's2c_join_team_fail';
  93. const S2C_TEAM_USER_JOIN = 's2c_team_user_join';
  94. const S2C_TEAM_USER_EXIT = 's2c_team_user_exit';
  95. const S2C_TEAM_CHANGE_HOST = 's2c_team_change_host';
  96. const S2C_USER_RECONNECT = 's2c_user_reconnect';
  97. const S2C_USER_DISCONNECT = 's2c_user_disconnect';
  98. const S2C_MATCH = 's2c_match';
  99. const S2C_MATCH_SUCCESS = 's2c_match_success';
  100. const S2C_MATCH_CANCEL = 's2c_match_cancel';
  101. const S2C_ROOM_GAMEDATA_CHANGE = 's2c_room_gameData_change';
  102. const S2C_USER_GAMEDATA_CHANGE = 's2c_user_gameData_change';
  103. const S2C_ROOM_CHANGE_HOST = 's2c_room_change_host';
  104. const S2C_ROOM_CLOSED = 's2c_room_closed';
  105. const S2C_TALK = 's2c_talk';
  106. const S2C_EXIT_ROOM = 's2c_exit_room';
  107. const S2C_FINISH = 's2c_finish';
  108. const S2C_READY = 's2c_ready';
  109. const S2C_NOT_READY = 's2c_not_ready';
  110. const S2C_RECEIVE_REWARD = 's2c_receive_reward';
  111. const S2C_SEASON = 's2c_season';
  112. const S2C_SET_USER_DATA_EXTRA = 's2c_set_user_data_extra';
  113. const S2C_RANK_DATA = 's2c_rank_data';
  114. //
  115. const C2S_MESSAGE = 'c2s_message';
  116. const C2S_SET_TEAM = 'c2s_set_team';
  117. const C2S_JOIN_TEAM = 'c2s_join_team';
  118. const C2S_EXIT_TEAM = 'c2s_exit_team';
  119. const C2S_CLOSE_ROOM = 'c2s_close_room';
  120. const C2S_ROOM_EXIT = 'c2s_room_exit';
  121. const C2S_BACKED_RETURN = 'c2s_backed_return';
  122. const C2S_BACKED = 'c2s_to_backed';
  123. const C2S_RECEIVE_REWARD = 'c2s_receive_reward';
  124. const C2S_SEASON = 'c2s_season';
  125. const C2S_SET_USER_DATA_EXTRA = 'c2s_set_user_data_extra';
  126. const C2S_RANK_DATA = 'c2s_rank_data';
  127. //
  128. const Ver: string = '0.6.2';
  129. /**客户端*/
  130. export class ch_net<GD extends game_protocol = game_protocol> {
  131. /**连接状态事件*/
  132. public state = chsdk.get_new_event<ws_event_protocol>();
  133. private _ws: WsClient | TTWsClient | WXWsClient | null = null;
  134. private _url?: string | null;
  135. private _ca?: string;
  136. private reconnection: boolean;
  137. private reconnectCount: number;
  138. private reconnectInterval: number;
  139. private timeoutInterval: number;
  140. private gameDataInterval: number;
  141. private gameEvtInterval: number;
  142. private timeout: any | null = null;
  143. private season_timeout: any | null = null;
  144. private json: boolean = false;
  145. private heartbeatInterval: any | null = null; // 心跳定时器
  146. private dirtyDataCheckInterval: any | null = null; //变量同步定时器
  147. private evtCheckInterval: any | null = null; //事件同步定时器
  148. private _currentReconnectCount: number = 0;
  149. private _pingTime: number = 0;
  150. private _ping: number = 0;//ping值
  151. private _callbacks: { [id: string]: (result: any) => void } = {};
  152. private _waitSends: Array<string | ArrayBuffer> = [];//| ArrayBufferLike | Blob | ArrayBufferView
  153. private _new_resolve<T>(id: string): Promise<T> { return new Promise(resolve => this._callbacks[id] = resolve); }
  154. private _lv: Level = { LowerRank: 0, Rank: 0, Star: 0 };
  155. private _userKey: string;
  156. private _userDataExtra: GD['extra'] = null;
  157. /**玩家扩展数据 */
  158. public get userDataExtra(): GD['extra'] { return this._userDataExtra; };
  159. /**获取玩家扩展数据某个字段*/
  160. public getUserDataExtraField<K extends keyof GD['extra']>(key: K): GD['extra'][K] { return this._userDataExtra?.[key]; }
  161. /**自己的段位信息
  162. * @returns {Object}段位信息对象
  163. */
  164. public get ownLevel(): Level { return this._lv };
  165. private _seasonInfo: any;
  166. /**
  167. * 赛季信息 包含当前赛季的基本信息以及排名历史。
  168. * @returns {SeasonInfo} 赛季信息对象
  169. */
  170. public get seasonInfo(): SeasonInfo { return this._seasonInfo };
  171. private _do_resolve<T>(id: string, data: T): boolean {
  172. const resolveCallback = this._callbacks[id];
  173. if (!resolveCallback) return false;
  174. resolveCallback(data);
  175. delete this._callbacks[id];
  176. return true;
  177. }
  178. private _team: NetTeam<GD> | null;
  179. private _room: NetRoom<GD> | null;
  180. public get dataInterval(): number { return this.gameDataInterval * 0.001 };
  181. public get evtInterval(): number { return this.gameEvtInterval * 0.001 };
  182. public get isActive(): boolean { return this._ws && this._ws.isActive };
  183. /**队伍信息*/
  184. public get team(): NetTeam<GD> {
  185. return this._team;
  186. }
  187. /**是否在队伍中*/
  188. public get inTeam(): boolean {
  189. return this._team != null && this._team.own != null;
  190. }
  191. /**房间信息*/
  192. public get room(): NetRoom<GD> {
  193. return this._room;
  194. }
  195. /**是否在房间中*/
  196. public get inRoom(): boolean {
  197. return this._room != null && !this._room.cloosed;
  198. }
  199. /**
  200. * 创建一个客户端
  201. */
  202. constructor() {
  203. this._waitSends = [];
  204. this._callbacks = {};
  205. }
  206. /**初始化
  207. * @param url - 连接的地址 'ws://localhost:3000'
  208. * @param options - 可选的配置选项对象,支持以下属性:
  209. * @example
  210. * 自定义消息
  211. interface message extends message_protocol {
  212. useItem(userId: string, otherId: string, itemId: number): void//使用道具
  213. }
  214. 自定义数据
  215. export interface gd extends game_data {
  216. r_lv(lv: number);//关卡信息
  217. r_time(time: number);//关卡时间
  218. }
  219. export const net = ch.get_new_net<gd, message>();
  220. const url = chsdk.getUrl('/handle').replace(chsdk.getUrl('/handle').split(':')[0], 'ws');
  221. const token = chsdk.getToken();
  222. net.init(url,{query: `token=${token}`});
  223. */
  224. public init(url: string, options?: options) {
  225. this._url = url;
  226. this._url = `${this._url}${options?.query?.length ? '?' + options.query : ''}`;
  227. this._ca = options?.ca;
  228. this.reconnection = options?.reconnection ?? true;
  229. this.reconnectCount = options?.reconnectCount ?? RECONNECT_COUNT;
  230. this.reconnectInterval = options?.reconnectInterval ?? RECONNECT_INTERVAL;
  231. this.timeoutInterval = options?.timeoutInterval ?? HEARTBEAT_TIMEOUT;
  232. this.gameDataInterval = options?.gameDataInterval ?? DIRTYDATACHECK_INTERVAL;
  233. this.gameEvtInterval = options?.gameEvtInterval ?? EVTCHECK_INTERVAL;
  234. this.json = options?.json ?? false;
  235. this._ws = new WsClient(this._url, this._ca);
  236. //if (chsdk.get_pf() === chsdk.pf.wx) this._ws = new WXWsClient(this._url, this._ca);
  237. //if (chsdk.get_pf() === chsdk.pf.tt) this._ws = new TTWsClient(this._url, this._ca);
  238. this._ws.onConnected = this._onConnected.bind(this);
  239. this._ws.onError = this._onError.bind(this);
  240. this._ws.onClosing = this._onClosing.bind(this);
  241. this._ws.onClosed = this._onClosed.bind(this);
  242. this._ws.onMessage = this._onMessage.bind(this);
  243. const isbuffer = typeof ArrayBuffer !== 'undefined';
  244. console.log('ch_net 初始化 ver:', Ver, isbuffer, this.json, this._url);
  245. //
  246. chsdk.sdk_event.on('hide', this.on_hide, this);
  247. chsdk.sdk_event.on('show', this.on_show, this);
  248. }
  249. /**模拟断线*/
  250. public SimulatedDisconnection(): void {
  251. this._ws.close();
  252. }
  253. /**主动断开连接*/
  254. public dispose(): void {
  255. this.state.clearAll();
  256. this._clear_team();
  257. this._clear_room();
  258. this._callbacks = {};
  259. this._waitSends.length = 0;
  260. this._ws.close(5000, 'client disconnect');
  261. chsdk.sdk_event.off('hide', this.on_hide, this);
  262. chsdk.sdk_event.off('show', this.on_show, this);
  263. }
  264. private on_hide(): void {
  265. if (!this.inRoom) return;
  266. this.send_data(C2S_BACKED);
  267. }
  268. private on_show(): void {
  269. if (!this.inRoom) return;
  270. this.send_data(C2S_BACKED_RETURN);
  271. }
  272. private encode(msg: any): string | ArrayBuffer {
  273. return this.json ? JSON.stringify(msg) : msgpack.encode(msg).buffer;
  274. }
  275. private decode(medata: any): any {
  276. return this.json ? JSON.parse(medata) : msgpack.decode(new Uint8Array(medata));
  277. }
  278. private send_data(type: string, data?: any): void {
  279. //console.log(type, data);
  280. const d = this.encode(data ? { type: type, data: data } : { type: type });
  281. //console.log(d);
  282. this._send(d);
  283. }
  284. /**开始连接,返回null为成功,否则为错误提示*/
  285. public async connect(): Promise<string | null> {
  286. if (!this._ws) {
  287. chsdk.log.error('not init');
  288. return 'not init';
  289. }
  290. if (!this._ws.connect()) return 'no netconnect';
  291. this.state.emit(this.state.key.Connecting);
  292. return this._new_resolve('connect');
  293. }
  294. /**主动重新连接*/
  295. public reconnect(): string | null {
  296. if (!this._ws) {
  297. chsdk.log.error('not init');
  298. return 'not init';
  299. }
  300. this._currentReconnectCount = 1;
  301. this.do_reconnect();
  302. }
  303. private do_reconnect(): void {
  304. this._userKey = null;
  305. this.state.emit(this.state.key.Reconnecting, this._currentReconnectCount);
  306. this._ws.connect();
  307. }
  308. /**玩家创建队伍,返回null为成功,否则为错误提示*/
  309. public async creat_team(player_count: number = 4): Promise<string | null> {
  310. if (!this._ws) return 'No network connection';
  311. if (this.inTeam) return null;
  312. this.send_data(C2S_SET_TEAM, player_count);
  313. return this._new_resolve('creat_team');
  314. }
  315. /**玩家进入队伍,返回null为成功,否则为错误提示*/
  316. public async join_team(password: string): Promise<string | null> {
  317. if (!this._ws) return 'No network connection';
  318. if (this.inTeam) return null;
  319. this.send_data(C2S_JOIN_TEAM, { password: password });
  320. return this._new_resolve('join_team');
  321. }
  322. /**玩家退出队伍,返回null为成功,否则为错误提示*/
  323. public async exit_team(): Promise<string | null> {
  324. try {
  325. if (!this._ws) return 'No network connection';
  326. if (!this.inTeam) return 'Not in team';
  327. if (this.inRoom) return 'in game';
  328. this.send_data(C2S_EXIT_TEAM);
  329. return this._new_resolve('exit_team');
  330. } catch (error) {
  331. return 'Unexpected error occurred';
  332. }
  333. }
  334. /**主机结束游戏关闭房间,成功后收到房间关闭事件*/
  335. public close_room(): void {
  336. if (!this._ws) return;
  337. if (!this.inRoom) return;
  338. if (!this.room.isHost) return;
  339. this.send_data(C2S_CLOSE_ROOM);
  340. }
  341. /**玩家退出房间 ,返回null为成功,否则为错误提示,退出成功后收到房间关闭事件*/
  342. public async exit_room(): Promise<string | null> {
  343. try {
  344. if (!this._ws) return 'No network connection';
  345. if (!this.inTeam) return 'Not in team';
  346. if (!this.inRoom) return 'Not in room';
  347. this.send_data(C2S_ROOM_EXIT);
  348. return this._new_resolve('exit_room');
  349. } catch (error) {
  350. return 'Unexpected error occurred';
  351. }
  352. }
  353. private _cache_rank: Ranks<GD['extra']> | null = null;
  354. private _cache_rank_time: number = 0;
  355. /**获取排行榜行信息 如果是string,就是错误信息,否则为排行榜信息*/
  356. public async getRankData(): Promise<string | Ranks<GD['extra']>> {
  357. if (chsdk.date.now() - this._cache_rank_time >= 30000) {
  358. this._cache_rank = null;
  359. }
  360. if (this._cache_rank) return this._cache_rank;
  361. if (!this._ws) return 'No network connection';
  362. this.send_data(C2S_RANK_DATA);
  363. return this._new_resolve('get_rank_data');
  364. }
  365. /**强制转化成用护排行类型*/
  366. public asUserRankType<T extends UserRank<GD['extra']> = UserRank<GD['extra']>>(data: any): T {
  367. return data as T;
  368. }
  369. /**领取赛季奖励 ,返回null为成功,否则为错误提示*/
  370. public async receive_reward(season: number): Promise<string | null> {
  371. if (!this._ws) return 'No network connection';
  372. this.send_data(C2S_RECEIVE_REWARD, season);
  373. return this._new_resolve('receive_reward');
  374. }
  375. /**主动刷新赛季信息,返回null为成功,否则为错误提示*/
  376. public async update_season(): Promise<string | null> {
  377. if (!this._ws) return 'No network connection';
  378. this.send_data(C2S_SEASON);
  379. return this._new_resolve('update_season');
  380. }
  381. /**更新设置玩家整个扩展数据,返回null为成功,否则为错误提示*/
  382. public async setUserExtra(data: GD['extra'] & SerializableObject<GD['extra']>): Promise<string | null> {
  383. if (!this._ws) return 'No network connection';
  384. this._userDataExtra = data;
  385. this.send_data(C2S_SET_USER_DATA_EXTRA, JSON.stringify(data));
  386. if (this.inTeam) (this.team.own as any).set_userExtra(this._userDataExtra);
  387. if (this.inRoom) (this.room.own as any).set_userExtra(this._userDataExtra);
  388. return this._new_resolve('set_user_extra');
  389. }
  390. /**更新设置玩家扩展数据某个字段的值 */
  391. public async setUserExtraField<K extends keyof GD['extra']>(key: K, value: GD['extra'][K] & SerializableObject<GD['extra'][K]>): Promise<string | null> {
  392. this._userDataExtra ??= {};
  393. this._userDataExtra[key] = value;
  394. return this.setUserExtra(this._userDataExtra);
  395. }
  396. //
  397. private _send(data: string | ArrayBuffer) {
  398. if (!this._ws) return;
  399. if (this._ws.isActive) {
  400. this._ws.send(data);
  401. } else {
  402. this._waitSends.push(data);
  403. }
  404. }
  405. //
  406. private _clear_team(): void {
  407. this._team?.dispose();
  408. this._team = null;
  409. }
  410. private _clear_room(): void {
  411. this._room?.dispose();
  412. this._room = null;
  413. }
  414. private _temp_team: any | null;
  415. private _team_info(data?: any | null) {
  416. this._clear_team();
  417. if (!this._userKey) {
  418. this._temp_team = data;
  419. } else if (this._temp_team || data) {
  420. data ??= this._temp_team;
  421. this._team = new NetTeam<GD>(this._userKey, data, this.send_data.bind(this));
  422. if (!this._do_resolve('creat_team', null)) this._do_resolve('join_team', null);
  423. this._temp_team = null;
  424. }
  425. }
  426. private _temp_match_data: any | null;
  427. private _match_success(data?: any | null): void {
  428. this._clear_room();
  429. if (!this._userKey) {
  430. this._temp_match_data = data;
  431. } else if (this._temp_match_data || data) {
  432. data ??= this._temp_match_data;
  433. const roomData = data.roomData;
  434. const pData = data.playerData;
  435. this._room = new NetRoom<GD>(this._userKey, roomData, pData, this.send_data.bind(this));
  436. if (this._team) (this._team as any).match_success();
  437. this._temp_match_data = null;
  438. }
  439. }
  440. private _updatePlayerStatus(id: string, online: boolean) {
  441. if (this.inTeam) (this._team as any).updatePlayerStatus(id, online);
  442. if (this.inRoom) (this._room as any).updatePlayerStatus(id, online);
  443. }
  444. //
  445. private _onMessage(msg: string | ArrayBuffer): void {
  446. this.resetHeartbeat();
  447. try {
  448. const data = this.decode(msg);
  449. if (data.type !== PONG) chsdk.log.log('receive', JSON.stringify(data));
  450. switch (data.type) {
  451. case PONG:
  452. this._ping = chsdk.date.now() - this._pingTime;
  453. chsdk.date.updateServerTime(data.data);
  454. //chsdk.log.log("pong:", this._ping, data.data, chsdk.date.now());
  455. break;
  456. case S2C_LOGIN_SUCCESS:
  457. this._lv = data.data.level;
  458. this._userKey = data.data.userKey;
  459. this._seasonInfo = data.data.seasonInfo;
  460. this._userDataExtra = transUserExtraDataform(data.data.userDataExtra);
  461. this._team_info();
  462. this._match_success();
  463. //
  464. if (this._do_resolve('connect', null)) {
  465. this.state.emit(this.state.key.Connected);
  466. } else {
  467. this.state.emit(this.state.key.Reconnected);
  468. }
  469. this._callbacks = {};
  470. this.startHeartbeat();
  471. while (this._waitSends.length) {
  472. if (!this._ws) break;
  473. const data = this._waitSends.shift();
  474. if (data) this._ws.send(data);
  475. }
  476. break;
  477. case S2C_TEAM_SUCCESS:
  478. this._team_info(data.data);
  479. break;
  480. case S2C_JOIN_TEAM_FAIL:
  481. this._do_resolve('join_team', data.data);
  482. break;
  483. case S2C_TEAM_USER_JOIN:
  484. const join_user = data.data;
  485. join_user.status = true;
  486. (this._team as any).addPlayer({ key: join_user.userKey, data: join_user }, true);
  487. break;
  488. case S2C_TEAM_USER_EXIT:
  489. const esit_id = data.data;
  490. if (esit_id === this._team.own.Id) {
  491. (this._team as any).closed();
  492. this._clear_team();
  493. this._do_resolve('exit_team', null);
  494. } else {
  495. (this._team as any).removePlayer(esit_id);
  496. }
  497. break;
  498. case S2C_TEAM_CHANGE_HOST:
  499. if (this.inTeam) (this._team as any).changeHost(data.data);
  500. break;
  501. case S2C_ROOM_CHANGE_HOST:
  502. if (this.inRoom) (this._room as any).changeHost(data.data);
  503. break;
  504. case S2C_USER_RECONNECT:
  505. this._updatePlayerStatus(data.data, true);
  506. break;
  507. case S2C_USER_DISCONNECT:
  508. this._updatePlayerStatus(data.data, false);
  509. break;
  510. case S2C_MATCH:
  511. (this._team as any).match_start();
  512. break;
  513. case S2C_MATCH_CANCEL:
  514. (this._team as any).match_cancel();
  515. break;
  516. case S2C_NOT_READY:
  517. (this._team as any).not_ready();
  518. break;
  519. case S2C_READY:
  520. (this._team as any).updatePlayerReady(data.data, true);
  521. break;
  522. case S2C_MATCH_SUCCESS:
  523. this._match_success(data.data);
  524. break;
  525. case S2C_ROOM_GAMEDATA_CHANGE:
  526. (this._room as any).server_change(data.data);
  527. break;
  528. case S2C_USER_GAMEDATA_CHANGE:
  529. const p = this._room.getPlayer(data.data.userKey);
  530. if (!p) break;
  531. (p as any).server_change(data.data.data);
  532. break;
  533. case S2C_ROOM_CLOSED:
  534. (this._room as any).closed(data.data);
  535. const list = this.room.all;
  536. for (let i = 0; i < list.length; i++) {
  537. const rp = list[i];
  538. if (rp.isAI) continue;
  539. if (rp.isOwn) {
  540. this._lv.LowerRank = rp.level.LowerRank;
  541. this._lv.Rank = rp.level.Rank;
  542. this._lv.Star = rp.level.Star;
  543. }
  544. if (this.inTeam) {
  545. const p = this.team.getPlayer(rp.Id);
  546. if (!p) continue;
  547. (p as any).set_level(rp.level, rp.rank, rp.score, rp.totalRank);
  548. }
  549. }
  550. this._do_resolve('exit_room', null);
  551. break;
  552. case S2C_TALK:
  553. if (this.inRoom) {
  554. (this._room as any).onChat(data.data.userKey, data.data.msg);
  555. } else if (this.inTeam) {
  556. (this._team as any).onChat(data.data.userKey, data.data.msg);
  557. }
  558. break;
  559. case S2C_FINISH:
  560. (this._room as any).finish(data.data.userKey, data.data.rank);
  561. break;
  562. case S2C_EXIT_ROOM:
  563. (this._room as any).exit(data.data);
  564. break;
  565. case S2C_RECEIVE_REWARD:
  566. this._do_resolve('receive_reward', null);
  567. break;
  568. case S2C_SEASON:
  569. this._seasonInfo = data.data;
  570. this._do_resolve('update_season', null);
  571. this.state.emit('NextSeason');
  572. break;
  573. case S2C_SET_USER_DATA_EXTRA:
  574. this._do_resolve('set_user_extra', null);
  575. break;
  576. case S2C_RANK_DATA:
  577. this._cache_rank_time = chsdk.date.now();
  578. const r = data.data;
  579. const rr = r.ownRank;
  580. const ownRank: UserRank<GD['extra']> = rr;
  581. ownRank.UserData = transUserDataform(rr.UserData);
  582. ownRank.UserDataExtra = transUserExtraDataform(rr.UserDataExtra);
  583. const elementsRank: UserRank<GD['extra']>[] = [];
  584. for (let i = 0; i < r.elementsRank.length; i++) {
  585. const rr = r.elementsRank[i];
  586. rr.UserData = transUserDataform(rr.UserData);
  587. rr.UserDataExtra = transUserExtraDataform(rr.UserDataExtra);
  588. elementsRank.push(rr);
  589. }
  590. this._cache_rank = { list: elementsRank, own: ownRank };
  591. this._do_resolve('get_rank_data', this._cache_rank);
  592. break;
  593. case C2S_MESSAGE:
  594. if (Array.isArray(data.data)) {
  595. if (this.inRoom) {
  596. for (let i = 0; i < data.data.length; i++) {
  597. const d = data.data[i];
  598. (this._room as any)._onEvt(d.type, d.data);
  599. }
  600. }
  601. } else {
  602. chsdk.log.log('no def type:', data.data);
  603. //(this.receive as any)._emit(data.type, data.data);
  604. }
  605. break;
  606. default:
  607. break;
  608. }
  609. } catch (error) {
  610. chsdk.log.warn(error)
  611. this.state.emit(this.state.key.Error, error);
  612. }
  613. }
  614. private _onConnected(evt: any): void {
  615. if (this.timeout) clearTimeout(this.timeout);
  616. this._currentReconnectCount = 0;
  617. this.timeout = setTimeout(() => {
  618. chsdk.log.warn("Closing connection whit no replay logoin!");
  619. this._ws.close();
  620. }, 3000);
  621. }
  622. private _onError(err: any): void {
  623. chsdk.log.log('err', err);
  624. this.state.emit(this.state.key.Error, err);
  625. }
  626. private _onClosing(): void {
  627. chsdk.log.log('closing');
  628. this.state.emit(this.state.key.Closing);
  629. }
  630. private _onClosed(code: number, reason: string): void {
  631. code ??= 0;
  632. reason ??= '';
  633. chsdk.log.log("WebSocket connection closed", code, reason);
  634. this.stopHeartbeat();
  635. if (!this.reconnection || (reason && code > 4000)) {
  636. this.state.emit(this.state.key.Closed, reason);
  637. this._do_resolve('connect', reason);
  638. chsdk.log.log("WebSocket connection closed", reason);
  639. return;
  640. }
  641. if (this._currentReconnectCount < this.reconnectCount) {
  642. this._currentReconnectCount += 1;
  643. chsdk.log.log(`Reconnecting... Attempt ${this._currentReconnectCount}`);
  644. setTimeout(() => { this.do_reconnect() }, this.reconnectInterval)
  645. } else {
  646. this._clear_team();
  647. this._clear_room();
  648. chsdk.log.error("Max reconnect attempts reached. Giving up.");
  649. this.state.emit(this.state.key.Closed, reason);
  650. this._do_resolve('connect', reason);
  651. }
  652. }
  653. private startHeartbeat() {
  654. this.heartbeatInterval = setInterval(() => {
  655. this.sendHeartbeat();
  656. }, HEARTBEAT_INTERVAL);
  657. this.dirtyDataCheckInterval = setInterval(() => {
  658. this.dirtyDataCheck();
  659. }, this.gameDataInterval);
  660. this.evtCheckInterval = setInterval(() => {
  661. this.evtCheck();
  662. }, this.gameEvtInterval);
  663. if (this.season_timeout) clearTimeout(this.season_timeout);
  664. this.season_timeout = setTimeout(() => {
  665. this.update_season();
  666. }, this.seasonInfo.SeasonEndTime - chsdk.date.now() + Math.floor(Math.random() * 2000));
  667. }
  668. private resetHeartbeat() {
  669. if (this.timeout) clearTimeout(this.timeout);
  670. this.timeout = setTimeout(() => {
  671. chsdk.log.error("Heartbeat timeout. Closing connection.");
  672. this._ws.close();
  673. }, this.timeoutInterval);
  674. }
  675. private sendHeartbeat() {
  676. this._pingTime = chsdk.date.now();
  677. this.send_data(PING, this._ping);
  678. }
  679. private stopHeartbeat() {
  680. if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
  681. if (this.timeout) clearTimeout(this.timeout);
  682. if (this.dirtyDataCheckInterval) clearInterval(this.dirtyDataCheckInterval);
  683. if (this.evtCheckInterval) clearInterval(this.evtCheckInterval);
  684. if (this.season_timeout) clearTimeout(this.season_timeout);
  685. }
  686. private evtCheck() {
  687. if (this.inRoom) {
  688. (this.room as any).doSendChat();
  689. } else if (this.inTeam) {
  690. (this.team as any).doSendChat();
  691. }
  692. }
  693. private dirtyDataCheck() {
  694. if (!this.inRoom) return;
  695. this.room.doSendDirty();
  696. }
  697. /**
  698. * 分享队伍信息,邀请进队
  699. * @param extend (可选)分享数据
  700. * @param title 分享显示标题
  701. * @param imageUrl 分享显示图片
  702. */
  703. public shareNetTeam<T extends { [key: string]: any }>(title?: string, imageUrl?: string, extend?: T) {
  704. if (!this.inTeam) return;
  705. chsdk.shareAppMessage(title, '', imageUrl, JSON.stringify({ type: 'NetTeam', pw: this.team.Password, extends: extend }));
  706. }
  707. /**
  708. *获取从好友分享的net数据进入游戏的数据
  709. */
  710. public getShareNetTeam<T extends { [key: string]: any }>(): { password: string, extend?: T } {
  711. const query = chsdk.getQuery();
  712. if (!query) return null;
  713. const message = query.message;
  714. if (!message) return null;
  715. const data = JSON.parse(message);
  716. if (!data) return null;
  717. if (data.type != 'NetTeam') return null;
  718. query.message = null;
  719. return { password: data.pw, extend: data.extends as T };
  720. }
  721. }