|
@@ -0,0 +1,582 @@
|
|
|
+import { NetRoom } from "./NetRoom";
|
|
|
+import { NetTeam } from "./NetTeam";
|
|
|
+import { WsClient } from "./WsClient";
|
|
|
+/**自定义同步游戏变量(只能以r_或p_开头,按帧率同步,重新进入会收到最新的数据)
|
|
|
+ * 只有第一个参数有效,可加上第一个参数跟第一个参数同类型,用于接收改变前的数据
|
|
|
+ * t_开头为队伍事件
|
|
|
+ * r_开头 主机游戏数据 主机权限
|
|
|
+ * revt_ 开头房间事件
|
|
|
+ * p_ 开头玩家数据 玩家权限和主机ai权限
|
|
|
+ * online 玩家在线下线状态
|
|
|
+ * 尽量拆分数据类型,不要设置过于复杂的数据类型*/
|
|
|
+export interface game_protocol extends chsdk.OmitIndex<chsdk.EventsMap> {
|
|
|
+ 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_state(state: number, old_state?: number): void;//房间游戏状态
|
|
|
+ p_state(state: number, old_state?: number): void;//玩家游戏状态
|
|
|
+ finish(rank: number): void;//玩家完成游戏获得名次
|
|
|
+ online(online: boolean): void//玩家断线重连消息
|
|
|
+}
|
|
|
+/**网络状态*/
|
|
|
+interface ws_event_protocol {
|
|
|
+ Connecting(): void;//连接中
|
|
|
+ Reconnecting(reconnectCount: number): void;//重新连接中
|
|
|
+ Connected(): void;//进入大厅连接成功
|
|
|
+ Reconnected(): void;//重连成功
|
|
|
+ Error(err: any | string): void;//错误消息
|
|
|
+ Closing(): void;//关闭连接中
|
|
|
+ Closed(event: CloseEvent): void;//连接已关闭
|
|
|
+}
|
|
|
+function isArrayBuffer(it: unknown): it is ArrayBuffer {
|
|
|
+ try {
|
|
|
+ return it instanceof ArrayBuffer
|
|
|
+ } catch (_) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+}
|
|
|
+const HEARTBEAT_INTERVAL = 1000; //每秒发送一次 Ping
|
|
|
+const HEARTBEAT_TIMEOUT = 30000; //30 秒的断线超时
|
|
|
+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_FINISH = 's2c_finish';
|
|
|
+const S2C_SETTLEMENT = 's2c_settlement';
|
|
|
+const S2C_READY = 's2c_ready';
|
|
|
+const S2C_NOT_READY = 's2c_not_ready';
|
|
|
+//
|
|
|
+const C2S_UPDATE_USER_GAMEDATA = 'c2s_update_user_gameData';
|
|
|
+const C2S_UPDATE_ROOM_GAMEDATA = 'c2s_update_room_gameData';
|
|
|
+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_MESSAGE = 'c2s_message';
|
|
|
+const C2S_MESSAGE_TO_HOST = 'c2s_message_to_host';
|
|
|
+const C2S_MESSAGE_TO_OTHER = 'c2s_message_without_self';
|
|
|
+const C2S_TALK = 'c2s_talk';
|
|
|
+const C2S_BACKED_RETURN = 'c2s_backed_return';
|
|
|
+const C2S_BACKED = 'c2s_to_backed';
|
|
|
+const C2S_FINISH = 'c2s_finish';
|
|
|
+//
|
|
|
+const Ver: string = '0.1.8';
|
|
|
+/**客户端*/
|
|
|
+export class ch_net<GD extends game_protocol = game_protocol> {
|
|
|
+ /**连接状态事件*/
|
|
|
+ public state = chsdk.get_new_event<ws_event_protocol>();
|
|
|
+ private _ws: WsClient | 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 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<string | ArrayBufferLike | Blob | ArrayBufferView> = [];
|
|
|
+ private _new_resolve<T>(id: string): Promise<T> { return new Promise(resolve => this._callbacks[id] = resolve); }
|
|
|
+ private _lv: { LowerRank: number, Rank: number, Star: number } = { LowerRank: 0, Rank: 0, Star: 0 };
|
|
|
+ public get ownLevel(): { LowerRank: number, Rank: number, Star: number } { return this._lv };
|
|
|
+ private _do_resolve<T>(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<GD> | null;
|
|
|
+ private _room: NetRoom<GD> | 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<GD> {
|
|
|
+ return this._team;
|
|
|
+ }
|
|
|
+ /**是否在队伍中*/
|
|
|
+ public get inTeam(): boolean {
|
|
|
+ return this._team != null && this._team.own != null;
|
|
|
+ }
|
|
|
+ /**房间信息*/
|
|
|
+ public get room(): NetRoom<GD> {
|
|
|
+ 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 - 可选的配置选项对象,支持以下属性:
|
|
|
+ * - **ca**: (可选) CA 证书字符串,用于 SSL 连接的验证。
|
|
|
+ * - **reconnection**: (可选) 指示是否启用重连机制,默认为 `true`。
|
|
|
+ * - **reconnectCount**: (可选) 在连接失败后重连的最大次数,默认值为 `10`。
|
|
|
+ * - **reconnectInterval**: (可选) 每次重连之间的间隔时间,以毫秒为单位,默认值为 `2000` (2 秒)。
|
|
|
+ * - **timeoutInterval**: (可选) 心跳断线时间,以毫秒为单位,默认值为 `30000` (30 秒)。
|
|
|
+ * - **gameEvtInterval**:(可选) 游戏事件同步间隔,默认值为 `66` (0.06秒 1秒钟15次)。
|
|
|
+ * - **gameDataInterval**:(可选) 游戏变量同步间隔,默认值为 `200` (0.2秒 1秒钟5次)。
|
|
|
+ * - **json**: (可选) 是否用json序列化否则用messagepck 默认为 `false`。
|
|
|
+ * @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<gd, message>();
|
|
|
+ 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?: { query?: string, ca?: string, reconnection?: boolean, reconnectCount?: number, reconnectInterval?: number, timeoutInterval?: number, gameEvtInterval?: number, gameDataInterval?: number, json?: boolean }) {
|
|
|
+ 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);
|
|
|
+ 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 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): any {
|
|
|
+ return this.json ? JSON.stringify(msg) : msgpack.encode(msg);
|
|
|
+ }
|
|
|
+ private decode(medata: any): any {
|
|
|
+ return isArrayBuffer(medata) ? msgpack.decode(new Uint8Array(medata)) : JSON.parse(medata);
|
|
|
+ }
|
|
|
+ private send_data(type: string, data?: any): void {
|
|
|
+ this._send(this.encode(data ? { type: type, data: data } : { type: type }));
|
|
|
+ }
|
|
|
+ /**开始连接,返回null为成功,否则为错误提示*/
|
|
|
+ public async connect(): Promise<string | null> {
|
|
|
+ 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 {
|
|
|
+ if (!this._ws.connect()) return;
|
|
|
+ this.state.emit(this.state.key.Reconnecting, this._currentReconnectCount);
|
|
|
+ }
|
|
|
+ /**玩家创建队伍,返回null为成功,否则为错误提示*/
|
|
|
+ public async creat_team(player_count: number = 4): Promise<string | null> {
|
|
|
+ if (!this._ws) return 'no netconnect';
|
|
|
+ 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<string | null> {
|
|
|
+ if (!this._ws) return 'no netconnect';
|
|
|
+ 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<string | null> {
|
|
|
+ if (!this._ws) return 'no netconnect';
|
|
|
+ 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');
|
|
|
+ }
|
|
|
+ /**主机结束游戏关闭房间,成功后收到房间关闭事件*/
|
|
|
+ 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<string | null> {
|
|
|
+ if (!this._ws) return 'no netconnect';
|
|
|
+ if (!this.inRoom) return 'not in room';
|
|
|
+ this.send_data(C2S_ROOM_EXIT);
|
|
|
+ return this._new_resolve('exit_room');
|
|
|
+ }
|
|
|
+ private _clear_team(): void {
|
|
|
+ this._team?.dispose();
|
|
|
+ this._team = null;
|
|
|
+ }
|
|
|
+ private _clear_room(): void {
|
|
|
+ if (!this._room) return;
|
|
|
+ this._room?.dispose();
|
|
|
+ this._room = null;
|
|
|
+ }
|
|
|
+ private _send(data: Parameters<WebSocket['send']>[0]) {
|
|
|
+ if (!this._ws) return;
|
|
|
+ if (this._ws.isActive) {
|
|
|
+ this._ws.send(data);
|
|
|
+ } else {
|
|
|
+ this._waitSends.push(data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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: MessageEvent): void {
|
|
|
+ this.resetHeartbeat();
|
|
|
+ try {
|
|
|
+ const { data: medata } = msg;
|
|
|
+ const data = this.decode(medata);
|
|
|
+ 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;
|
|
|
+ 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:
|
|
|
+ if (this._team) this._clear_team();
|
|
|
+ this._team = new NetTeam<GD>(data.data, this.send_data.bind(this));
|
|
|
+ if (!this._do_resolve('creat_team', null)) this._do_resolve('join_team', null);
|
|
|
+ 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:
|
|
|
+ (this._team as any).changeHost(data.data);
|
|
|
+ break;
|
|
|
+ case S2C_ROOM_CHANGE_HOST:
|
|
|
+ (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._clear_room();
|
|
|
+ const roomData = data.data.roomData;
|
|
|
+ const pData = data.data.playerData;
|
|
|
+ this._room = new NetRoom<GD>(roomData, pData);
|
|
|
+ if (this._team) (this._team as any).match_success();
|
|
|
+ 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);
|
|
|
+ if (this.inTeam) {
|
|
|
+ const list = this.room.all;
|
|
|
+ for (let i = 0; i < list.length; i++) {
|
|
|
+ const rp = list[i];
|
|
|
+ if (rp.isAI) continue;
|
|
|
+ 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_SETTLEMENT:
|
|
|
+ (this._room as any).settlement(data.data);
|
|
|
+ break;
|
|
|
+ case C2S_MESSAGE:
|
|
|
+ if (Array.isArray(data.data)) {
|
|
|
+ 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(() => {
|
|
|
+ this._ws.close();
|
|
|
+ chsdk.log.error("Closing connection.");
|
|
|
+ }, 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.Connecting);
|
|
|
+ }
|
|
|
+
|
|
|
+ private _onClosed(event: CloseEvent): void {
|
|
|
+ chsdk.log.log("WebSocket connection closed", event);
|
|
|
+ this.stopHeartbeat();
|
|
|
+ if (!this.reconnection || (event.reason && event.code > 4000)) {
|
|
|
+ this.state.emit(this.state.key.Closed, event);
|
|
|
+ this._do_resolve('connect', event);
|
|
|
+ chsdk.log.log("WebSocket connection closed", event);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (this._currentReconnectCount < this.reconnectCount) {
|
|
|
+ this._currentReconnectCount += 1;
|
|
|
+ chsdk.log.log(`Reconnecting... Attempt ${this._currentReconnectCount}`);
|
|
|
+ setTimeout(() => { this.do_reconnect() }, this.reconnectInterval)
|
|
|
+ } else {
|
|
|
+ chsdk.log.error("Max reconnect attempts reached. Giving up.");
|
|
|
+ this.state.emit(this.state.key.Closed, event);
|
|
|
+ this._do_resolve('connect', event);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private startHeartbeat() {
|
|
|
+ this.heartbeatInterval = setInterval(() => {
|
|
|
+ this.sendHeartbeat();
|
|
|
+ }, HEARTBEAT_INTERVAL);
|
|
|
+ this.dirtyDataCheckInterval = setInterval(() => {
|
|
|
+ this.dirtyDataCheck();
|
|
|
+ }, this.gameDataInterval);
|
|
|
+ this.evtCheckInterval = setInterval(() => {
|
|
|
+ this.evtCheck();
|
|
|
+ }, this.gameEvtInterval);
|
|
|
+ }
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+ private evtCheck() {
|
|
|
+ if (this.inRoom) {
|
|
|
+ (this.room as any).doSendChat((msg: string) => {
|
|
|
+ if (!msg) return;
|
|
|
+ this.send_data(C2S_TALK, msg);
|
|
|
+ });
|
|
|
+ (this.room as any).doSendMode((mode0, mode1, mode2) => {
|
|
|
+ if (mode0.length > 0) this.send_data(C2S_MESSAGE, mode0);
|
|
|
+ if (mode1.length > 0) this.send_data(C2S_MESSAGE_TO_HOST, mode1);
|
|
|
+ if (mode2.length > 0) this.send_data(C2S_MESSAGE_TO_OTHER, mode2);
|
|
|
+ });
|
|
|
+ const ps = this.room.all;
|
|
|
+ for (let i = 0; i < ps.length; i++) {
|
|
|
+ const p = ps[i];
|
|
|
+ (p as any).doFinishGame(() => { this.send_data(C2S_FINISH, p.Id); });
|
|
|
+ }
|
|
|
+ } else if (this.inTeam) {
|
|
|
+ (this.team as any).doSendChat((msg: string) => {
|
|
|
+ if (!msg) return;
|
|
|
+ this.send_data(C2S_TALK, msg);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private dirtyDataCheck() {
|
|
|
+ if (!this.inRoom) return;
|
|
|
+ (this.room.own as any).doDirtyData((dirty) => {
|
|
|
+ this.send_data(C2S_UPDATE_USER_GAMEDATA, { userKey: this.room.own.Id, data: dirty });
|
|
|
+ })
|
|
|
+ if (!this.room.isHost) return;
|
|
|
+ (this._room as any).doDirtyData((dirty) => {
|
|
|
+ this.send_data(C2S_UPDATE_ROOM_GAMEDATA, dirty);
|
|
|
+ })
|
|
|
+ const ais = this.room.all;
|
|
|
+ for (let i = 0; i < ais.length; i++) {
|
|
|
+ const ai = ais[i];
|
|
|
+ (ai as any).doDirtyData((dirty) => {
|
|
|
+ this.send_data(C2S_UPDATE_USER_GAMEDATA, { userKey: ai.Id, data: dirty });
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 分享队伍信息,邀请进队
|
|
|
+ * @param extend (可选)分享数据
|
|
|
+ * @param title 分享显示标题
|
|
|
+ * @param imageUrl 分享显示图片
|
|
|
+ */
|
|
|
+ public shareNetTeam<T extends { [key: string]: any }>(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<T extends { [key: string]: any }>(): { 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 };
|
|
|
+ }
|
|
|
+}
|