123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- import { NetBase, results, transUserDataform } from "./NetBase";
- import { NetPlayer } from "./NetPlayer";
- type EventNames<Map extends chsdk.OmitIndex<chsdk.EventsMap>> = Extract<keyof Map, string>;
- type EventParams<Map extends chsdk.OmitIndex<chsdk.EventsMap>, Ev extends EventNames<Map>> = Ev extends keyof Map ? Map[Ev] extends (...args: any[]) => any ? Parameters<Map[Ev]> : never : never;
- type StartEventNames<Map extends chsdk.OmitIndex<chsdk.EventsMap>, Prefix extends string> = Extract<keyof Map, string> extends infer K ? K extends `${Prefix}${string}` ? K : never : never;
- type EventParamFrist<Map extends chsdk.OmitIndex<chsdk.EventsMap>, Ev extends EventNames<Map>> = Ev extends keyof Map
- ? Map[Ev] extends (...args: infer Params) => any ? Params extends [infer First, ...any[]] ? First : never : never : never;
- type PickEvent<T, Prefix1 extends string, Prefix2 extends string, Prefix3 extends string> = Pick<T, Extract<keyof T, `${Prefix1}${string}` | `${Prefix2}${string}` | `${Prefix3}${string}`>>;
- const C2S_TALK = 'c2s_talk';
- 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_FINISH = 'c2s_finish';
- const C2S_UPDATE_USER_GAMEDATA = 'c2s_update_user_gameData';
- const C2S_UPDATE_ROOM_GAMEDATA = 'c2s_update_room_gameData';
- /**网络房间*/
- export class NetRoom<GD> extends NetBase {
- public readonly evt = chsdk.get_new_event<PickEvent<GD, 'r_', 'r_obj', 'revt_'>>();
- private _hostId: string;
- /**游戏主机ID*/
- public get HostId(): string { return this._hostId };
- private _status: boolean = true;
- /**游戏是否关闭*/
- public get cloosed(): boolean { return !this._status };
- private _own_id: string;
- public get ownId(): string { return this._own_id };
- private _players: Map<string, NetPlayer<GD>> = new Map();
- //
- private _waitSends_mode0: Array<{ type: string, data: any }> = [];
- private _waitSends_mode1: Array<{ type: string, data: any }> = [];
- private _waitSends_mode2: Array<{ type: string, data: any }> = [];
- private _send: (type: string, data?: any) => void;
- /**自己是否是主机*/
- public get isHost(): boolean {
- return this._own_id === this._hostId;
- }
- //
- constructor(ownId: string, roomData: any, playerData: any, send: (type: string, data?: any) => void) {
- super(roomData.roomId);
- this._send = send;
- this._own_id = ownId;
- this._hostId = roomData.hostInfo;
- this._status = roomData.status;
- let ps = Object.keys(playerData).map(key => ({ key, data: playerData[key] }));
- for (let i = 0; i < ps.length; i++) this.addPlayer(ps[i]);
- this.initValue(roomData.gameData);
- }
- private addPlayer(p: any): void {
- const id = p.key;
- const pd = p.data;
- let player = this._players.get(id);
- if (!player) {
- player = new NetPlayer<GD>(id);
- this._players.set(id, player);
- }
- player.init(pd);
- (player as any).set_own(player.Id === this._own_id);
- (player as any).set_host(player.Id === this._hostId, this.isHost)
- }
- private updatePlayerStatus(id: string, online: boolean): void {
- const p = this.getPlayer(id);
- if (p) {
- (p as any).change_online(online);
- const location = p.location;
- const nickName = p.nickName;
- (this.evt as any)._emit('r_online', id, online, location, nickName);
- }
- }
- private changeHost(id: string): void {
- this._hostId = id;
- this._players.forEach((v, _) => { (v as any).set_host(v.Id === this._hostId, this.isHost); });
- const p = this.getPlayer(this._hostId);
- (this.evt as any)._emit('r_host', id, p.location, p.nickName);
- }
- /**自己的所有信息*/
- public get own(): NetPlayer<GD> {
- return this._players.get(this._own_id);
- }
- /**其它玩家信息*/
- public get others(): NetPlayer<GD>[] {
- return Array.from(this._players.values()).filter(player => !player.isOwn);
- }
- /**所有ai信息*/
- public get ais(): NetPlayer<GD>[] {
- return Array.from(this._players.values()).filter(player => player.isAI);
- }
- /**所有玩家*/
- public get all(): NetPlayer<GD>[] {
- return this.getAllPlayer();
- }
- /**在线玩家信息*/
- public get onlines(): NetPlayer<GD>[] {
- return Array.from(this._players.values()).filter(player => player.online);
- }
- /**不在线玩家信息*/
- public get outlines(): NetPlayer<GD>[] {
- return Array.from(this._players.values()).filter(player => !player.online);
- }
- /**所有玩家*/
- public getAllPlayer(): NetPlayer<GD>[] {
- return Array.from(this._players.values());
- }
- /**将玩家按 location 放到一个数组中,并自定义数组长度*/
- public getAllPlayersAtLocations(customLength: number): (NetPlayer<GD> | null)[] {
- const locationArray: (NetPlayer<GD> | null)[] = Array(customLength).fill(null);
- this._players.forEach((player) => {
- if (player.location >= 0 && player.location < customLength) locationArray[player.location] = player;
- });
- return locationArray;
- }
- /**中某个玩家的所有信息*/
- public getPlayer(id: string): NetPlayer<GD> {
- return this._players.get(id);
- }
- /**获取除了某个id的所有玩家*/
- public getExPlayer(id: string): NetPlayer<GD>[] {
- return Array.from(this._players.values()).filter(player => player.Id != id);
- }
- /**获在线或不在线的所有玩家*/
- public getOnlinePlayer(isOnline: boolean): NetPlayer<GD>[] {
- return isOnline ? this.onlines : this.outlines;
- }
- /**是否有AI权限*/
- public canAiPlayer(player: NetPlayer<GD>): boolean {
- if (!this._status) return false;
- if (!this.isHost) return false;
- if (!player || !player.isAI) return false;
- return true;
- }
- /**是否有AI权限Id,返回可以控制的player*/
- public canAiPlayerId(id: string): NetPlayer<GD> | null {
- if (!this._status) return null;
- if (!this.isHost) return null;
- const player = this._players.get(id);
- if (!player) return null;
- if (!player.isAI) return null;
- return player;
- }
- /**是否拥有权限(包括,自己和自己是主机时的ai)*/
- public hasPermission(player: NetPlayer<GD>): boolean {
- return player.isOwn || this.canAiPlayer(player);
- }
- /**创建自定义obj数据*/
- public creatObj<T extends { [key: string]: any }>(data: T): string {
- if (!this._status) return;
- if (!this.isHost) return;
- let oid: number = super.getValue('oid') ?? 0;
- oid++;
- const key = 'obj_' + oid;
- super.setValue('oid', oid);
- super.setValueDirty('oid', oid);
- super.setValue(key, data);
- super.setValueDirty(key, data);
- (this.evt as any)._emit('r_obj', key, data);
- return key;
- }
- /**根据key获取obj数据*/
- public getObj<T extends { [key: string]: any }>(key: string): T {
- return super.getValue(key);
- }
- /**获取当前所有的obj数据*/
- public getAllObj<T extends { [key: string]: any }>(): { key: string, data: T }[] {
- const list: { key: string, data: T }[] = [];
- const oid = super.getValue('oid') ?? 0;
- for (let i = 1; i <= oid; i++) {
- const key = 'obj_' + i;
- const data = super.getValue(key);
- if (data) list.push({ key: key, data: data === 0 ? null : data });
- }
- return list;
- }
- /**不建议经常更改obj */
- public changeObj<T extends { [key: string]: any }>(key: string, data: T): void {
- if (!this.isHost) return;
- const old = super.getValue(key);
- if (old) {
- super.setValue(key, data);
- super.setValueDirty(key, data);
- (this.evt as any)._emit('r_obj', key, data, old);
- }
- }
- /**删除obj数据*/
- public deleteObj<T extends { [key: string]: any } | 0>(key: string, data: T | 0 = 0) {
- if (!this.isHost) return;
- const old = super.getValue(key);
- if (old) {
- super.setValue(key, data);
- super.setValueDirty(key, data);
- (this.evt as any)._emit('r_obj', key, null, old);
- }
- }
- /**修改某个键的值*/
- public setValue<T extends StartEventNames<GD, 'r_'>, T2 extends EventParamFrist<GD, T>>(key: T, data: T2): void {
- if (!this._status) return;
- if (!this.isHost) return;//不是主机没有权限
- let old = super.getValue(key);
- if (old) {
- if (typeof data === "object") {
- //old = JSON.parse(JSON.stringify(old));
- } else if (data === old) {
- return;
- }
- }
- super.setValue(key, data);
- super.setValueDirty(key, data);
- //
- (this.evt as any)._emit(key, data, old);
- }
- /**获取数据的值*/
- public getValue<T extends StartEventNames<GD, 'r_'>, T2 extends EventParamFrist<GD, T>>(key: T): T2 {
- return super.getValue(key);
- }
- private server_change(data: { [key: string]: any }): void {
- if (!this._status) return;
- if (this.isHost) return;
- const evt: { evt: 0 | 1, key: string, data: any, old: any }[] = [];
- Object.keys(data).forEach(key => {
- evt.push(this.change(key, data[key]));
- });
- const e = (this.evt as any)
- for (let i = 0; i < evt.length; i++) {
- const ee = evt[i];
- if (!ee) continue;
- if (ee.evt === 1) {
- e._emit('r_obj', ee.key, ee.data, ee.old);
- } else {
- e._emit(ee.key, ee.data, ee.old);
- }
- }
- }
- private change(key: string, data: any): { evt: 0 | 1, key: string, data: any, old: any } | null {
- const old = super.getValue(key);
- super.setValue(key, data);
- if (typeof data !== "object" && data === old) return null;
- if (key === 'oid') {
- return null;
- } else if (key.startsWith('obj_')) {
- return { evt: 1, key: key, data: data === 0 ? null : data, old: old };
- } else {
- return { evt: 0, key: key, data: data, old: old };
- }
- }
- private _results: any[] = [];
- /**
- * 游戏结算数据
- *
- * @returns {Array} results - 包含游戏结算信息的数组。
- * 每个元素表示一个玩家的结算数据,结构如下:
- *
- * - Id: {string} 玩家唯一标识符
- * - AddScore: {number} 本局游戏中增加的星星数
- * - IsFinish: {boolean} 游戏是否已完成
- * - Rank: {number} 玩家在当前游戏中的排名
- * - Score: {number} 玩家在最后星星数
- * - TotalRank: {number} 玩家的总排名
- * - Level: {Object} 玩家当前等级的信息
- * - LowerRank: {number} 当前段位的等级
- * - Rank: {number} 段位
- * - Star: {number} 当前段位的等级的星级
- * - UserData: {Object} 玩家个人信息
- * - gid: {string} 游戏全局唯一标识符
- * - head: {string} 玩家头像的 URL
- * - hid: {number} 玩家省份id
- * - ip: {string} 玩家 IP 地址
- * - loginTime: {number} 玩家登录时间的时间戳
- * - nickName: {string} 玩家昵称
- * - openId: {string} 玩家在平台上的唯一标识符
- * - option: {string} 玩家选择的选项或设置
- * - pf: {string} 平台信息
- * - registerTime: {number} 玩家注册时间的时间戳
- * - userId: {number} 玩家在系统中的用户 ID
- * - province: {string} 玩家所在的省份
- * - Elements: {Array} 包含其他玩家的结算数据
- * - Rank: {number} 其他玩家的排名
- * - Level: {Object} 其他玩家的等级信息
- * - LowerRank: {number} 其他玩家当前段位的等级
- * - Rank: {number} 其他玩家的段位
- * - Star: {number} 其他玩家段位的等级的星级
- * - UserData: {Object} 其他玩家的个人信息(与上面的 UserData 结构相同)
- */
- public get results(): results[] {
- return this._results;
- }
- /**获取自己本局结算*/
- public get own_results(): results {
- const rs = this.results;
- if (!rs) return null;
- for (let i = 0; i < rs.length; i++) {
- if (rs[i].Id === this._own_id) {
- return rs[i];
- }
- }
- return null;
- }
- /**获取自己本局获得星星*/
- public get own_results_addScore(): number {
- const rs = this.results;
- if (!rs) return 0;
- for (let i = 0; i < rs.length; i++) {
- if (rs[i].Id === this._own_id) {
- return rs[i].AddScore;
- }
- }
- return 0;
- }
- /**获取自己本局获得名次*/
- public get own_results_rank(): number {
- const rs = this.results;
- if (!rs) return 0;
- for (let i = 0; i < rs.length; i++) {
- if (rs[i].Id === this._own_id) {
- return rs[i].Rank;
- }
- }
- return 0;
- }
- public sort_results<T extends StartEventNames<GD, "p_">>(sort_name: T): void {
- if (this._results.length > 1) {
- this._results.sort((a, b) => {
- if (a.Rank === 0 && b.Rank === 0) {
- if (sort_name) {
- return this.getPlayer(b.Id).getValue(sort_name) - this.getPlayer(a.Id).getValue(sort_name);
- }
- return 0;
- } else if (a.Rank === 0) {
- return 1;
- } else if (b.Rank === 0) {
- return -1;
- } else {
- return a.Rank - b.Rank;
- }
- });
- }
- }
- private closed(data: any): void {
- this._status = false;
- this._results.length = 0;
- const ps = Object.keys(data).map(key => ({ key, data: data[key] }));
- for (let i = 0; i < ps.length; i++) {
- const key = ps[i].key;
- const user = ps[i].data;
- user.Id = key;
- const player = this.getPlayer(key);
- (player as any).set_level(user.Level, user.Rank, user.Score, user.TotalRank);
- user.UserData = {
- gid: player.gid, head: player.head, hid: player.hid, ip: player.ip, loginTime: player.loginTime,
- nickName: player.nickName, openId: player.openId, option: player.option, pf: player.pf, registerTime: player.registerTime, userId: player.userId, province: player.province
- };
- const elements = user.Elements;
- if (elements) {
- for (let i = 0; i < elements.length; i++) {
- elements[i] = transUserDataform(elements[i]);
- }
- }
- this._results.push(user);
- };
- if (this._results.length > 1) {
- this._results.sort((a, b) => {
- if (a.Rank === 0 && b.Rank === 0) {
- return 0;
- } else if (a.Rank === 0) {
- return 1;
- } else if (b.Rank === 0) {
- return -1;
- } else {
- return a.Rank - b.Rank;
- }
- });
- }
- //
- (this.evt as any)._emit('r_closed');
- }
- private finish(pid: string, rank: number): void {
- const player = this.getPlayer(pid);
- if (!player) return;
- (player as any).setFinish(rank);
- (this.evt as any)._emit('r_finish', pid, rank);
- }
- private exit(pid: string): void {
- const player = this.getPlayer(pid);
- if (!player) return;
- const location = player.location;
- const nickName = player.nickName;
- (player as any).exit();
- (this.evt as any)._emit('r_exit', pid, location, nickName);
- }
- /**向房间所有玩家发消息*/
- public sendEvt<T extends StartEventNames<GD, 'revt_'>, T2 extends EventParams<GD, T>>(key: T, ...data: T2): void {
- if (!this._status) return;
- this._waitSends_mode0.push({ type: key, data: data });
- }
- /**向房间主机发消息*/
- public sendToHost<T extends StartEventNames<GD, 'revt_'>, T2 extends EventParams<GD, T>>(key: T, ...data: T2): void {
- if (!this._status) return;
- this._waitSends_mode1.push({ type: key, data: data });
- }
- /**向房间其它玩家*/
- public sendToOther<T extends StartEventNames<GD, 'revt_'>, T2 extends EventParams<GD, T>>(key: T, ...data: T2): void {
- if (!this._status) return;
- this._waitSends_mode2.push({ type: key, data: data });
- }
- /**处理发送事件*/
- private doSendMode(): void {
- if (this._waitSends_mode0.length > 0) {
- this._send(C2S_MESSAGE, this._waitSends_mode0);
- this._waitSends_mode0.length = 0;
- }
- if (this._waitSends_mode1.length > 0) {
- this._send(C2S_MESSAGE_TO_HOST, this._waitSends_mode1);
- this._waitSends_mode1.length = 0;
- }
- if (this._waitSends_mode2.length > 0) {
- this._send(C2S_MESSAGE_TO_OTHER, this._waitSends_mode2);
- this._waitSends_mode2.length = 0;
- }
- }
- private _chat_msg: string | null = null;
- private _last_chat_time: number = 0;
- /**房间里聊天 ,成功返回true,发送频率过快返回false*/
- public chat(msg: string): boolean {
- const now = chsdk.date.now();
- if (now - this._last_chat_time < 1000) return false;
- this._last_chat_time = now;
- this._chat_msg = msg;
- return true;
- }
- private onChat(id: string, msg: string): void {
- const p = this.getPlayer(id);
- (this.evt as any)._emit('r_chat', id, msg, p.location, p.nickName);
- }
- private doSendChat() {
- if (this._chat_msg) {
- this._send(C2S_TALK, this._chat_msg);
- this._chat_msg = null;
- }
- this.doSendMode();
- const ps = this.all;
- for (let i = 0; i < ps.length; i++) {
- const p = ps[i];
- (p as any).doFinishGame(() => { this._send(C2S_FINISH, p.Id); });
- }
- }
- public doSendDirty() {
- (this.own as any).doDirtyData((dirty) => {
- this._send(C2S_UPDATE_USER_GAMEDATA, { userKey: this.own.Id, data: dirty });
- })
- if (!this.isHost) return;
- (this as any).doDirtyData((dirty) => {
- this._send(C2S_UPDATE_ROOM_GAMEDATA, dirty);
- })
- const ais = this.ais;
- for (let i = 0; i < ais.length; i++) {
- const ai = ais[i];
- (ai as any).doDirtyData((dirty) => {
- this._send(C2S_UPDATE_USER_GAMEDATA, { userKey: ai.Id, data: dirty });
- })
- }
- }
- //
- private _onEvt(key: string, data: any): void {
- (this.evt as any)._emit(key, ...data);
- }
- //
- public dispose(): void {
- super.dispose();
- this._send = null;
- this._players.forEach((v, _) => {
- v.dispose();
- });
- this._players.clear();
- this._status = false;
- this.evt.clearAll();
- }
- }
|