Browse Source

拼接部分UI

en 2 months ago
commit
dd26a573f4
100 changed files with 7155 additions and 0 deletions
  1. 2 0
      .creator/asset-template/typescript/Custom Script Template Help Documentation.url
  2. 5 0
      .creator/default-meta.json
  3. 24 0
      .gitignore
  4. 9 0
      assets/ch.meta
  5. 9 0
      assets/ch/audio.meta
  6. 365 0
      assets/ch/audio/audio.ts
  7. 9 0
      assets/ch/audio/audio.ts.meta
  8. 9 0
      assets/ch/ch-sdk.meta
  9. 0 0
      assets/ch/ch-sdk/ch-sdk.umd.js
  10. 17 0
      assets/ch/ch-sdk/ch-sdk.umd.js.meta
  11. 341 0
      assets/ch/ch-sdk/chsdk.d.ts
  12. 9 0
      assets/ch/ch-sdk/chsdk.d.ts.meta
  13. 45 0
      assets/ch/ch-sdk/提示说明.txt
  14. 11 0
      assets/ch/ch-sdk/提示说明.txt.meta
  15. 31 0
      assets/ch/ch.ts
  16. 9 0
      assets/ch/ch.ts.meta
  17. 9 0
      assets/ch/ch_module.meta
  18. 9 0
      assets/ch/ch_module/Config.meta
  19. 146 0
      assets/ch/ch_module/Config/PassCfg.ts
  20. 9 0
      assets/ch/ch_module/Config/PassCfg.ts.meta
  21. 212 0
      assets/ch/ch_module/Config/TaskCfg.ts
  22. 9 0
      assets/ch/ch_module/Config/TaskCfg.ts.meta
  23. 9 0
      assets/ch/ch_module/Mgr.meta
  24. 89 0
      assets/ch/ch_module/Mgr/BundleMgr.ts
  25. 1 0
      assets/ch/ch_module/Mgr/BundleMgr.ts.meta
  26. 171 0
      assets/ch/ch_module/Mgr/ResMgr.ts
  27. 1 0
      assets/ch/ch_module/Mgr/ResMgr.ts.meta
  28. 9 0
      assets/ch/ch_module/Module.meta
  29. 212 0
      assets/ch/ch_module/Module/Ch_jigsaw.ts
  30. 9 0
      assets/ch/ch_module/Module/Ch_jigsaw.ts.meta
  31. 268 0
      assets/ch/ch_module/Module/Ch_pass.ts
  32. 1 0
      assets/ch/ch_module/Module/Ch_pass.ts.meta
  33. 263 0
      assets/ch/ch_module/Module/Ch_task.ts
  34. 1 0
      assets/ch/ch_module/Module/Ch_task.ts.meta
  35. 160 0
      assets/ch/ch_module/Module/Ch_turn.ts
  36. 1 0
      assets/ch/ch_module/Module/Ch_turn.ts.meta
  37. 351 0
      assets/ch/ch_util.ts
  38. 9 0
      assets/ch/ch_util.ts.meta
  39. 396 0
      assets/ch/chsdk_inside.d.ts
  40. 9 0
      assets/ch/chsdk_inside.d.ts.meta
  41. 9 0
      assets/ch/net.meta
  42. 112 0
      assets/ch/net/NetBase.ts
  43. 1 0
      assets/ch/net/NetBase.ts.meta
  44. 233 0
      assets/ch/net/NetPlayer.ts
  45. 1 0
      assets/ch/net/NetPlayer.ts.meta
  46. 475 0
      assets/ch/net/NetRoom.ts
  47. 1 0
      assets/ch/net/NetRoom.ts.meta
  48. 255 0
      assets/ch/net/NetTeam.ts
  49. 1 0
      assets/ch/net/NetTeam.ts.meta
  50. 114 0
      assets/ch/net/TTWsClient.ts
  51. 1 0
      assets/ch/net/TTWsClient.ts.meta
  52. 109 0
      assets/ch/net/WXWsClient.ts
  53. 1 0
      assets/ch/net/WXWsClient.ts.meta
  54. 105 0
      assets/ch/net/WsClient.ts
  55. 9 0
      assets/ch/net/WsClient.ts.meta
  56. 9 0
      assets/ch/net/modules.meta
  57. 4 0
      assets/ch/net/modules/msgpack.min.d.ts
  58. 9 0
      assets/ch/net/modules/msgpack.min.d.ts.meta
  59. 0 0
      assets/ch/net/modules/msgpack.min.js
  60. 17 0
      assets/ch/net/modules/msgpack.min.js.meta
  61. 723 0
      assets/ch/net/net.ts
  62. 1 0
      assets/ch/net/net.ts.meta
  63. 9 0
      assets/ch/pvp.meta
  64. 241 0
      assets/ch/pvp/ch_pvp.ts
  65. 1 0
      assets/ch/pvp/ch_pvp.ts.meta
  66. 9 0
      assets/ch/start.meta
  67. 22 0
      assets/ch/start/ch_net_help.ts
  68. 1 0
      assets/ch/start/ch_net_help.ts.meta
  69. 22 0
      assets/ch/start/ch_sdk_comp.ts
  70. 9 0
      assets/ch/start/ch_sdk_comp.ts.meta
  71. 224 0
      assets/ch/start/ch_start.ts
  72. 1 0
      assets/ch/start/ch_start.ts.meta
  73. 9 0
      assets/ch/table.meta
  74. 235 0
      assets/ch/table/IndexedDB.ts
  75. 9 0
      assets/ch/table/IndexedDB.ts.meta
  76. 155 0
      assets/ch/table/TableUtil.ts
  77. 1 0
      assets/ch/table/TableUtil.ts.meta
  78. 9 0
      assets/core_tgx.meta
  79. 9 0
      assets/core_tgx/AnnotationUtil.meta
  80. 9 0
      assets/core_tgx/AnnotationUtil/Singleton.meta
  81. 115 0
      assets/core_tgx/AnnotationUtil/Singleton/SingletonAnnotation.ts
  82. 9 0
      assets/core_tgx/AnnotationUtil/Singleton/SingletonAnnotation.ts.meta
  83. 9 0
      assets/core_tgx/CachePool.meta
  84. 132 0
      assets/core_tgx/CachePool/CachePool.ts
  85. 9 0
      assets/core_tgx/CachePool/CachePool.ts.meta
  86. 9 0
      assets/core_tgx/CachePool/NodePool.ts.meta
  87. 9 0
      assets/core_tgx/UIAnnotation.meta
  88. 73 0
      assets/core_tgx/UIAnnotation/UIControllerAnnotation.ts
  89. 9 0
      assets/core_tgx/UIAnnotation/UIControllerAnnotation.ts.meta
  90. 9 0
      assets/core_tgx/base.meta
  91. 117 0
      assets/core_tgx/base/AudioMgr.ts
  92. 9 0
      assets/core_tgx/base/AudioMgr.ts.meta
  93. 68 0
      assets/core_tgx/base/EventDispatcher.ts
  94. 9 0
      assets/core_tgx/base/EventDispatcher.ts.meta
  95. 41 0
      assets/core_tgx/base/InputMgr.ts
  96. 13 0
      assets/core_tgx/base/InputMgr.ts.meta
  97. 37 0
      assets/core_tgx/base/ModuleClass.ts
  98. 9 0
      assets/core_tgx/base/ModuleClass.ts.meta
  99. 46 0
      assets/core_tgx/base/ModuleContext.ts
  100. 9 0
      assets/core_tgx/base/ModuleContext.ts.meta

+ 2 - 0
.creator/asset-template/typescript/Custom Script Template Help Documentation.url

@@ -0,0 +1,2 @@
+[InternetShortcut]
+URL=https://docs.cocos.com/creator/manual/en/scripting/setup.html#custom-script-template

+ 5 - 0
.creator/default-meta.json

@@ -0,0 +1,5 @@
+{
+  "image": {
+    "type": "sprite-frame"
+  }
+}

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+
+#///////////////////////////
+# Cocos Creator 3D Project
+#///////////////////////////
+library/
+temp/
+local/
+build/
+profiles/
+native
+#//////////////////////////
+# NPM
+#//////////////////////////
+node_modules/
+
+#//////////////////////////
+# VSCode
+#//////////////////////////
+.vscode/
+
+#//////////////////////////
+# WebStorm
+#//////////////////////////
+.idea/

+ 9 - 0
assets/ch.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "db192c13-9d77-4c2d-8ffb-44aee47087c7",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/ch/audio.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "38e1adf1-e497-447e-ac89-61392a94ed0a",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 365 - 0
assets/ch/audio/audio.ts

@@ -0,0 +1,365 @@
+import { assetManager, AudioClip, AudioSource, director, Node } from "cc";
+const ch_log = chsdk.log;
+const ch_storage = chsdk.storage;
+/**音频等资源加载方式*/
+export enum loadType {
+    /**未知*/
+    none = 0,
+    /**bundle*/
+    bundle = 1,
+    /**远程*/
+    remote = 2,
+}
+interface AudioConfig {
+    volume_music: number;
+    volume_effect: number;
+    switch_music: boolean;
+    switch_effect: boolean;
+}
+/**音频播放控制
+ * 需要初始化资源加载方式
+ * loadType.bundle 时需要设置 bundle名
+ * loadType.remote 测试使用远程的音频需要设置远程地址
+*/
+export default class ch_audio {
+    private static _instance: ch_audio;
+    public static getInstance(): ch_audio {
+        if (!this._instance) this._instance = new ch_audio();
+        return this._instance;
+    }
+    private _volume_music: number = 1;
+    private _volume_effect: number = 1;
+    private _switch_music: boolean = true;
+    private _switch_effect: boolean = true;
+    private readonly _effect_max: number = 5;
+    private _effect_index: number = 0;
+    private _effect_source_pool: AudioSource[] = [];
+    private _music_source: AudioSource;
+    private _load_type: loadType = loadType.none;
+    private _bundle_name: string;
+    private _remote_url: string;
+    private _playing_sound: Set<string> = new Set();
+    private _remote_cache: Map<string, AudioClip> = new Map();
+    constructor() {
+        const audio = new Node();
+        audio.name = '__ch_audio__';
+        director.getScene().addChild(audio);
+        director.addPersistRootNode(audio);
+        this._music_source = this._create(audio);
+        for (let i = 0; i < this._effect_max; i++) {
+            this._effect_source_pool.push(this._create(audio));
+        }
+        this.load();
+    }
+    /**
+     * 创建音频源
+     * @param node 节点
+     * @param volume 音量
+     * @returns AudioSource 音频源组件
+     */
+    private _create(node: Node): AudioSource {
+        const source = node.addComponent(AudioSource);
+        source.loop = false;
+        source.playOnAwake = false;
+        source.volume = 0.5;
+        return source;
+    }
+    /**初始化*/
+    init(load_type: loadType, bundle_name: string, remote_url: string): void {
+        this._load_type = load_type;
+        this._bundle_name = bundle_name;
+        this._remote_url = remote_url.endsWith('/') ? remote_url : remote_url + '/';
+    }
+    /**切换bundle*/
+    set_bundle_name(bundle_name: string): void {
+        this._bundle_name = bundle_name;
+    }
+    /**
+     * 释放通过 [[load]] 或者 [[loadDir]] 加载的声音资源。
+     * @param sound 声音资源路径
+     */
+    release(sound?: string): void {
+        if (this._load_type == loadType.none) {
+            ch_log.warn('音频模块未初始化');
+        } else if (this._load_type == loadType.bundle) {
+            const bundle = assetManager.getBundle(this._bundle_name);
+            if (!sound) {
+                bundle.releaseAll();
+            } else {
+                bundle.release(sound, AudioClip);
+            }
+        } else if (this._load_type == loadType.remote) {
+            if (!sound) {
+                this._remote_cache.forEach((v, k) => {
+                    assetManager.releaseAsset(v);
+                });
+                this._remote_cache.clear();
+            } else {
+                const path = this._remote_url + sound;
+                const clip = this._remote_cache.get(path);
+                if (clip) {
+                    assetManager.releaseAsset(clip);
+                    this._remote_cache.delete(path);
+                }
+            }
+        }
+    }
+    /** 保存音乐音效的音量、开关配置数据到本地 */
+    save() {
+        const local_data: any = {};
+        local_data.volume_music = this._volume_music;
+        local_data.volume_effect = this._volume_effect;
+        local_data.switch_music = this._switch_music;
+        local_data.switch_effect = this._switch_effect;
+        ch_storage.set("ch_audio", local_data);
+    }
+    /** 本地加载音乐音效的音量、开关配置数据并设置到游戏中 */
+    load() {
+        const local_data = ch_storage.getObject("ch_audio");
+        if (local_data) {
+            try {
+                this.setState(local_data);
+            }
+            catch (e) {
+                this.setStateDefault();
+            }
+        }
+        else {
+            this.setStateDefault();
+        }
+    }
+
+    private setState(local_data: AudioConfig) {
+        this.volumeMusic = local_data.volume_music;
+        this.volumeEffect = local_data.volume_effect;
+        this.switchMusic = local_data.switch_music;
+        this.switchEffect = local_data.switch_effect;
+    }
+    private setStateDefault() {
+        this.volumeMusic = 0.8;
+        this.volumeEffect = 0.8;
+        this.switchMusic = true;
+        this.switchEffect = true;
+    }
+    /**
+     * 获取背景音乐音量
+     */
+    get volumeMusic(): number {
+        return this._volume_music;
+    }
+    /** 
+     * 设置背景音乐音量
+     * @param value     音乐音量值
+     */
+    set volumeMusic(value: number) {
+        this._volume_music = value;
+        this._music_source.volume = value;
+    }
+    /** 
+     * 获取背景音乐开关值 
+     */
+    get switchMusic(): boolean {
+        return this._switch_music;
+    }
+    /** 
+     * 设置背景音乐开关值
+     * @param value     开关值
+     */
+    set switchMusic(value: boolean) {
+        this._switch_music = value;
+        if (value == false) this._music_source.stop();
+    }
+    /** 
+     * 获取音效音量 
+     */
+    get volumeEffect(): number {
+        return this._volume_effect;
+    }
+    /**
+     * 设置获取音效音量
+     * @param value     音效音量值
+     */
+    set volumeEffect(value: number) {
+        this._volume_effect = value;
+        for (let i = 0; i < this._effect_source_pool.length; i++) {
+            this._effect_source_pool[i].volume = this._volume_effect;
+        }
+    }
+
+    /** 
+     * 获取音效开关值 
+     */
+    get switchEffect(): boolean {
+        return this._switch_effect;
+    }
+    /**
+     * 设置音效开关值
+     * @param value     音效开关值
+     */
+    set switchEffect(value: boolean) {
+        this._switch_effect = value;
+        if (value == false) {
+            for (let i = 0; i < this._effect_source_pool.length; i++) {
+                this._effect_source_pool[i].stop();
+            }
+        }
+    }
+    /**
+     * @en
+     * play short audio, such as strikes,explosions
+     * @zh
+     * 播放短音频,比如 打击音效,爆炸音效等
+     * @param sound clip or url for the audio
+     * @param interval 同名字音频限制播放间隔(毫秒)   (默认:0不限制  特殊系数:>0 <=1 使用音频时间X此系数)
+     */
+    playOneShot(sound: AudioClip | string, interval: number = 0, remote_ext: string = '.mp3') {
+        if (!this._switch_effect) return;
+        if (sound instanceof AudioClip) {
+            this.doPlayOneShot(sound, interval);
+        }
+        else {
+            if (this._load_type == loadType.none) {
+                ch_log.warn('音频模块未初始化');
+            } else if (this._load_type == loadType.bundle) {
+                const bundle = assetManager.getBundle(this._bundle_name);
+                if (!bundle) {
+                    ch_log.warn(`请确保 bundle${this._bundle_name} 已加载`);
+                } else {
+                    bundle.load(sound, (err, clip: AudioClip) => {
+                        if (err) {
+                            ch_log.error(err);
+                        }
+                        else {
+                            this.doPlayOneShot(clip, interval);
+                        }
+                    });
+                }
+            } else if (this._load_type == loadType.remote) {
+                const fullPath = this._remote_url + sound + remote_ext;
+                if (this._remote_cache.has(fullPath)) {
+                    this.doPlayOneShot(this._remote_cache.get(fullPath)!, interval);
+                    return;
+                }
+                assetManager.loadRemote(fullPath, (err: Error | null, clip: AudioClip) => {
+                    if (err) {
+                        ch_log.error(err);
+                    } else {
+                        this._remote_cache.set(fullPath, clip);
+                        this.doPlayOneShot(clip, interval);
+                    }
+                });
+            }
+        }
+    }
+    private doPlayOneShot(clip: AudioClip, interval: number): void {
+        const name: string = clip.name;
+        if (interval > 0) {
+            if (this._playing_sound.has(name)) return;
+            this._playing_sound.add(name);
+            const time = interval <= 1 ? clip.getDuration() * interval * 1000 : interval;
+            setTimeout(() => { this._playing_sound.delete(name); }, time);
+            this.getNextEffectSource().playOneShot(clip, this._volume_effect);
+        } else {
+            this.getNextEffectSource().playOneShot(clip, this._volume_effect);
+        }
+    }
+    private getNextEffectSource(): AudioSource {
+        const source = this._effect_source_pool[this._effect_index];
+        this._effect_index = (this._effect_index + 1) % this._effect_max;
+        return source;
+    }
+    /**
+     * @en
+     * play long audio, such as the bg music
+     * @zh
+     * 播放长音频,比如 背景音乐
+     * @param sound clip or url for the sound
+     */
+    play(sound: AudioClip | string, remote_ext: string = '.mp3') {
+        if (!this._switch_music) return;
+        if (sound instanceof AudioClip) {
+            this._music_source.loop = true;
+            this._music_source.stop();
+            this._music_source.clip = sound;
+            this._music_source.play();
+            this._music_source.volume = this._volume_music;
+        }
+        else {
+            if (this._load_type == loadType.none) {
+                ch_log.warn('音频模块未初始化');
+            } else if (this._load_type == loadType.bundle) {
+                const bundle = assetManager.getBundle(this._bundle_name);
+                if (!bundle) {
+                    ch_log.warn(`请确保 bundle${this._bundle_name} 已加载`);
+                } else {
+                    bundle.load(sound, (err, clip: AudioClip) => {
+                        if (err) {
+                            ch_log.error(err);
+                        }
+                        else {
+                            this._music_source.loop = true;
+                            this._music_source.stop();
+                            this._music_source.clip = clip;
+                            this._music_source.play();
+                            this._music_source.volume = this._volume_music;
+                        }
+                    });
+                }
+            } else if (this._load_type == loadType.remote) {
+                assetManager.loadRemote(this._remote_url + sound + remote_ext, (err: Error | null, clip: AudioClip) => {
+                    if (err) {
+                        ch_log.error(err);
+                    }
+                    else {
+                        this._music_source.loop = true;
+                        this._music_source.stop();
+                        this._music_source.clip = clip;
+                        this._music_source.play();
+                        this._music_source.volume = this._volume_music;
+                    }
+                });
+            }
+        }
+    }
+
+    /**
+     * stop the audio play
+     */
+    stop() {
+        this._music_source.stop();
+        for (let i = 0; i < this._effect_source_pool.length; i++) {
+            this._effect_source_pool[i].stop();
+
+        }
+    }
+    /**stop and clean */
+    clean() {
+        this._music_source.stop();
+        this._music_source.clip?.name
+        this._music_source.clip = null;
+        for (let i = 0; i < this._effect_source_pool.length; i++) {
+            this._effect_source_pool[i].stop();
+            this._effect_source_pool[i].clip = null;
+        }
+    }
+    /**
+     * pause the audio play
+     */
+    pause() {
+        this._music_source.pause();
+    }
+
+    /**
+     * resume the audio play
+     */
+    resume() {
+        if (!this._switch_music) return;
+        this._music_source.play();
+    }
+
+    /** 重播当前音乐 */
+    public replay_music(): void {
+        this._music_source.stop();
+        this._music_source.play();
+    }
+}

+ 9 - 0
assets/ch/audio/audio.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "800c25c3-c527-42e8-9c74-d1964938c8cb",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/ch/ch-sdk.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "7e49db0b-86bc-4aea-8ef8-2aa8a18d95d8",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

File diff suppressed because it is too large
+ 0 - 0
assets/ch/ch-sdk/ch-sdk.umd.js


+ 17 - 0
assets/ch/ch-sdk/ch-sdk.umd.js.meta

@@ -0,0 +1,17 @@
+{
+  "ver": "4.0.24",
+  "importer": "javascript",
+  "imported": true,
+  "uuid": "98a7aaa0-9482-4d5f-b82b-657c06abeaca",
+  "files": [
+    ".js"
+  ],
+  "subMetas": {},
+  "userData": {
+    "loadPluginInEditor": true,
+    "loadPluginInWeb": true,
+    "loadPluginInNative": true,
+    "loadPluginInMiniGame": true,
+    "isPlugin": true
+  }
+}

+ 341 - 0
assets/ch/ch-sdk/chsdk.d.ts

@@ -0,0 +1,341 @@
+declare namespace chsdk {
+    /**服务器类型对应地址*/
+    enum serverType {
+        test = 1,
+        /**测试服*/
+        dev = 2,
+        /**正线服*/
+        online = 3
+    }
+    /**广告类型*/
+    enum ad_type {
+        /**激励视频*/
+        rewarded = "Rewarded",
+        /**插屏广告*/
+        interstitial = "Interstitial",
+        /**banner 广告*/
+        banner = "Banner",
+        /**自定义广告*/
+        custom = "Custom"
+    }
+    /**广告状态*/
+    enum ad_state {
+        /**展示失败*/
+        fail = 0,
+        /**展示成功*/
+        show = 1,
+        /**完整展示或点击*/
+        rewarded = 2
+    }
+    /**日志等级*/
+    enum loglevel {
+        OFF = 0,
+        ERROR = 1,
+        WARN = 2,
+        DEBUG = 3,
+        INFO = 4,
+        ALL = 5
+    }
+    /**请求码*/
+    enum code {
+        fail = 999,
+        /**请求成功*/
+        success = 0,
+        /**token失效 */
+        token_err = -1,
+        /**请求超时*/
+        time_out = 1001,
+        /**同一接口请求次数超出限制*/
+        too_many_requests = 1002
+    }
+    /**更新周期
+      1永久,
+      2日更新,
+      3周更新,
+      4月更新
+    */
+    enum updateType {
+        none = 1,
+        day = 2,
+        week = 3,
+        month = 4
+    }
+    /**平台*/
+    enum pf {
+        /**测试*/
+        web = "web",
+        /**微信*/
+        wx = "wx",
+        /**抖音*/
+        tt = "tt"
+    }
+    type pf_conf = {
+        adUnitId: string;
+        multiton: boolean;
+        inster_unitId?: string;
+        tmplIds?: string[];
+        bannerAds?: {
+            adUnitId: string;
+            adIntervals: number;
+            style: {
+                left: number;
+                top: number;
+                width: number;
+                height: number;
+            };
+        }[];
+    } | null;
+    /**初始化sdk,并登录对应平台
+     * @param gid 游戏ID(开发后台配置)
+     * @param loglevel 日志级别 关闭=0, ERROR = 1, WARN = 2, DEBUG = 3, INFO = 4, ALL = 5,
+     * @param serverIP 服务器类型  测试服 =2, 正线服 = 3,
+     * @returns code === 0 成功 err?=错误信息
+     */
+    function init(gid: string, loglevel: loglevel, serverIP: serverType): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**当前平台*/
+    function get_pf(): pf;
+    /**玩家平台信息*/
+    function get_player_info(): {
+        nickName: string;
+        avatarUrl: string;
+        gender: number;
+        hid: number;
+        province: string;
+        ip: string;
+        loginTime: number;
+        registerTime: number;
+    };
+    /**游戏id*/
+    function get_gid(): string;
+    /**游戏是否初始完毕*/
+    function get_inited(): boolean;
+    /**玩家id*/
+    function get_uid(): number;
+    /**玩家openid*/
+    function get_openid(): string;
+    /**获取平台上用户的呢称和头像,获取成功后自动同步到服务器,一般在进排行榜前调用*/
+    function getUserInfo(): Promise<{
+        nickName: string;
+        avatarUrl: string;
+        gender: number;
+    }>;
+    /**播放奖励广告
+            * @param scene 奖励目的场景
+           playRewardAd("复活")
+           .then(success => {
+             if (success) {
+                 console.log("广告播放成功,用户获得奖励");
+                 // 在这里执行成功后的操作
+             } else {
+                 console.log("广告播放失败,用户未获得奖励");
+                 // 处理广告播放失败的逻辑
+             }
+           })
+           .catch(error => {
+               console.error("发生未处理的错误: ", error);
+            });
+            */
+    function playRewardAd(reason: string): Promise<boolean>;
+    /**展示插屏广告*/
+    function showInsterAd(scene: string): Promise<boolean>;
+    /**关闭插屏广告*/
+    function destroyInsterAd(): void;
+    /**展示Banner广告*/
+    function showBannerAd(index: number, scene: string): Promise<boolean>;
+    /**隐藏Banner广告 */
+    function hideBannerAd(index: number): void;
+    /**设置广告配置*/
+    function setConf(pf: pf, conf: pf_conf): void;
+    /**当前平台是否有侧边栏功能*/
+    function checkHasSidebar(): boolean;
+    /**抖音是否从侧边栏进入游戏,微信是否从我的小程序进入游戏*/
+    function checkFromSidebar(): boolean;
+    /**抖音进入侧边栏*/
+    function goToSidebar(): Promise<boolean>;
+    /**上报自定义埋点事件
+    * @param evt 后台定义的事件名
+    * @param data 后台定义的事件参数
+    */
+    function reportEvent(evt: string, data: {
+        [key: string]: any;
+    }): void;
+    /**上报广告
+    * @param adsId 广告id
+    * @param adsState 广告状态 展示失败=0,展示成功=1,完整展示获得奖励=2
+    * @param adsScene 广告场景目的
+    * @param adsType 广告类型  (默认)激励视频 ='Rewarded',插屏广告='Interstitial', banner 广告 ='Banner',自定义广告 ='Custom'
+    * @param adStartTime 开始播放时间戳ms
+    * @returns code = 0 上报成功 err 上报错误提示
+    */
+    function reportSeeAds(adsId: string, adsType: ad_type, adsState: ad_state, adsScene: string, adStartTime: number): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 上传游戏远程存储数据
+    * @param key 要保存的key
+    * @param save_data 游戏数据
+    * @returns code 错误码 errMsg 错误信息
+    */
+    function saveGameData(key: string, save_data: {
+        [key: string]: any;
+    }): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 获取游戏远程存储数据
+    * @param key 数据key
+    * @returns code 错误码 errMsg 错误信息 data 游戏数据
+    */
+    function loadGameData(key: string): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 更改用户名称头像等信息
+    * @param nickName 名称
+    * @param avatar 头像
+    */
+    function changeUserData(nickName?: string, avatar?: string): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 上传排行榜数据
+    * @param rankField 排行榜类型标识
+    * @param rankValue 分数(如果分数和缓存中的数据一致将不上传)
+    * @param update 更新周期(不同周期为不同的排行榜)  1永久, 2每日,3每星期,4每月
+    * @param valueType 数值类 0覆盖(默认) 1:累加
+    * @param extend 扩展数据(非必须)
+    */
+    function saveRankData(rankField: string, rankValue: number, update?: chsdk.updateType, valueType?: 0 | 1, extend?: {
+        [key: string]: any;
+    }): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 获取排行榜信息
+    * @param rankField 排行榜类型标识
+    * @param update 更新周期(不同周期为不同的排行榜)  1永久, 2每日,3每星期,4每月
+    * @param count 拉取个数
+    * @param isOwn 是否拉取自己的排行 默认为false
+    * @param cache 默认为true,1分钟内优先从缓存中拿排行数据,否则请求服务器
+    * @returns data:{errCode 错误码 errMsg 错误信息 data 排行榜数据(list) own 自己的排名}
+    */
+    function loadRankData(rankField: string, update?: updateType, count?: number, isOwn?: boolean, cache?: boolean): Promise<{
+        code: number;
+        err?: string;
+        data?: {
+            list?: {
+                head: string;
+                nickName: string;
+                rank: number;
+                score: number;
+                userId: number;
+                [key: string]: any;
+            }[];
+            own?: {
+                head: string;
+                nickName: string;
+                rank: number;
+                score: number;
+                userId: number;
+                [key: string]: any;
+            };
+        };
+    }>;
+    /**
+    * @description 上传地区排行榜数据
+    * @param rankField 排行榜类型标识
+    * @param rankValue 分数(如果分数和缓存中的数据一致将不上传)
+    * @param update 更新周期(不同周期为不同的排行榜)  1永久, 2每日,3每星期,4每月
+    * @param extend 扩展数据(非必须)
+    */
+    function saveProvinceRankData(rankField: string, rankValue: number, update?: updateType, extend?: {
+        [key: string]: any;
+    }): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 获取具体某地区排行榜信息
+    * @param hid 省份代码
+    * @param rankField 排行榜类型标识
+    * @param update 更新周期(不同周期为不同的排行榜)  1永久, 2每日,3每星期,4每月
+    * @param count 拉取个数
+    * @param isOwn 是否显示自己的排行 默认为false不显示
+    * @param cache 默认为true,1分钟内优先从缓存中拿排行数据,否则请求服务器
+    * @returns data:{errCode 错误码 errMsg 错误信息 data 排行榜数据数组}
+    */
+    function loadProvinceRankData(hid: number, rankField: string, update?: updateType, count?: number, isOwn?: boolean, cache?: boolean): Promise<{
+        code: number;
+        err?: string;
+        data?: {
+            list?: {
+                head: string;
+                nickName: string;
+                rank: number;
+                score: number;
+                userId: number;
+                [key: string]: any;
+            }[];
+            own?: {
+                head: string;
+                nickName: string;
+                rank: number;
+                score: number;
+                userId: number;
+                [key: string]: any;
+            };
+        };
+    }>;
+    /**
+    * @description 获取全国省份地区排行榜信息
+    * @param rankField 排行榜类型
+    * @param cache 默认为true,1分钟内优先从缓存中拿排行数据,否则请求服务器
+    * @returns data:{errCode 错误码 errMsg 错误信息 data 排行榜数据数组{hid 省份代码,province省份名...}}
+    */
+    function loadProvinceRankInfo(rankField: string, cache?: boolean): Promise<{
+        code: number;
+        err?: string;
+        data?: {
+            hid: number;
+            province: string;
+            rank: number;
+            score: number;
+        }[];
+    }>;
+    /**玩家改变玩家默认省份
+    * @param hid 省份代码
+    * @returns errCode 错误码 errMsg 错误信息 time服务器时间戳(秒)
+    */
+    function setUserLocation(hid: number): Promise<{
+        code: number;
+        err?: string;
+        data?: number;
+    }>;
+    /**获取服务器时间戳(秒)
+    * @returns errCode 错误码 errMsg 错误信息 time服务器时间戳(秒)
+    */
+    function getServerTime(): Promise<{
+        code: number;
+        err?: string;
+        time?: number;
+    }>;
+    /**获取城市代码对应地名 */
+    function provinceCode2Name(hid: number): string;
+    /**获取所有城市代码名字*/
+    function getAllProvince(): [number, string][];
+}

+ 9 - 0
assets/ch/ch-sdk/chsdk.d.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "e78216d9-6fde-4483-9c8b-1e4644183842",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 45 - 0
assets/ch/ch-sdk/提示说明.txt

@@ -0,0 +1,45 @@
+1.
+chsdk.d.ts
+ch-sdk.umd.js
+直接将上面的文件复制到cocosreator assets里
+
+2.
+尽早调用chsdk.init 初始化后成功后可以调用其它接口
+具体接口参看接口chsdk.d.ts提示说明
+3.
+注意 chsdk.init 初始化的参数
+上线测试阶段初始化可以使用测试服
+正式上线后改为正式服关闭调试输出
+游戏 id和自定义上报事件需要在后台设置,需要和运营人员沟通
+
+下面的地址需要微信或抖音后台添加到白名单
+测试服务器地址
+https://dev.ichunhao.cn
+https://receivetest.ichunhao.cn
+正式服务器地址
+https://receive.ichunhao.cn
+https://app.ichunhao.cn
+
+
+cocoscreator 测试脚本
+@ccclass('test')
+export class test extends Component {
+    async start() {
+        const ret = await chsdk.init('1001',chsdk.loglevel.ALL,chsdk.serverType.dev);
+        console.log('初始化',ret); 
+        let r = await chsdk.saveRankData('test',99,chsdk.updateType.day);
+        console.log('上传排行傍数据',r); 
+        r = await chsdk.loadRankData('test',chsdk.updateType.day,50,true);
+        console.log('拉取排行傍数据',r); 
+        r = await chsdk.saveGameData('test',{test_data:"测试数据"});
+        console.log('保存游戏数据',r);
+        r = await chsdk.loadGameData('test');
+        console.log('加载游戏数据',r);
+        r = await chsdk.getServerTime();
+        console.log('获取服务器时间',r);
+        r=await chsdk.loadProvinceRankInfo('level');
+        console.log('获取全国榜',r);
+    }
+    update(deltaTime: number) {
+    }
+}

+ 11 - 0
assets/ch/ch-sdk/提示说明.txt.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "1.0.1",
+  "importer": "text",
+  "imported": true,
+  "uuid": "e7e6477c-07a0-484e-82fa-0dbfb107ccff",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 31 - 0
assets/ch/ch.ts

@@ -0,0 +1,31 @@
+import ch_audio from "./audio/audio";
+import pvp from "./pvp/ch_pvp";
+import util from "./ch_util";
+import { ch_net, game_protocol } from "./net/net";
+import { NetPlayer } from "./net/NetPlayer";
+import { NetRoom } from "./net/NetRoom";
+export { NetPlayer, NetRoom };
+export type { game_protocol };
+export const ch = {
+    /**主sdk(需要初始化)*/
+    sdk: chsdk,
+    /**日志*/
+    log: chsdk.log,
+    /**本地缓存*/
+    storage: chsdk.storage,
+    /**日期*/
+    date: chsdk.date,
+    /**创建一个模块事件*/
+    get_new_event<CT>() { return chsdk.get_new_event<CT>() },
+    //---------------------------------------------
+    /**交互*/
+    pvp: pvp,
+    /**创建一个新的网络连接管理*/
+    get_new_net<gd extends game_protocol>() { return new ch_net<gd>(); },
+    /**工具*/
+    util: util,
+    /**音频播放模块(需要初始化)*/
+    get audio(): ch_audio { return ch_audio.getInstance(); },
+    wsurl: '',
+}
+

+ 9 - 0
assets/ch/ch.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "6a89b820-e575-4417-a7aa-fa7735d59d35",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/ch/ch_module.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "8e438c8a-f090-4bf1-8535-6a028648fa32",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/ch/ch_module/Config.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "fcf37ba6-3dc4-4ea9-9706-25e125c705c5",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 146 - 0
assets/ch/ch_module/Config/PassCfg.ts

@@ -0,0 +1,146 @@
+
+export class PassCfg {
+
+    public static maxKeyCount = 8;
+    private static intervalTime = 10;
+    public static ItemPath = "Pass/Res/Items/"
+    public static toastPath = "Pass/Prefab/toast"
+    public static passItems = {
+        201: {
+            id: 201,
+            reward: [{
+                itemId: 0,
+                count: 100,
+                type: 0
+            },
+            {
+                itemId: 0,
+                count: 200,
+                type: 1
+            }]
+        },
+        202: {
+            id: 202,
+            reward: [{
+                itemId: 0,
+                count: 100,
+                type: 0
+            },
+            {
+                itemId: 100,
+                count: 5,
+                type: 1
+            }]
+        },
+        203: {
+            id: 203,
+            reward: [{
+                itemId: 1000,
+                count: 10,
+                type: 0
+            },
+            {
+                itemId: 3001,
+                count: 2,
+                type: 1
+            }]
+        },
+        204: {
+            id: 204,
+            reward: [{
+                itemId: 0,
+                count: 100,
+                type: 0
+            },
+            {
+                itemId: 3002,
+                count: 2,
+                type: 1
+            }]
+        },
+        205: {
+            id: 205,
+            reward: [{
+                itemId: 100,
+                count: 5,
+                type: 0
+            },
+            {
+                itemId: 3003,
+                count: 1,
+                type: 1
+            }]
+        },
+        206: {
+            id: 206,
+            reward: [{
+                itemId: 0,
+                count: 200,
+                type: 0
+            },
+            {
+                itemId: 3001,
+                count: 5,
+                type: 1
+            }]
+        },
+        207: {
+            id: 207,
+            reward: [{
+                itemId: 0,
+                count: 200,
+                type: 0
+            },
+            {
+                itemId: 1000,
+                count: 20,
+                type: 1
+            }]
+        },
+        208: {
+            id: 208,
+            reward: [{
+                itemId: 0,
+                count: 500,
+                type: 0
+            },
+            {
+                itemId: 3002,
+                count: 3,
+                type: 1
+            }]
+        },
+        209: {
+            id: 209,
+            reward: [{
+                itemId: 1000,
+                count: 30,
+                type: 0
+            },
+            {
+                itemId: 3003,
+                count: 3,
+                type: 1
+            }]
+        }
+
+    }
+}
+
+export class PassItemCfg {
+    id: number;
+    reward: PassRewardCfg[];
+}
+
+export class PassRewardCfg {
+    itemId: number;
+    count: number;
+    type: number;
+}
+
+export enum PassType {
+    Normal = 0, // 普通
+    video = 1, // 视频
+}
+
+

+ 9 - 0
assets/ch/ch_module/Config/PassCfg.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "a29c3905-125e-44e0-aeb7-83db928f260c",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 212 - 0
assets/ch/ch_module/Config/TaskCfg.ts

@@ -0,0 +1,212 @@
+
+export class TaskCfg {
+    public static Taskitems = {
+        "501": {
+            "id": 501,
+            "name": "道具大礼包",
+            "desc": "",
+            "res": "",
+            "items": [
+                {
+                    "itemId": 3002,
+                    "num": 1
+                },
+                {
+                    "itemId": 3001,
+                    "num": 1
+                },
+                {
+                    "itemId": 1000,
+                    "num": 10
+                },
+                {
+                    "itemId": 100,
+                    "num": 5
+                }
+            ],
+            "value": 3,
+            "type": 0
+        },
+        "502": {
+            "id": 502,
+            "name": "完成1关主线关卡",
+            "desc": "",
+            "res": "",
+            "items": [
+                {
+                    "itemId": 1000,
+                    "num": 10
+                },
+                {
+                    "itemId": 0,
+                    "num": 20
+                },
+                {
+                    "itemId": 100,
+                    "num": 5
+                }
+            ],
+            "value": 1,
+            "type": 1
+        },
+        "503": {
+            "id": 503,
+            "name": "完成15关主线关卡",
+            "desc": "",
+            "res": "",
+            "items": [
+                {
+                    "itemId": 3003,
+                    "num": 1
+                }, {
+                    "itemId": 1000,
+                    "num": 20
+                },
+                {
+                    "itemId": 0,
+                    "num": 100
+                },
+                {
+                    "itemId": 100,
+                    "num": 5
+                }
+            ],
+            "value": 15,
+            "type": 1
+        },
+        "504": {
+            "id": 504,
+            "name": "完成30关主线关卡",
+            "desc": "",
+            "res": "",
+            "items": [
+                {
+                    "itemId": 3001,
+                    "num": 1
+                }, {
+                    "itemId": 3002,
+                    "num": 1
+                },
+                {
+                    "itemId": 1000,
+                    "num": 30
+                },
+                {
+                    "itemId": 0,
+                    "num": 300
+                }
+            ],
+            "value": 30,
+            "type": 1
+        },
+        "505": {
+            "id": 505,
+            "name": "观看10次视频",
+            "desc": "",
+            "res": "",
+            "items": [
+                {
+                    "itemId": 3001,
+                    "num": 1
+                },
+                {
+                    "itemId": 3002,
+                    "num": 1
+                },
+                {
+                    "itemId": 1000,
+                    "num": 50
+                },
+                {
+                    "itemId": 0,
+                    "num": 200
+                },
+                {
+                    "itemId": 100,
+                    "num": 10
+                }
+            ],
+            "value": 10,
+            "type": 3
+        },
+        "506": {
+            "id": 506,
+            "name": "每日拼图",
+            "desc": "",
+            "res": "",
+            "items": [
+                {
+                    "itemId": 1000,
+                    "num": 10
+                },
+                {
+                    "itemId": 0,
+                    "num": 20
+                },
+                {
+                    "itemId": 100,
+                    "num": 5
+                }
+            ],
+            "value": 1,
+            "type": 4
+        },
+        "507": {
+            "id": 507,
+            "name": "分享两次游戏",
+            "desc": "",
+            "res": "",
+            "items": [
+                {
+                    "itemId": 1000,
+                    "num": 20
+                },
+                {
+                    "itemId": 0,
+                    "num": 100
+                }
+            ],
+            "value": 2,
+            "type": 2
+        }
+    }
+    public static refreshTime = 15 //刷新间隔
+
+    //弹窗路径
+    public static PopupUi = {
+
+    }
+
+    //道具资源路径
+    public static itemPath = "Task/Res/Items/"
+    public static iconPath = "Task/Res/"
+
+    public static toastPath = "Task/Prefab/Common/toast"
+}
+
+export class TaskConfig {
+    id: number;
+    name: string;
+    desc: string;
+    res: string;
+    items: Array<{ itemId: number, num: number }>;
+    type: number; //TaskType任务类型
+    value: number;
+}
+
+export enum TaskStatus {
+    UNFINISHED = 0, //未领取
+    RECEIVED = 1 //已领取
+}
+
+
+/**任务类型 */
+export enum TaskType {
+    ITEM, //看视频直接领取
+    LEVEL,//完成关卡直接领取
+    SHARE,// 分享直接领取
+    VIDEO, //看视频领取奖励
+    JIGSAW, // 完成拼图
+    FIRST, //首次通关任务
+}
+

+ 9 - 0
assets/ch/ch_module/Config/TaskCfg.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "1730e4ba-bf3b-41b4-aea4-ba40959436cc",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/ch/ch_module/Mgr.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "24709e72-58c4-470f-9f0c-854eb50eaab1",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 89 - 0
assets/ch/ch_module/Mgr/BundleMgr.ts

@@ -0,0 +1,89 @@
+import { assetManager, AssetManager } from "cc";
+
+/**
+ * 分包管理器
+ * 提供分包加载、获取、移除功能。
+ */
+class BundleMgr {
+    /** 私有构造函数*/
+    private constructor() { }
+
+    /** 单例实例 */
+    public static readonly instance: BundleMgr = new BundleMgr();
+
+    /**
+     * 获取指定分包,如果未加载则进行加载。
+     * @param nameOrUrl - 分包名称或URL。
+     * @param onProgress - 进度回调函数。
+     * @returns Promise<AssetManager.Bundle | null> - 加载完成后的Promise。
+     */
+    public async getBundle(nameOrUrl: string, onProgress?: (progress: number) => void): Promise<AssetManager.Bundle | null> {
+        const bundle = assetManager.getBundle(nameOrUrl);
+        if (bundle) return bundle;
+
+        try {
+            const loadedBundle = await this.loadBundle(nameOrUrl);
+            if (onProgress) {
+                await this.loadAssetsWithProgress(loadedBundle, onProgress);
+            }
+            return loadedBundle;
+        } catch (error) {
+            console.error(`分包 ${nameOrUrl} 加载失败`, error.message);
+            return null;
+        }
+    }
+
+    /**
+     * 加载指定分包。
+     * @param nameOrUrl - 分包名称或URL。
+     * @returns Promise<AssetManager.Bundle> - 加载完成后的Promise。
+     */
+    private loadBundle(nameOrUrl: string): Promise<AssetManager.Bundle> {
+        return new Promise((resolve, reject) => {
+            assetManager.loadBundle(nameOrUrl, (err, loadedBundle) => {
+                if (err) {
+                    reject(err);
+                } else {
+                    resolve(loadedBundle);
+                }
+            });
+        });
+    }
+
+    /**
+     * 加载分包中的资源并提供进度反馈。
+     * @param bundle - 已加载的分包。
+     * @param onProgress - 进度回调函数。
+     * @returns Promise<void> - 加载完成后的Promise。
+     */
+    private loadAssetsWithProgress(bundle: AssetManager.Bundle, onProgress: (progress: number) => void): Promise<void> {
+        return new Promise((resolve, reject) => {
+            const assets = bundle.getDirWithPath('');
+            const totalAssets = assets.length;
+            let loadedAssets = 0;
+
+            if (totalAssets === 0) {
+                onProgress(1);
+                resolve();
+                return;
+            }
+
+            assets.forEach((asset) => {
+                bundle.load(asset.path, (err) => {
+                    if (err) {
+                        reject(err);
+                        return;
+                    }
+                    loadedAssets++;
+                    onProgress(loadedAssets / totalAssets);
+                    if (loadedAssets === totalAssets) {
+                        resolve();
+                    }
+                });
+            });
+        });
+    }
+}
+
+/** 分包管理器实例 */
+export const bundleMgr = BundleMgr.instance;

+ 1 - 0
assets/ch/ch_module/Mgr/BundleMgr.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"e9e0d8a3-ef3c-492e-b917-1f60ee5d7514","files":[],"subMetas":{},"userData":{}}

+ 171 - 0
assets/ch/ch_module/Mgr/ResMgr.ts

@@ -0,0 +1,171 @@
+import { Asset, AssetManager, assetManager } from "cc";
+import { bundleMgr } from "./BundleMgr";
+
+
+/** 
+ * 资源管理器
+ * 提供资源加载、释放功能。
+ */
+class ResMgr {
+    /** 私有构造函数 */
+    private constructor() { }
+
+    /** 单例实例 */
+    public static readonly instance: ResMgr = new ResMgr();
+
+    /**
+     * 加载资源
+     * @param resPath 资源路径
+     * @param onProgress 进度回调函数
+     * @param onComplete 完成回调函数
+     * @returns Promise<T> 加载完成后的Promise
+     */
+    public async loadRes<T extends Asset>(
+        resPath: string,
+        onProgress?: (completedCount: number, totalCount: number, item: any) => void,
+        onComplete?: (err: Error | null, asset: T) => void
+    ): Promise<T> {
+        try {
+            const { bundleName, path } = this.path(resPath);
+            const bundle = await bundleMgr.getBundle(bundleName);
+            return await this.loadAsset<T>(bundle, path, onProgress, onComplete);
+        } catch (error) {
+            console.error(`加载资源失败: ${resPath}`, error.message);
+            throw error;
+        }
+    }
+
+    /**
+     * 加载目录下的所有资源
+     * @param resPath 资源路径
+     * @param onProgress 进度回调函数
+     * @param onComplete 完成回调函数
+     * @returns Promise<Array<Asset>> 加载完成后的Promise
+     */
+    public async loadResDir(
+        resPath: string,
+        onProgress?: (completedCount: number, totalCount: number, item: any) => void,
+        onComplete?: (err: Error | null, assets: Array<Asset>) => void
+    ): Promise<Array<Asset>> {
+        try {
+            const { bundleName, path } = this.path(resPath);
+            const bundle = await bundleMgr.getBundle(bundleName);
+            return await this.loadAssetDir(bundle, path, onProgress, onComplete);
+        } catch (error) {
+            console.error(`加载目录失败: ${resPath}`, error.message);
+            throw error;
+        }
+    }
+
+    /**
+     * 释放指定分包单个资源
+     * @param resPath 资源路径
+     */
+    public releaseRes(resPath: string): void {
+        const { bundleName, path } = this.path(resPath);
+        const bundle = assetManager.getBundle(bundleName);
+        if (bundle) {
+            bundle.release(path);
+        } else {
+            console.error(`分包 ${bundleName} 未找到,无法释放资源 ${path}。`);
+        }
+    }
+
+    /**
+     * 释放指定分包全部资源
+     * @param bundleName 分包名称
+     */
+    public releaseBundle(bundleName: string): void {
+        const bundle = assetManager.getBundle(bundleName);
+        if (bundle) {
+            bundle.releaseAll();
+            assetManager.removeBundle(bundle);
+        } else {
+            console.error(`分包 ${bundleName} 未找到,无法移除。`);
+        }
+    }
+
+    /** 移除所有分包 */
+    public releaseAll(): void {
+        assetManager.releaseAll();
+    }
+
+    /**
+     * 加载单个资源的辅助方法
+     * @param bundle 资源所在的分包
+     * @param path 资源路径
+     * @param onProgress 进度回调函数
+     * @param onComplete 完成回调函数
+     * @returns Promise<T> 加载完成后的Promise
+     */
+    private loadAsset<T extends Asset>(
+        bundle: AssetManager.Bundle,
+        path: string,
+        onProgress?: (completedCount: number, totalCount: number, item: any) => void,
+        onComplete?: (err: Error | null, asset: T) => void
+    ): Promise<T> {
+        return new Promise<T>((resolve, reject) => {
+            bundle.load(
+                path,
+                (completedCount, totalCount, item) => onProgress?.(completedCount, totalCount, item),
+                (err, asset) => {
+                    onComplete?.(err, asset as T);
+                    if (err) {
+                        console.error(`从分包加载资源 ${path} 失败`, err.message);
+                        reject(err);
+                    } else {
+                        resolve(asset as T);
+                    }
+                }
+            );
+        });
+    }
+
+    /**
+     * 加载目录下所有资源的辅助方法
+     * @param bundle 资源所在的分包
+     * @param path 目录路径
+     * @param onProgress 进度回调函数
+     * @param onComplete 完成回调函数
+     * @returns Promise<Array<Asset>> 加载完成后的Promise
+     */
+    private loadAssetDir(
+        bundle: AssetManager.Bundle,
+        path: string,
+        onProgress?: (completedCount: number, totalCount: number, item: any) => void,
+        onComplete?: (err: Error | null, assets: Array<Asset>) => void
+    ): Promise<Array<Asset>> {
+        return new Promise<Array<Asset>>((resolve, reject) => {
+            bundle.loadDir(
+                path,
+                (completedCount, totalCount, item) => onProgress?.(completedCount, totalCount, item),
+                (err, assets) => {
+                    onComplete?.(err, assets);
+                    if (err) {
+                        console.error(`从分包加载目录 ${path} 失败`, err.message);
+                        reject(err);
+                    } else {
+                        resolve(assets);
+                    }
+                }
+            );
+        });
+    }
+
+    /**
+ * 解析资源路径并将其分解为包名和路径
+ *
+ * @param resPath - 要解析的资源路径
+ * @returns 返回包含 bundleName 和 path 的对象
+ */
+    private path(resPath: string): { bundleName: string; path: string } {
+        const [bundleName, ...pathParts] = resPath.split('/');
+        return {
+            bundleName,
+            path: pathParts.join('/')
+        };
+    }
+}
+
+/** 资源管理器实例 */
+export const resMgr = ResMgr.instance;

+ 1 - 0
assets/ch/ch_module/Mgr/ResMgr.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"3ba040f7-b937-46a0-8061-14e03a47d940","files":[],"subMetas":{},"userData":{}}

+ 9 - 0
assets/ch/ch_module/Module.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "d38e9a66-0829-405b-8f94-ce4d64cbc8e0",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 212 - 0
assets/ch/ch_module/Module/Ch_jigsaw.ts

@@ -0,0 +1,212 @@
+import { AssetManager, director, instantiate, isValid, Node, Prefab } from "cc";
+import { bundleMgr } from "../Mgr/BundleMgr";
+import { resMgr } from "../Mgr/ResMgr";
+
+export interface evt {
+    onComClick: () => void;
+    onCloseGame: () => void;
+    onGetReward: (itemId: number, count: number) => void;
+    onViedoReward: (success: boolean) => void;
+    onSaveGameData: (save_data: JigsawObj) => void;
+}
+
+export interface JigsawObj {
+    /** 放置拼图数据 */
+    jigsawData: { [key: number]: Array<[number, number]> };
+    /** 拥有的碎片数据 */
+    cardData: { [key: number]: Array<number> };
+    /** 拥有道具数据 */
+    itemsData: { [key: number]: number };
+    /** 完成领取拼图奖励数据 */
+    rewardImgData: Array<number>;
+    /** 解锁索引位置点 */
+    unlockIndex: number;
+    /** 添加的碎片数量 */
+    addCardCount: number;
+}
+
+/** 主窗口 */
+const mainPopup = "Jigsaw/Prefab/Popup/jigsawListPopup";
+/** 包名 */
+const bundleName = "Jigsaw";
+
+/** 定义一个类 */
+class Ch_jigsaw {
+    /** 私有构造函数 */
+    private constructor() { }
+    /** 单例模式 */
+    public static readonly instance: Ch_jigsaw = new Ch_jigsaw();
+    private _firstParent: Node | null = null;
+    private _secondParent: Node | null = null;
+    private _data: JigsawObj = {
+        /** 放置拼图数据 */
+        jigsawData: {},
+        /** 拥有的碎片数据 */
+        cardData: {},
+        /** 拥有道具数据 */
+        itemsData: {},
+        /** 完成领取拼图奖励数据 */
+        rewardImgData: [],
+        /** 解锁索引位置点 */
+        unlockIndex: 0,
+        /** 添加的碎片数量 */
+        addCardCount: 0
+    };
+    private _evt = chsdk.get_new_event<evt>();
+    get evt() {
+        return this._evt;
+    }
+    /**
+     *  初始化数据
+     * @param data 
+     */
+    init(data: JigsawObj) {
+        // 初始化
+        this._data = data ? data : this._data;
+    }
+
+
+    /** 公共点击事件 */
+    onComClick() {
+        this.evt.emit(this.evt.key.onComClick);
+    }
+
+    /**
+     * 获得奖励
+     * @param itemId 
+     */
+    onGetReward(itemId: number, count: number) {
+        // console.log("获得奖励", itemId);
+        this.evt.emit(this.evt.key.onGetReward, itemId, count);
+    }
+
+    /**
+     * 看视频广告
+     */
+    onViedoReward(success: boolean) {
+        this.evt.emit(this.evt.key.onViedoReward, success);
+    }
+
+    /**
+     * 保存游戏数据
+     * @param save_data 
+     */
+    onSaveGameData(save_data: JigsawObj) {
+        this._data = save_data;
+        this.evt.emit(this.evt.key.onSaveGameData, save_data);
+    }
+
+    /**
+     * 添加碎片
+     * @param count 
+     */
+    onAddCard(count: number) {
+        this._data.addCardCount += count;
+        this.evt.emit(this.evt.key.onSaveGameData, this._data);
+    }
+
+    /**
+     * 获取未使用的碎片数量
+     */
+    getNoUseCardCount() {
+        let count = this._data.addCardCount;
+        let cardData = this._data.cardData;
+        let placeData = this._data.jigsawData;
+        for (let key in placeData) {
+            if (placeData.hasOwnProperty(key)) {
+                let arr = placeData[key];
+                for (let i = 0; i < arr.length; i++) {
+                    let [x, y] = arr[i];
+                    if (x != y) {
+                        count++;
+                    }
+                }
+            }
+        }
+        for (let key in cardData) {
+            if (cardData.hasOwnProperty(key)) {
+                let arr = cardData[key];
+                count += arr.length;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * 获取游戏数据
+     */
+    getGameData() {
+        return this._data;
+    }
+
+
+    /**
+     *  获取父节点
+     * @returns 
+     */
+    getInterParent() {
+        return this._secondParent;
+    }
+
+    /**
+     * 加载分包
+     * @param preogerss 
+     * @param onComplete 
+     */
+    loadBundle(preogerss?: (progress: number) => void, onComplete?: (err: Error | null, bundle: AssetManager.Bundle | null) => void): void {
+        const onProgress = (progress: number) => {
+            chsdk.log.debug(`加载进度:${progress}%`);
+            if (preogerss) {
+                preogerss(progress);
+            }
+        };
+        bundleMgr.getBundle(bundleName, onProgress)
+            .then((bundle) => {
+                if (bundle) {
+                    chsdk.log.debug('分包加载成功!');
+                    // 在这里可以执行加载完成后的操作
+                    if (onComplete) {
+                        onComplete(null, bundle);
+                    }
+                } else {
+                    chsdk.log.debug('分包加载失败!');
+                    // 这里可以执行失败后的操作
+                    setTimeout(() => {
+                        // 这里可以尝试重新加载分包
+                        this.loadBundle(preogerss, onComplete);
+                    }, 1000);
+                }
+            })
+    }
+
+    /**
+     * 显示主窗口
+     * @param parent 父节点
+     * @param interParent 界面父节点
+     */
+    show(parent?: Node, interParent?: Node) {
+        this._firstParent = parent ? parent : null;
+        this._secondParent = interParent ? interParent : null;
+        resMgr.loadRes<Prefab>(mainPopup).then((prefab: Prefab) => {
+            let node = instantiate(prefab);
+            if (parent) {
+                if (isValid(parent)) {
+                    node.parent = parent;
+                } else {
+                    node.destroy();
+                }
+            }
+            else {
+                let canvas = director.getScene().getChildByName("Canvas");
+                if (canvas) {
+                    node.parent = canvas;
+                }
+            }
+        });
+    }
+}
+
+/** 导出单例模式的实例 */
+export const ch_jigsaw: Ch_jigsaw = Ch_jigsaw.instance;
+
+

+ 9 - 0
assets/ch/ch_module/Module/Ch_jigsaw.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "19696f4c-96c5-43b7-8958-37755f0d0500",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 268 - 0
assets/ch/ch_module/Module/Ch_pass.ts

@@ -0,0 +1,268 @@
+import { AssetManager, director, instantiate, Node, Prefab } from "cc";
+import { bundleMgr } from "../Mgr/BundleMgr";
+import { resMgr } from "../Mgr/ResMgr";
+import { PassCfg } from "../Config/PassCfg";
+import { ch } from "../../ch";
+
+export interface evt {
+    onComClick: () => void;
+    onGetReward: (itemId: number, count: number) => void;
+    onViedoReward: (success: boolean) => void;
+    onSaveGameData: (save_data: PassObj) => void;
+}
+
+export interface PassObj {
+    rewardId: Array<{ id: number, indexArr: Array<number> }>; //领取的奖励数据
+    keyCount: number; // 钥匙数量
+    unlockLevel: number; //解锁等级
+    curTimetatmp: number; //当前时间戳
+}
+
+/** 主窗口 */
+const mainPopup = "Pass/passPopup";
+/** 包名 */
+const bundleName = "Pass";
+
+/** 定义一个类 */
+class Ch_pass {
+    /** 私有构造函数 */
+    private constructor() { }
+    /** 单例模式 */
+    public static readonly instance: Ch_pass = new Ch_pass();
+    private _firstParent: Node | null = null;
+    private _secondParent: Node | null = null;
+
+    private _data: PassObj = {
+        rewardId: [],
+        keyCount: 0,
+        unlockLevel: 0,
+        curTimetatmp: 0
+    };
+    private _evt = chsdk.get_new_event<evt>();
+    get evt() {
+        return this._evt;
+    }
+
+    /**
+     *  初始化数据
+     * @param data 
+     */
+    init(data: any) {
+        // 初始化
+        this._data = data ? data : this._data;
+        //是否初始化数据
+        let diffTime = this.getDiffTime();
+        if (diffTime <= 0) {
+            this.clear();
+            this.onSaveGameData();
+        }
+    }
+
+    /** 公共点击事件 */
+    onComClick() {
+        this.evt.emit(this.evt.key.onComClick);
+    }
+
+    /**
+     * 获得奖励
+     * @param itemId 
+     */
+    onGetReward(itemId: number, count: number) {
+        // console.log("获得奖励", itemId);
+        this.evt.emit(this.evt.key.onGetReward, itemId, count);
+        this.onSaveGameData();
+    }
+
+    /**
+     * 看视频广告
+     */
+    onViedoReward(success: boolean) {
+        this.evt.emit(this.evt.key.onViedoReward, success);
+    }
+
+    /**
+     * 保存游戏数据
+     * @param save_data 
+     */
+    onSaveGameData() {
+        this.evt.emit(this.evt.key.onSaveGameData, this._data);
+    }
+
+    /**
+     *  获取内部父节点
+     * @returns 
+     */
+    getInterParent() {
+        return this._secondParent;
+    }
+
+    /**
+     * 获取游戏数据
+     */
+    getGameData() {
+        return this._data;
+    }
+
+    /**
+     * 添加通行证钥匙
+     */
+    addPassKey(count: number = 1) {
+        if (this._data.unlockLevel < this.getItemLength()) {
+            this._data.keyCount += count;
+            // 更新解锁等级
+            while (this._data.keyCount >= PassCfg.maxKeyCount) {
+                this._data.keyCount -= PassCfg.maxKeyCount;
+                this._data.unlockLevel++;
+            }
+            this.onSaveGameData();
+        }
+    }
+
+    /**
+     * 检测是否有未领取的奖励
+     */
+    checkHasReward() {
+        let pidArr = this.getUnlockPidArr();
+        let hasReward = false;
+        for (let i = this._data.rewardId.length - 1; i >= 0; i--) {
+            let reward = this._data.rewardId[i];
+            if (pidArr.indexOf(reward.id) != -1) {
+                if (reward.indexArr.length == PassCfg.passItems[reward.id].reward.length) {
+                    pidArr.splice(pidArr.indexOf(reward.id), 1);
+                }
+            }
+        }
+        hasReward = pidArr.length > 0;
+        return hasReward;
+    }
+    /**
+    * 获取预领取奖励
+    */
+    getPreRewaedIs() {
+        let pidArr = this.getUnlockPidArr();
+        for (let i = this._data.rewardId.length - 1; i >= 0; i--) {
+            let reward = this._data.rewardId[i];
+            if (pidArr.indexOf(reward.id) != -1) {
+                if (reward.indexArr.indexOf(1) != -1) {
+                    pidArr.splice(pidArr.indexOf(reward.id), 1);
+                }
+            }
+        }
+        return pidArr.length;
+    }
+    /**
+     * 获取解锁的pidArr
+     */
+    private getUnlockPidArr() {
+        let pidArr = [];
+        let items = PassCfg.passItems;
+        let keys = Object.keys(items);
+        for (let i = 0; i < keys.length; i++) {
+            let key = keys[i];
+            let item = items[key];
+            if (i < this._data.unlockLevel) {
+                pidArr.push(item.id);
+            }
+        }
+
+        return pidArr;
+    }
+
+    /**
+    * 获取奖励等级
+     */
+    private getItemLength() {
+        let level = 0;
+        let items = PassCfg.passItems;
+        for (let key in items) {
+            level++;
+        }
+        return level;
+    }
+
+    /**
+     *  获取当前时间差
+     * @returns 当前时间差
+     */
+    private getDiffTime(): number {
+        const now = ch.date.getTime();
+        if (!ch.date.isSameDate(now, this._data.curTimetatmp)) {
+            return 0;
+        }
+        let diffTime = ch.date.getDayEndTime(now) - now;
+        return diffTime;
+    }
+
+    /**
+     * 清空数据
+     */
+    clear() {
+        this._data.curTimetatmp = ch.date.getTime();
+        this._data.keyCount = 0;
+        this._data.unlockLevel = 0;
+        this._data.rewardId = [];
+    }
+
+    /**
+     * 加载分包
+     * @param preogerss 
+     * @param onComplete 
+     */
+    loadBundle(preogerss?: (progress: number) => void, onComplete?: (err: Error | null, bundle: AssetManager.Bundle | null) => void): void {
+        const onProgress = (progress: number) => {
+            chsdk.log.debug(`加载进度:${progress}%`);
+            if (preogerss) {
+                preogerss(progress);
+            }
+        };
+        bundleMgr.getBundle(bundleName, onProgress)
+            .then((bundle) => {
+                if (bundle) {
+                    chsdk.log.debug('分包加载成功!');
+                    // 在这里可以执行加载完成后的操作
+                    if (onComplete) {
+                        onComplete(null, bundle);
+                    }
+                } else {
+                    chsdk.log.debug('分包加载失败!');
+                    // 这里可以执行失败后的操作
+                    setTimeout(() => {
+                        // 这里可以尝试重新加载分包
+                        this.loadBundle(preogerss, onComplete);
+                    }, 1000);
+                }
+            })
+    }
+
+    /**
+     * 显示主窗口
+     * @param parent 父节点
+     * @param interParent 界面父节点
+     */
+    show(parent?: Node, interParent?: Node) {
+        this._firstParent = parent ? parent : null;
+        this._secondParent = interParent ? interParent : null;
+        resMgr.loadRes<Prefab>(mainPopup).then((prefab: Prefab) => {
+            let node = instantiate(prefab);
+            if (parent) {
+                node.parent = parent;
+            }
+            else {
+                let canvas = director.getScene().getChildByName("Canvas");
+                if (canvas) {
+                    node.parent = canvas;
+                }
+            }
+        });
+    }
+
+    hide() {
+        this._firstParent.removeAllChildren();
+        this._secondParent.removeAllChildren();
+    }
+}
+
+/** 导出单例模式的实例 */
+export const ch_pass: Ch_pass = Ch_pass.instance;
+
+

+ 1 - 0
assets/ch/ch_module/Module/Ch_pass.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"a20ed540-5de8-4280-b8c9-6d5ce813c47b","files":[],"subMetas":{},"userData":{}}

+ 263 - 0
assets/ch/ch_module/Module/Ch_task.ts

@@ -0,0 +1,263 @@
+import { AssetManager, director, instantiate, isValid, Node, Prefab } from "cc";
+import { bundleMgr } from "../Mgr/BundleMgr";
+import { resMgr } from "../Mgr/ResMgr";
+
+export interface evt {
+    onComClick: () => void;
+    onGetReward: (itemId: Array<{ itemId: number, num: number }>) => void;
+    onViedoReward: (success: boolean) => void;
+    onSaveGameData: (save_data: TaskObj) => void;
+    onJumpGameLevel: (callBack: Function) => void;
+    onShareGame: (callBack: Function) => void;
+    onUpdatePreTask: (count: number) => void;
+    onJump: (type: TaskType) => void;
+}
+/**任务类型 */
+export enum TaskType {
+    ITEM, //看视频直接领取
+    LEVEL,//完成关卡直接领取
+    SHARE,// 分享直接领取
+    VIDEO, //看视频领取奖励
+    JIGSAW, // 完成拼图
+}
+
+export class TaskObj {
+    taskIds: Array<number>; //完成任务领取id
+    preTaskIds: Array<number>; //完成任务未领取id
+    taskTypeData: { [key: number]: { taskType: number, count: number } }; //任务类型数据
+    curTimetatmp: number; //当前时间戳
+}
+
+/** 主窗口 */
+const mainPopup = "Task/Prefab/Popup/taskPopup";
+/** 包名 */
+const bundleName = "Task";
+
+/** 定义一个类 */
+class Ch_task {
+    /** 私有构造函数 */
+    private constructor() { }
+    /** 单例模式 */
+    public static readonly instance: Ch_task = new Ch_task();
+    private _firstParent: Node | null = null;
+    private _secondParent: Node | null = null;
+    private _taskCfg: { [key: number]: { id: number, type: number, value: number } } = null;
+    private _data: TaskObj = {
+        taskIds: [],
+        preTaskIds: [],
+        taskTypeData: {},
+        curTimetatmp: 0
+    };
+    private _evt = chsdk.get_new_event<evt>();
+    get evt() {
+        return this._evt;
+    }
+
+    /**
+     *  初始化数据
+     * @param data 
+     */
+    init(data: TaskObj) {
+        // 初始化
+        this._data = data ? data : this._data;
+
+    }
+
+    /**
+     * 初始配置
+     * @param data 
+     */
+    initTaskCfg(data: { [key: number]: { id: number, type: number, value: number } }) {
+        if (!this._taskCfg || JSON.stringify(this._taskCfg) != JSON.stringify(data)) {
+            this._taskCfg = data;
+        }
+    }
+
+    /** 公共点击事件 */
+    onComClick() {
+        this.evt.emit(this.evt.key.onComClick);
+    }
+
+    /**
+     * 获得奖励
+     * @param itemId 
+     */
+    onGetReward(items: Array<{ itemId: number, num: number }>) {
+        this.evt.emit(this.evt.key.onGetReward, items);
+    }
+
+    /**
+    * 看视频广告
+    */
+    onViedoReward(success: boolean) {
+        this.evt.emit(this.evt.key.onViedoReward, success);
+    }
+
+    /**
+     * 跳到游戏关卡
+     */
+    onJumpGameLevel() {
+        this.evt.emit(this.evt.key.onJumpGameLevel, (isFinish: boolean) => {
+            if (isFinish) {
+                //更新任务数据
+                this.updateTaskTypeData(TaskType.LEVEL);
+                this.onSaveGameData();
+                this.onUpdatePreTask();
+            }
+        });
+    }
+    onJump(type: TaskType) {
+        this.evt.emit(this.evt.key.onJump, type);
+    }
+    /**
+     * 分享游戏
+     */
+    onShareGame(cb?: Function) {
+        this.evt.emit(this.evt.key.onShareGame, (isSuccess: boolean) => {
+            if (isSuccess) {
+                //更新任务数据
+                if (cb) {
+                    cb();
+                    //this.updateTaskTypeData(TaskType.SHARE, 1);
+                    //this.onSaveGameData();
+                    //this.onUpdatePreTask();
+                }
+            }
+        });
+    }
+
+    /**
+    *  更新任务类型数据
+    * @param type 任务类型
+    * @param count 
+    */
+    updateTaskTypeData(type: number, count: number = 1) {
+        let taskTypeData = this._data.taskTypeData[type];
+        if (taskTypeData) {
+            taskTypeData.count += count;
+        } else {
+            this._data.taskTypeData[type] = { taskType: type, count: count };
+        }
+        //更新待领取任务
+        this.updatePreTaksIds(this._data.taskTypeData[type].taskType, this._data.taskTypeData[type].count);
+    }
+
+
+    /**
+       * 更新待领取的奖励id
+       * @param itemId 
+       */
+    private updatePreTaksIds(type: number, count: number) {
+        for (let key in this._taskCfg) {
+            let item = this._taskCfg[key];
+            if (item.type == type && item.value <= count) {
+                if (this._data.preTaskIds.indexOf(item.id) == -1) {
+                    this._data.preTaskIds.push(item.id);
+                }
+            }
+        }
+    }
+
+    /**
+     * 更新待领取的任务id
+     */
+    onUpdatePreTask() {
+        this.evt.emit(this.evt.key.onUpdatePreTask, this._data.preTaskIds.length);
+    }
+    /**
+     * 保存游戏数据
+     */
+    onSaveGameData() {
+        this.evt.emit(this.evt.key.onSaveGameData, this._data);
+    }
+
+    /**
+     *  获取内部父节点
+     * @returns 
+     */
+    getInterParent() {
+        return this._secondParent;
+    }
+
+    /**
+     * 获取游戏数据
+     */
+    getGameData() {
+        return this._data;
+    }
+
+    /**
+     * 清空数据
+     */
+    protected clear() {
+        this._data = {
+            taskIds: [],
+            preTaskIds: [],
+            taskTypeData: {},
+            curTimetatmp: 0
+        };
+    }
+
+
+    /**
+     * 加载分包
+     * @param preogerss 
+     * @param onComplete 
+     */
+    loadBundle(preogerss?: (progress: number) => void, onComplete?: (err: Error | null, bundle: AssetManager.Bundle | null) => void): void {
+        const onProgress = (progress: number) => {
+            chsdk.log.debug(`加载进度:${progress}%`);
+            if (preogerss) {
+                preogerss(progress);
+            }
+        };
+        bundleMgr.getBundle(bundleName, onProgress)
+            .then((bundle) => {
+                if (bundle) {
+                    chsdk.log.debug('分包加载成功!');
+                    // 在这里可以执行加载完成后的操作
+                    if (onComplete) {
+                        onComplete(null, bundle);
+                    }
+                } else {
+                    chsdk.log.debug('分包加载失败!');
+                    // 这里可以执行失败后的操作
+                    setTimeout(() => {
+                        // 这里可以尝试重新加载分包
+                        this.loadBundle(preogerss, onComplete);
+                    }, 1000);
+                }
+            })
+    }
+
+    /**
+     * 显示主窗口
+     * @param parent 父节点
+     * @param interParent 界面父节点
+     */
+    show(parent?: Node, interParent?: Node) {
+        this._firstParent = parent ? parent : null;
+        this._secondParent = interParent ? interParent : null;
+        resMgr.loadRes<Prefab>(mainPopup).then((prefab: Prefab) => {
+            let node = instantiate(prefab);
+            if (parent) {
+                if (isValid(parent)) {
+                    node.parent = parent;
+                } else {
+                    node.destroy();
+                }
+            }
+            else {
+                let canvas = director.getScene().getChildByName("Canvas");
+                if (canvas) {
+                    node.parent = canvas;
+                }
+            }
+        });
+    }
+}
+
+/** 导出单例模式的实例 */
+export const ch_task: Ch_task = Ch_task.instance;
+
+

+ 1 - 0
assets/ch/ch_module/Module/Ch_task.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"1cc5b2de-6999-40ee-892d-bd4ff6206354","files":[],"subMetas":{},"userData":{}}

+ 160 - 0
assets/ch/ch_module/Module/Ch_turn.ts

@@ -0,0 +1,160 @@
+import { AssetManager, director, instantiate, isValid, Node, Prefab } from "cc";
+import { bundleMgr } from "../Mgr/BundleMgr";
+import { resMgr } from "../Mgr/ResMgr";
+
+export interface evt {
+    onComClick: () => void;
+    onGetReward: (itemId: number, count: number) => void;
+    onViedoReward: (success: boolean) => void;
+    onSaveGameData: (save_data: number) => void;
+}
+
+export interface TurnObj {
+    itemArr: Array<{ itemId: number, count: number, weigth: number }>;
+    total: number;
+}
+
+/** 主窗口 */
+const mainPopup = "Turn/Prefab/Popup/turnPopup";
+/** 包名 */
+const bundleName = "Turn";
+
+/** 定义一个类 */
+class Ch_turn {
+    /** 私有构造函数 */
+    private constructor() { }
+    /** 单例模式 */
+    public static readonly instance: Ch_turn = new Ch_turn();
+    private _firstParent: Node | null = null;
+    private _secondParent: Node | null = null;
+    private _itemArr: Array<{ itemId: number, count: number, weigth: number }> = [];
+    private _data: TurnObj = { itemArr: this._itemArr, total: 0 };
+    private _evt = chsdk.get_new_event<evt>();
+    get evt() {
+        return this._evt;
+    }
+
+    /**
+     *  初始化数据
+     * @param data 
+     */
+    init(total: number = 0) {
+        // 初始化
+        this._data.total = total ? total : 0;
+    }
+
+
+    /** 公共点击事件 */
+    onComClick() {
+        this.evt.emit(this.evt.key.onComClick);
+    }
+
+    /**
+     * 获得奖励
+     * @param itemId 
+     */
+    onGetReward(itemId: number, count: number) {
+        // console.log("获得奖励", itemId);
+        this.evt.emit(this.evt.key.onGetReward, itemId, count);
+    }
+
+    /**
+     * 看视频广告
+     */
+    onViedoReward(success: boolean) {
+        this.evt.emit(this.evt.key.onViedoReward, success);
+    }
+
+    /**
+     * 保存游戏数据
+     * @param save_data 
+     */
+    onSaveGameData(total: number) {
+        this._data.total = total;
+        this.evt.emit(this.evt.key.onSaveGameData, total);
+    }
+
+    /**
+     *  获取内部父节点
+     * @returns 
+     */
+    getInterParent() {
+        return this._secondParent;
+    }
+
+    /**
+     * 获取游戏数据
+     */
+    getGameData() {
+        return this._data;
+    }
+
+    /**
+     * 清空数据
+     */
+    protected clear() {
+        this._data.total = 0;
+    }
+
+    /**
+     * 加载分包
+     * @param preogerss 
+     * @param onComplete 
+     */
+    loadBundle(preogerss?: (progress: number) => void, onComplete?: (err: Error | null, bundle: AssetManager.Bundle | null) => void): void {
+        const onProgress = (progress: number) => {
+            chsdk.log.debug(`加载进度:${progress}%`);
+            if (preogerss) {
+                preogerss(progress);
+            }
+        };
+        bundleMgr.getBundle(bundleName, onProgress)
+            .then((bundle) => {
+                if (bundle) {
+                    chsdk.log.debug('分包加载成功!');
+                    // 在这里可以执行加载完成后的操作
+                    if (onComplete) {
+                        onComplete(null, bundle);
+                    }
+                } else {
+                    chsdk.log.debug('分包加载失败!');
+                    // 这里可以执行失败后的操作
+                    setTimeout(() => {
+                        // 这里可以尝试重新加载分包
+                        this.loadBundle(preogerss, onComplete);
+                    }, 1000);
+                }
+            })
+    }
+
+    /**
+     * 显示主窗口
+     * @param parent 父节点
+     * @param interParent 界面父节点
+     */
+    show(parent?: Node, interParent?: Node) {
+        this._firstParent = parent ? parent : null;
+        this._secondParent = interParent ? interParent : null;
+        resMgr.loadRes<Prefab>(mainPopup).then((prefab: Prefab) => {
+            let node = instantiate(prefab);
+            if (parent) {
+                if (isValid(parent)) {
+                    node.parent = parent;
+                } else {
+                    node.destroy();
+                }
+            }
+            else {
+                let canvas = director.getScene().getChildByName("Canvas");
+                if (canvas) {
+                    node.parent = canvas;
+                }
+            }
+        });
+    }
+}
+
+/** 导出单例模式的实例 */
+export const ch_turn: Ch_turn = Ch_turn.instance;
+
+

+ 1 - 0
assets/ch/ch_module/Module/Ch_turn.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"c76e069a-80ca-4f9e-8e66-1ebe3e9ef0eb","files":[],"subMetas":{},"userData":{}}

+ 351 - 0
assets/ch/ch_util.ts

@@ -0,0 +1,351 @@
+import { size, view, Node, UITransform, screen, ImageAsset, SpriteFrame, Texture2D, sys, assetManager } from "cc";
+/**工具*/
+class ch_util {
+    private static _instance: ch_util;
+    public static getInstance(): ch_util {
+        if (!this._instance) this._instance = new ch_util();
+        return this._instance;
+    }
+
+    /**
+    * 随机数 (包含min,不包含max)
+    * @param min
+    * @param max
+    * @param isInt
+    * @return {*}
+    */
+    public getRandom(min: number = 0, max: number = 1): number {
+        if (min == null) min = 0;
+        if (max == null) max = 1;
+        if (min === max) return min;
+        return min + (Math.random() * (max - min));
+    }
+    /**
+    * 随机整数-不包含最大值
+    * @param min
+    * @param max
+    * @return {*}
+    */
+    public getRandomInt(min: number, max: number): number {
+        min = Math.ceil(min); max = Math.ceil(max);
+        return Math.floor(Math.random() * (max - min)) + min;
+    }
+    /** 生成随机整数 -1 或 1*/
+    public getRandomDir(): -1 | 1 {
+        return Math.floor(Math.random() * 2) === 0 ? -1 : 1;
+    }
+    /**
+    * 在指定数组中随机取出N个不重复的数据
+    * @param resArr
+    * @param ranNum
+    * @returns {Array}
+    */
+    public getRandomDiffValueFromArr<T>(resArr: Array<T>, ranNum: number): Array<T> {
+        let arr = new Array<T>();
+        let result = new Array<T>();
+        if (!resArr || resArr.length <= 0 || ranNum <= 0) {
+            return result;
+        }
+        for (let i = 0; i < resArr.length; i++) {
+            arr.push(resArr[i]);
+        }
+        if (ranNum >= arr.length) return arr;
+        ranNum = Math.min(ranNum, arr.length - 1);
+        for (let i = 0; i < ranNum; i++) {
+            let ran = this.getRandomInt(0, arr.length - 1);
+            result.push(arr.splice(ran, 1)[0]);
+        }
+        return result;
+    }
+    /**
+     * 取小数位
+     * @param decimal 小数
+     * @param places 位数
+     * @return {number}
+     */
+    public numberToDecimal(decimal: number, places: number): number {
+        let round: number = Math.pow(10, places);
+        return Math.round(decimal * round) / round;
+    }
+
+    public parse(text: string, reciver?: (key: any, value: any) => any): any {
+        try {
+            return JSON.parse(text, reciver);
+        } catch (error) {
+            //ch_log.error(error);
+            return null;
+        }
+    }
+
+    public stringify(value: any, replacer?: (key: string, value: any) => any, space?: string | number) {
+        try {
+            return JSON.stringify(value, replacer, space);
+        } catch (error) {
+            return null;
+        }
+    }
+    /**
+     * 判断字符是否为双字节字符(如中文字符)
+     * @param string 原字符串
+     */
+    public str_isDoubleWord(string: string): boolean {
+        return /[^\x00-\xff]/.test(string);
+    }
+    /**
+    * 是否为空
+    * @param str 
+    */
+    public str_isEmpty(str: string): boolean {
+        if (str == null || str == undefined || str.length == 0) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 转美式计数字符串
+     * @param value 数字
+     * @example
+     * 123456789 = 123,456,789
+     */
+    public numberTotPermil(value: number): string {
+        return value.toLocaleString();
+    }
+
+    private readonly _k: number = 1000;
+    private readonly _k_sizes_en: string[] = ['', 'K', 'M', 'G', 'T', 'P', 'E'];
+    private readonly _k_sizes_cn: string[] = ['', '千', '百万', '十亿', '万亿', '千万亿', '百亿亿'];
+    private readonly _w: number = 10000;
+    private readonly _w_sizes_en: string[] = ['', 'W', 'M', 'B', 'T'];
+    private readonly _w_sizes_cn: string[] = ['', '万', '亿', '兆', '京'];
+
+    /**  
+    * 通用单位转换方法  
+    */
+    private convertNumber(value: number, base: number, sizes: string[], fixed: number): string {
+        if (value < base) return value.toString();
+        const i = Math.floor(Math.log(value) / Math.log(base));
+        const r = value / Math.pow(base, i);
+        if (i >= sizes.length) return value.toString();
+        return `${r.toFixed(fixed)}${sizes[i]}`;
+    }
+    /**   
+     * 转单位计数(默认英文)  
+     * @param value 数字  
+     * @param fixed 保留小数位数  
+     * @param isEn 是否英文  
+     * @example  
+     * 12345 = 12.35K  
+     */
+    public numberToThousand(value: number, fixed: number = 2, isEn: boolean = true): string {
+        const sizes = isEn ? this._k_sizes_en : this._k_sizes_cn;
+        return this.convertNumber(value, this._k, sizes, fixed);
+    }
+    /**   
+     * 转单位计数(默认中文)  
+     * @param value 数字  
+     * @param fixed 保留小数位数  
+     * @param isEn 是否英文  
+     * @example  
+     * 12345 = 1.23万  
+     */
+    public numberToTenThousand(value: number, fixed: number = 2, isEn: boolean = false): string {
+        const sizes = isEn ? this._w_sizes_en : this._w_sizes_cn;
+        return this.convertNumber(value, this._w, sizes, fixed);
+    }
+    /**获取一个唯一标识的字符串 */
+    public guid() {
+        let guid: string = Math.random().toString(36).substring(2);
+        return guid;
+    }
+    /**排序一个json Object*/
+    public obj_sort(obj: any): any {
+        let sorted_keys: string[] = Object.keys(obj).sort();
+        let new_obj = {};
+        for (let i = 0; i < sorted_keys.length; i++) {
+            const key = sorted_keys[i];
+            const value = obj[key];
+            const t = this.obj_get_value_type(value);
+            new_obj[key] = t == 'object' ? this.obj_sort(value) : value;
+        }
+        return new_obj;
+    }
+    /**获取一个josn值的类型*/
+    public obj_get_value_type(value: any): string {
+        let type: string;
+        if (value === null) {
+            type = 'null';
+        } else {
+            type = typeof value;
+        }
+        // 如果是对象或数组,还可以进一步判断
+        if (type === 'object') {
+            if (Array.isArray(value)) { type = 'array'; }
+            //else if (value instanceof Date) {  type = 'date';   }  
+        }
+        return type;
+    }
+    /**
+     * 判断指定的值是否为对象
+     * @param value 值
+     */
+    public valueIsObject(value: any): boolean {
+        return Object.prototype.toString.call(value) === '[object Object]';
+    }
+    /**
+    * 深拷贝
+    * @param target 目标
+    */
+    /** 克隆对象 */
+    public obj_clone<T = any>(target_: T, record_set = new Set()): T {
+        let result: any;
+
+        switch (typeof target_) {
+            case "object": {
+                // 数组:遍历拷贝
+                if (Array.isArray(target_)) {
+                    if (record_set.has(target_)) {
+                        return target_;
+                    }
+
+                    record_set.add(target_);
+                    result = [];
+                    for (let k_n = 0; k_n < target_.length; ++k_n) {
+                        // 递归克隆数组中的每一项
+                        result.push(this.obj_clone(target_[k_n], record_set));
+                    }
+                }
+                // null:直接赋值
+                else if (target_ === null) {
+                    result = null;
+                }
+                // RegExp:直接赋值
+                else if ((target_ as any).constructor === RegExp) {
+                    result = target_;
+                }
+                // 普通对象:循环递归赋值对象的所有值
+                else {
+                    if (record_set.has(target_)) {
+                        return target_;
+                    }
+
+                    record_set.add(target_);
+                    result = {};
+                    for (const k_s in target_) {
+                        result[k_s] = this.obj_clone(target_[k_s], record_set);
+                    }
+                }
+
+                break;
+            }
+
+            case "function": {
+                result = target_.bind({});
+                break;
+            }
+
+            default: {
+                result = target_;
+            }
+        }
+
+        return result;
+    }
+    /**
+    * 拷贝对象
+    * @param target 目标
+    */
+    public copy(target: object): object {
+        return this.parse(this.stringify(target));
+    }
+    //请求相关--------------------------------------------
+    public getReqId(): string {
+        return Date.now() + "_" + Math.ceil(1e3 * Math.random());
+    }
+    private _url_data: Record<string, string> | null = null;
+    /**获取链接中传入的某个值*/
+    public getUrlData<T extends (string | number)>(key: string): T | null {
+        if (!this._url_data) {
+            this._url_data = this.parseUrl();
+        }
+        const value = this._url_data[key];
+        if (value === undefined) return null;
+        if (typeof value === 'string') {
+            const numberValue = parseFloat(value);
+            if (!isNaN(numberValue)) return (numberValue as unknown) as T;
+            return value as T;
+        }
+        return null;
+    }
+    private parseUrl(): Record<string, string> {
+        if (!sys.isBrowser || typeof window !== "object" || !window.document) return {};
+        const url = window.document.location.href;
+        const queryString = url.split("?")[1];
+        if (!queryString) return {};
+        return queryString.split("&").reduce<Record<string, string>>((acc, param) => {
+            const [key, value] = param.split("=");
+            if (key) acc[decodeURIComponent(key)] = value ? decodeURIComponent(value) : '';
+            return acc;
+        }, {});
+    }
+    /**获到node的坐标和范围用于平台在对应位置创建按纽*/
+    public getBtnOp(btnNode: Node): { left: number, top: number, width: number, height: number } {
+        let btnNodeUiTransform = btnNode.getComponent(UITransform);
+        const btnSize = size(btnNodeUiTransform.width + 0, btnNodeUiTransform.height + 0);
+        //let frameWidth = screen.windowSize.width / screen.devicePixelRatio
+        let frameHeight = screen.windowSize.height / screen.devicePixelRatio
+        //const winSize = screen.windowSize;
+        //const designSize = view.getDesignResolutionSize();
+        //console.log('designSize', designSize);
+        //console.log('winSize:', winSize);
+        //console.log('frameSize:', frameWidth,frameHeight);
+        //适配不同机型来创建微信授权按钮
+        let rect = btnNodeUiTransform.getBoundingBoxToWorld();
+        let ratio = screen.devicePixelRatio;
+        let scale = view.getScaleX();
+        let factor = scale / ratio;
+        let offsetX = 0;
+        let offsetY = 0;
+        let top = frameHeight - (rect.y + rect.height) * factor - offsetY;
+        let left = rect.x * factor + offsetX;
+        const width = btnSize.width * factor;
+        const height = btnSize.height * factor;
+        return { left: left, top: top, width: width, height: height }
+    }
+    /**远程加载图片*/
+    public async loadImage(url: string): Promise<SpriteFrame> {
+        if (chsdk.get_pf() == chsdk.pf.web) {
+            return await this.loadRemoteSprite(url);
+        } else {
+            const idata = await chsdk.loadImage(url);
+            return this.getSpriteFrame(idata);
+        }
+    }
+    /**cocos加载远程图片*/
+    public loadRemoteSprite(remoteUrl: string, ext: '.png' | '.jpg' = '.png'): Promise<SpriteFrame> {
+        return new Promise((resolve) => {
+            assetManager.loadRemote<ImageAsset>(remoteUrl, { ext: ext }, function (err, imageAsset) {
+                const spriteFrame = new SpriteFrame();
+                const texture = new Texture2D();
+                texture.image = imageAsset;
+                spriteFrame.texture = texture;
+                resolve(spriteFrame);
+            });
+        });
+    }
+    /**远程加载的图片数据转成spriteFrame*/
+    private getSpriteFrame(img: any): SpriteFrame {
+        if (!img) return null;
+        try {
+            const spriteFrame = new SpriteFrame();
+            const texture = new Texture2D();
+            texture.image = img instanceof ImageAsset ? img : new ImageAsset(img);
+            spriteFrame.texture = texture;
+            return spriteFrame;
+        } catch (error) {
+            //ch_log.warn(error);
+            return null;
+        }
+    }
+}
+export default ch_util.getInstance();

+ 9 - 0
assets/ch/ch_util.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "a9303097-d3ab-4e26-b93f-faf28d0f92a6",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 396 - 0
assets/ch/chsdk_inside.d.ts

@@ -0,0 +1,396 @@
+declare namespace chsdk {
+    /**日志等级*/
+    enum loglevel {
+        OFF = 0,
+        ERROR = 1,
+        WARN = 2,
+        DEBUG = 3,
+        INFO = 4,
+        ALL = 5
+    }
+    /**上报类型*/
+    export enum reportType {
+        /**默认不上报*/
+        off = 0,
+        /**使用ch服务器*/
+        ch = 1,
+        /**上报到应用平台(需在各平台后台设置上报事件,微信,抖音)*/
+        platform = 2,
+        /**上面两个都选*/
+        ch__platform = 3
+    }
+    /**获取权限码*/
+    export function getOption(): string;
+    /**获取token*/
+    export function getToken(): string;
+    export function init_inside(gid: string, loglevel: number, serverIP: number | string, isLocal?: boolean, report?: reportType): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+     * 获取基本功能完成接口地址
+     * @param action 接口字符串如 '/user/shareList'
+     * @returns
+     */
+    export function getUrl(action: string): string;
+    /**
+    * 获取上报功能完成接口地址
+    * @param action 接口字符串如 '/user/shareList'
+    * @returns
+    */
+    export function getReportUrl(action: string): string;
+    export function check_req_time(req_type: string): {
+        code: number;
+        err: string;
+    } | null;
+    export function makePostTokenRequest(url: string, body?: any): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    export function base64_encode(input: string): string;
+    export function base64_decode(input: string): string;
+    export function base64_xo_encode(input: string, key: string): string;
+    export function base64_xo_decode(input: string, key: string): string;
+    /**
+     * md5加密
+     */
+    export function md5HashStr(str: string): string;
+    /**
+     * 一次http请求
+     * @param url 请求地址
+     * @param method 请求方式
+     * @param data
+     * @param timeout
+     * @param responseType
+     * @param headers
+     * @returns
+     */
+    export function do_request(url: string, method: 'POST' | 'GET' | 'PUT', data?: any, timeout?: number, responseType?: XMLHttpRequestResponseType, headers?: {
+        [key in string]: string;
+    }): Promise<any>;
+    /**验证权限功能
+    * key 某个功能权限
+    * 返回 是否验证成功
+    */
+    export function verify_option(key: string): boolean;
+    /**获取一个唯一标识的字符串 */
+    export function guid(): string;
+    /**判断是否有名字,没有返回玩家*/
+    export function getDefNickName(info: any): string;
+    /**
+    * 主动记录分数到缓存
+    * @param rankType 排行榜类型
+    * @param update 更新类型
+    * @param type  0 个人排行 1地区排行
+    * @param score 分数
+    */
+    export function recordCacheScore(rankField: string, update: number, type: 0 | 1, score: number): void;
+    /**
+     * 获取缓存中的分数
+     * @param rankField
+     * @param update
+     * @param type
+     * @returns
+     */
+    export function getCacheScore(rankField: string, update: number, type: 0 | 1): number;
+    /**清空所有缓存分数*/
+    export function cleanCacheScore(): void;
+    /**清空所有缓存排行榜数据*/
+    export function cleanCacheRank(): void;
+    /**当前平台是否有分享功能*/
+    export function canShareAppMessage(): boolean;
+    /**
+    * 主动拉起分享转发
+    */
+    export function shareAppMessage(title?: string, imageUrlId?: string, imageUrl?: string, message?: string): void;
+    /**
+    * 主动拉起分享转发并等待是错分享成功
+    */
+    export function shareAppMessageAsync(title?: string, imageUrlId?: string, imageUrl?: string, message?: string): Promise<boolean>;
+    /**设置被动分享参数,具体参看平台的参数设置*/
+    export function setOnShareAppMessage(res: {}): void;
+    export function getQuery(): any;
+    /**
+    * @description 上传从分享进入(无需主动调用,除非需要在message里加入游戏特别数据)
+    * @param openid 服务器拿到的玩家oid 如果为空从平台拿
+    * @param message 分享的自定义数据
+    */
+    export function sendShare(openid?: string | null, message?: string | null): Promise<{
+        code: number;
+        err?: string;
+        data?: any;
+    }>;
+    /**
+    * @description 获取从自己分享进入游戏的玩家列表
+    */
+    export function getShareList(): Promise<{
+        code: number;
+        err?: string;
+        data?: {
+            gid: string;
+            head: string;
+            hid: number;
+            ip: string;
+            loginTime: number;
+            nickName: string;
+            openId: string;
+            option: string;
+            pf: string;
+            registerTime: number;
+            userId: number;
+            msg?: string;
+        }[];
+    }>;
+    /**开始录屏*/
+    export function recorderStart(duration?: number): void;
+    /**结束录屏*/
+    export function recorderStop(): void;
+    /**分享录屏 成功返回videoId,失败返回null*/
+    export function shareRecord(options: {
+        templateId?: string;
+        imageUrl?: string;
+        title?: string;
+        desc?: string;
+        query?: string;
+        path?: string;
+        topics?: string[];
+    }): Promise<string | null>;
+    /**上传配置
+    * @param name 配置包名称
+    * @param key 配置项名称
+    * @param config 配置
+    * @param secret 密钥
+    * @returns errCode 错误码 errMsg 错误信息  data 关卡配置列表
+    */
+    export function uploadConfig(name: string, key: string, config: {
+        [key: string]: any;
+    }, secret?: string): Promise<{
+        code: number;
+        err?: string;
+    }>;
+    /**预加载配置
+    * @param name 配置包名称
+    * @param keys 配置项名称列表
+    * @param secret 密钥
+    * @param refesh 强制刷新否则从缓存中拿
+    * @returns errCode 错误码 errMsg 错误信息  data 关卡配置列表
+    */
+    export function preloadConfig(name: string, keys: string[], secret?: string, refesh?: boolean): Promise<{
+        code: number;
+        err?: string;
+        data?: {
+            [key: string]: any;
+        };
+    }>;
+    /**获取配置
+    * @param name 配置包名称
+    * @param key 配置项名称列表
+    * @param secret 密钥
+    * @param refesh 强制刷新否则从缓存中拿
+    * @returns errCode 错误码 errMsg 错误信息  data 关卡配置列表
+    */
+    export function getConfig(name: string, key: string, secret?: string, refesh?: boolean): Promise<{
+        code: number;
+        err?: string;
+        data?: {
+            [key: string]: any;
+        };
+    }>;
+    /**创建反馈按钮
+    * @param op 按纽坐标大小
+    * 例:feed_node.active = ch.sdk.createFeedbackButton(ch.util.getBtnOp(feed_node));
+    * 如果对应平台有此功能(目前只有wx)返回true,没有返回false
+    * 成功记得 destoryFeedbackButton();*/
+    export function createFeedbackButton(op: {
+        left: number;
+        top: number;
+        width: number;
+        height: number;
+    }): boolean;
+    /**销毁反馈按钮*/
+    export function destoryFeedbackButton(): void;
+    /**复制到剪切板*/
+    export function setClipboardData(content: string): void;
+    /**短震动(15ms)*/
+    export function vibrateShort(): void;
+    /**长震动(400ms)*/
+    export function vibrateLong(): void;
+    /**界面提示信息*/
+    export function showToast(title: string, duration?: number, icon?: 'success' | 'error' | 'fail' | 'loading' | 'none'): boolean;
+    /**获取菜单坐标范围*/
+    export function getMenuButtonBoundingClientRect(): any;
+    export function showLoading(title?: string): boolean;
+    export function hideLoading(): void;
+    export function showModal(title?: string, content?: string, confirmText?: string, showCancel?: boolean, cancelText?: string): Promise<{
+        confirm: boolean;
+        cancel: boolean;
+    }>;
+    export function openSetting(scope: string | 'scope.userInfo' | 'scope.userLocation'): Promise<boolean>;
+    /**加载图片
+    * cocos里不要直接调用,使用 ch.util.loadImage(url);
+    */
+    export function loadImage(imgURL: string): Promise<any>;
+    /**当前平台是否有关注功能*/
+    export function canAwemeUserProfile(): boolean;
+    /**检查是否已经关注*/
+    export function checkFollowAwemeState(): Promise<boolean>;
+    /**打开关注返回关注结果*/
+    export function openAwemeUserProfile(): Promise<boolean>;
+    /**当前平台是否有订阅功能*/
+    export function canSubscribeMessage(): boolean;
+    /**订阅消息 参数为空的话使用配置里的模板id*/
+    export function requestSubscribeMessage(tmplIds?: string[]): Promise<boolean>;
+    class ch_log {
+        private static _instance;
+        static getInstance(): ch_log;
+        private _log_level;
+        set_log_level(loglevel: loglevel): void;
+        private _no;
+        private _log;
+        private _info;
+        private _debug;
+        private _trace;
+        private _warn;
+        private _error;
+        private _log_start;
+        private _log_end;
+        get log(): (message?: any, ...optionalParams: any[]) => void;
+        get info(): (message?: any, ...optionalParams: any[]) => void;
+        get debug(): (message?: any, ...optionalParams: any[]) => void;
+        get trace(): (message?: any, ...optionalParams: any[]) => void;
+        get warn(): (message?: any, ...optionalParams: any[]) => void;
+        get error(): (message?: any, ...optionalParams: any[]) => void;
+        /**记录开始计时*/
+        get log_start(): (label?: string) => void;
+        /** 打印范围内时间消耗*/
+        get log_end(): (label?: string) => void;
+    }
+    /**日志打印*/
+    export const log: ch_log;
+    /**事件*/
+    export interface EventsMap {
+        [event: string]: (...args: any[]) => void;
+    }
+    export type EventNames<Map extends EventsMap> = keyof Map & string;
+    export type OmitIndex<T> = {
+        [K in keyof T as string extends K ? never : number extends K ? never : K]: T[K];
+    };
+    export type EventParams<Map extends EventsMap, Ev extends EventNames<Map>> = Parameters<Map[Ev]>;
+    class EventModel<CT extends OmitIndex<EventsMap>> {
+        private _handlersMap;
+        /** 事件键 */
+        key: {
+            [k in EventNames<CT>]: k;
+        };
+        private _isEmitting;
+        private _pendingOperations;
+        /**
+        * 监听事件
+        * @param type_ 事件类型
+        * @param callback_ 触发回调
+        * @param target_ 事件目标对象
+        * @param once_b_ 是否触发单次
+        * @returns 触发回调
+        */
+        on<T extends EventNames<CT>, T2 extends (...event_: EventParams<CT, T>) => void>(type_: T, callback_: T2, target_?: any, once_?: boolean): typeof callback_ | null;
+        once<T extends EventNames<CT>, T2 extends (...event_: EventParams<CT, T>) => void>(type_: T, callback_: T2, target_?: any): void;
+        /**
+         * 取消监听事件
+         * @param type_ 事件类型
+         * @param callback_ 触发回调
+         * @param target_ 事件目标对象
+         * @returns 触发回调
+         */
+        off<T extends EventNames<CT>, T2 extends (...event_: EventParams<CT, T>) => void>(type_: T, callback_?: T2, target_?: any): void;
+        /**
+         * 清除某个事件队列
+         * @param type_ 事件名,为空的话清除所有
+         */
+        clearAll<T extends EventNames<CT>>(type_?: T): void;
+        /**
+        * 派发事件
+        * @param type_ 事件类型
+        * @param args_ 事件参数
+        */
+        emit<T extends EventNames<CT>, T2 extends EventParams<CT, T>>(type_: T, ...args_: T2): void;
+        private _emit;
+    }
+    /**sdk事件*/
+    export interface event_sdk {
+        show(): void;
+        hide(): void;
+        onShare(success: boolean): void;
+        onShowAd(type: 'Rewarded' | 'Interstitial' | 'Banner', success: boolean): void;
+    }
+    export const sdk_event: EventModel<event_sdk>;
+    export function get_new_event<CT>(): EventModel<CT>;
+    /**日期时间*/
+    class ch_date {
+        private static _instance;
+        static getInstance(): ch_date;
+        private _day_s;
+        private _diff;
+        updateServerTime(s: number): void;
+        now(): number;
+        getTime(): number;
+        getDayStartTime(s: number): number;
+        getDayEndTime(e: number): number;
+        getWeekEndTime(e: number): number;
+        getMonthEndTime(e: number): number;
+        /**两个时间相隔天数*/
+        getDiffDayNum(e: number, t: number): number;
+        /**判定两个时间是否是同一天*/
+        isSameDate(timestamp1: number, timestamp2: number): boolean;
+        /**
+         * 单位毫秒格式化
+         * - $H: 替换为小时,补全空位(02:00:00)
+         * - $h: 替换为小时,不补全(2:00:00)
+         * - $M: 替换为分钟,补全空位(00:02:00)
+         * - $m: 替换为分钟,不补全(00:2:00)
+         * - $S: 替换为秒,补全空位(00:00:02)
+         * - $s: 替换为秒,不补全(0:00:2)
+         */
+        ms_format(ms_n_: number, format_s_?: string): string;
+    }
+    export const date: ch_date;
+    /**本地存储*/
+    class ch_storage {
+        private static _instance;
+        static getInstance(): ch_storage;
+        /**
+         * 缓存变量存储
+         * @param  key
+         * @param  value
+         * @param  user_id 区别用户
+         */
+        set(key: string, value: any, user_id?: string | null): any;
+        /**
+         * 获取缓存变量区别用户
+         * @param {*} key
+         * @returns
+         */
+        private get;
+        /** 获取指定关键字的string*/
+        getString(key: string, user_id?: string | null): string | null;
+        /** 获取指定关键字的数值 */
+        getNumber(key: string, user_id?: string | null, defaultValue?: number): number;
+        /** 获取指定关键字的布尔值*/
+        getBoolean(key: string, user_id?: string | null): boolean;
+        /** 获取指定关键字的JSON对象*/
+        getJson(key: string, user_id?: string | null): any;
+        /** 获取指定关键字的JSON对象*/
+        getObject(key: string, user_id?: string | null): any;
+        /**
+         * 删除缓存变量
+         * @param {*} key
+        */
+        remove(key: string, user_id?: string | null): any;
+        /** 清空整个本地存储 */
+        clear(): void;
+    }
+    /**本地存储*/
+    export const storage: ch_storage;
+    export { };
+}

+ 9 - 0
assets/ch/chsdk_inside.d.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "0ddd0740-37ee-4339-bfc3-33d9302a99a7",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/ch/net.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "8d783bae-c476-461f-b3e6-3cb70421533f",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 112 - 0
assets/ch/net/NetBase.ts

@@ -0,0 +1,112 @@
+/**网络变量基类*/
+export class NetBase {
+    private _id: string;
+    public get Id(): string { return this._id };
+    private dataMap: { [key: string]: any } = {};
+    private dirtyMap: { [key: string]: any } = {};
+    private _dirty: boolean = false;
+    constructor(id: string) {
+        this._id = id;
+        this.dataMap = {};
+    }
+    /**初始变量值*/
+    public initValue(data: { [key: string]: any }): void {
+        this.dataMap = data ?? {};
+    }
+    /**修改某个键的值*/
+    protected setValue(key: string, value: any): void {
+        this.dataMap[key] = value;
+    }
+    protected setValueDirty(key: string, value: any): void {
+        this.dirtyMap[key] = value;
+        this._dirty = true;
+    }
+    /**获取数据的值*/
+    protected getValue(key: string): any {
+        return this.dataMap[key];
+    }
+    /**处理脏数据*/
+    private doDirtyData(f: (data: { [key: string]: any }) => void): void {
+        if (!this._dirty) return;
+        f(this.dirtyMap);
+        this.dirtyMap = {};
+        this._dirty = false;
+    }
+    protected dispose(): void {
+        this.dataMap = {};
+        this.dirtyMap = {};
+        this._dirty = false;
+    }
+}
+export function transUserDataform(data: any): UserData {
+    data.hid = Number.parseInt(data.hid);
+    data.province = chsdk.provinceCode2Name(data.hid) ?? '其它';
+    data.userId = Number.parseInt(data.userId);
+    data.loginTime = Number.parseInt(data.loginTime);
+    data.registerTime = Number.parseInt(data.registerTime);
+    data.nickName = data.nickName || '玩家' + data.userId;
+    return data;
+}
+export function transUserExtraDataform<T>(data: string): T | null {
+    return data ? JSON.parse(data) as T : null
+}
+/**玩家信息*/
+export interface UserData {
+    gid: string,
+    head: string,
+    hid: number,
+    ip: string,
+    loginTime: number,
+    nickName: string,
+    openId: string,
+    option: string,
+    pf: string,
+    registerTime: number,
+    userId: number,
+    province: string
+}
+export interface Elements {
+    /**排名*/Rank: number,
+    /**段位信息*/Level: Level,
+    /**玩家基本信息*/UserData: UserData
+}
+/**段位信息*/
+export interface Level {
+    /**段位*/ LowerRank: number,
+    /**该段位下的级别*/  Rank: number,
+    /**该段位下星星数*/ Star: number
+}
+export interface results {
+    /**玩家Id*/Id: string,
+    /**变动星星数*/AddScore: number,
+    /**是否完成*/IsFinish: boolean,
+    /**本局排名*/Rank: number,
+    /**最终星星数*/Score: number,
+    /**段位排名*/TotalRank: number,
+    /**段位信息*/Level: Level,
+    /**玩家基本信息*/UserData: UserData,
+    /**相近排名玩家信息*/Elements: Elements[],
+}
+
+export interface RankHistory {
+     /**是否处理过奖励 */IsReceive: boolean,
+     /**排名*/Rank: number,
+     /**分数*/Score: number,
+     /**所在赛季*/Season: number
+}
+export interface SeasonInfo {
+     /**当前赛季的编号 */Season: number,
+     /**赛季结束时间的时间戳 */SeasonEndTime: number,
+     /**赛季结束时间的时间戳 */SeasonStartTime: number,
+     /**历史赛季数组 */RankHistory: RankHistory[]
+}
+export interface UserRank<T> {
+    /**玩家基本信息*/UserData: UserData,
+    /**玩家扩展数据*/UserDataExtra: T,
+    /**段位信息*/ Level: Level,
+    /**排名*/ Rank: number,
+}
+export interface Ranks<T> {
+    /**排名列表*/list: UserRank<T>[],
+    /**玩家自己的排名*/own: UserRank<T>,
+}

+ 1 - 0
assets/ch/net/NetBase.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"8f540fe1-affb-4680-9926-7807c2d336f1","files":[],"subMetas":{},"userData":{}}

+ 233 - 0
assets/ch/net/NetPlayer.ts

@@ -0,0 +1,233 @@
+import { NetBase, transUserDataform, transUserExtraDataform, UserData } from "./NetBase";
+type EventNames<Map extends chsdk.OmitIndex<chsdk.EventsMap>> = Extract<keyof Map, string>;
+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> = Pick<T, Extract<keyof T, `${Prefix1}${string}` | `${Prefix2}${string}`>>;
+type protocol = { extra?: Record<string, any> } & chsdk.OmitIndex<chsdk.EventsMap>;
+/**网络玩家*/
+export class NetPlayer<GD extends protocol> extends NetBase {
+    public readonly evt = chsdk.get_new_event<PickEvent<GD, 'p_', 'online' | 'finish' | 'ready' | 'exit'>>();
+    private _location: number = 0;
+    private _online: boolean = true;
+    private _ready: boolean = false;
+    private _score: number = 0;
+    /**总星数*/
+    public get score(): number { return this._score };
+    private _rank: number = 0;
+    /**当局排名*/
+    public get rank(): number { return this._rank };
+    private _exit: boolean = false;
+    /**是否退出游戏*/
+    private get isExit(): boolean { return this._exit };
+    private _totalRank: number = 0;
+    /**总排名 */
+    public get totalRank(): number { return this._totalRank };
+    private _ishost: boolean = true;
+    /**活跃可互动状态(在线,还没有离开房间,还没有结算名次)*/
+    public get active(): boolean { return this.online && !this.isExit && !this._rank };
+    /**房间位置0开头*/
+    public get location(): number { return this._location };
+    private _userData: UserData;
+    /**游戏id*/
+    public get gid(): string { return this._userData.gid };
+    /**头像*/
+    public get head(): string { return this._userData.head };
+    /**省id*/
+    public get hid(): number { return this._userData.hid };
+    /**来自省份 */
+    public get province(): string { return this._userData.province };
+    public get ip(): string { return this._userData.ip };
+    /**最近登录时间戳*/
+    public get loginTime(): number { return this._userData.loginTime };
+    /**名称*/
+    public get nickName(): string { return this._userData.nickName };
+    /**平台d */
+    public get openId(): string { return this._userData.openId };
+    /**权限*/
+    public get option(): string { return this._userData.option };
+    /**平台*/
+    public get pf(): string { return this._userData.pf };
+    /**用户Id */
+    public get userId(): number { return this._userData.userId };
+    /**注册时间戳*/
+    public get registerTime(): number { return this._userData.registerTime };
+    /**是否是主机*/
+    public get isHost(): boolean { return this._ishost };
+    /**是否准备*/
+    public get ready(): boolean { return this._ready };
+    /**是否在线*/
+    public get online(): boolean { return this._online };
+    /**是否能开启匹配*/
+    public get canMatch(): boolean { return this.online && this.ready };
+    private _rankInfo: { LowerRank: number, Rank: number, Star: number } | null;
+    private _userDataExtra: GD['extra'] = null;
+    /**玩家扩展数据 */
+    public get userDataExtra(): GD['extra'] { return this._userDataExtra; };
+    /**获取玩家扩展数据某个字段*/
+    public getUserDataExtraField<K extends keyof GD['extra']>(key: K): GD['extra'][K] { return this._userDataExtra?.[key]; }
+    /**段位信息*/
+    public get level(): { LowerRank: number, Rank: number, Star: number } | null { return this._rankInfo };
+    /**是否是当前玩家自己*/
+    private _isOwn: boolean = false;
+    public get isOwn(): boolean { return this._isOwn };
+    /**是否是AI */
+    public get isAI(): boolean { return this._userData.userId < 8888; };
+    private _canAI: boolean = false;
+    /**是否有控制当前AI权限*/
+    public get canAI(): boolean { return this._canAI; };
+    /**是否有此玩家数据权限*/
+    public get isPermission(): boolean { return this.isOwn || this._canAI; };
+    public init(pd: { location: number, status: boolean, userData: any, rank: number, TotalRank: number, score: number, teamReady?: boolean; gameData?: any, userRank?: any, userDataExtra?: any }): void {
+        this._location = pd.location;
+        this._online = pd.status ?? true;
+        this._ready = pd.teamReady;
+        this._score = pd.score;
+        this._rank = pd.rank;
+        this._totalRank = pd.TotalRank;
+        this._userData = transUserDataform(pd.userData);
+        this._rankInfo = pd.userRank;
+        this._userDataExtra = transUserExtraDataform(pd.userDataExtra);
+        if (pd.gameData) this.initValue(pd.gameData);
+    }
+    private set_userExtra(data: any): void {
+        this._userDataExtra = data;
+        (this.evt as any)._emit('p_extra');
+    }
+    private set_level(level: { LowerRank: number, Rank: number, Star: number }, rank: number, score: number, totalRank: number): void {
+        this._rankInfo = level;
+        this._rank = rank;
+        this._totalRank = totalRank;
+        this._score = score;
+    }
+    private set_own(isOwn: boolean): void {
+        this._isOwn = isOwn;
+    }
+    private set_host(isHost: boolean, ownHost: boolean): void {
+        this._ishost = isHost;
+        this._canAI = this.isAI && ownHost;
+    }
+    private change_online(ol: boolean): void {
+        this._online = ol;
+        (this.evt as any)._emit('online', this._online);
+    }
+    private change_ready(ready: boolean): void {
+        this._ready = ready;
+        (this.evt as any)._emit('ready', this._ready);
+    }
+    /**创建自定义obj数据*/
+    public creatObj<T extends { [key: string]: any }>(data: T): string {
+        if (!this.isPermission) 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('p_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.isPermission) return;
+        const old = super.getValue(key);
+        if (old) {
+            super.setValue(key, data);
+            super.setValueDirty(key, data);
+            (this.evt as any)._emit('p_obj', key, data, old);
+        }
+    }
+    /**删除obj数据*/
+    public deleteObj(key: string) {
+        if (!this.isPermission) return;
+        const old = super.getValue(key);
+        if (old) {
+            super.setValue(key, 0);
+            super.setValueDirty(key, 0);
+            (this.evt as any)._emit('p_obj', key, null, old);
+        }
+    }
+    /**修改某个键的值*/
+    public setValue<T extends StartEventNames<GD, 'p_'>, T2 extends EventParamFrist<GD, T>>(key: T, data: T2): void {
+        if (!this.isPermission) return;
+        let old = super.getValue(key);
+        if (old) {
+            if (typeof old === "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, 'p_'>, T2 extends EventParamFrist<GD, T>>(key: T): T2 {
+        return super.getValue(key);
+    }
+    //服务器发送过来的数据
+    private server_change(data: { [key: string]: any }): void {
+        if (this.isPermission) return;
+        const evt: { evt: 0 | 1, key: string, data: any, old: any }[] = [];
+        Object.keys(data).forEach(key => {
+            const old = super.getValue(key);
+            super.setValue(key, data[key]);
+            if (key === 'oid') {
+                return;
+            } else if (key.startsWith('obj_')) {
+                evt.push({ evt: 1, key: key, data: data[key] === 0 ? null : data[key], old: old });
+            } else {
+                evt.push({ evt: 0, key: key, data: data[key], old: old });
+            }
+        });
+        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('p_obj', ee.key, ee.data, ee.old);
+            } else {
+                e._emit(ee.key, ee.data, ee.old);
+            }
+        }
+    }
+    private setFinish(rank: number): void {
+        this._rank = rank;
+        (this.evt as any)._emit('finish', this._rank);
+    }
+    private exit(): void {
+        this._exit = true;
+        (this.evt as any)._emit('exit');
+    }
+    private _finsh_tag: boolean = false;
+    /**玩家完成游戏 不是自己或主机没有权限*/
+    public finishGame(): void {
+        if (!this.isPermission) return;
+        this._finsh_tag = true;
+    }
+    private doFinishGame(f: (id: string) => void): void {
+        if (this._finsh_tag) f(this.Id);
+        this._finsh_tag = false;
+    }
+    public dispose(): void {
+        super.dispose();
+        this.evt.clearAll();
+    }
+}

+ 1 - 0
assets/ch/net/NetPlayer.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"360c11e4-0499-4012-96e3-63d989687a4d","files":[],"subMetas":{},"userData":{}}

+ 475 - 0
assets/ch/net/NetRoom.ts

@@ -0,0 +1,475 @@
+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} 玩家的总排名  
+ * - LevelCtrl: {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} 其他玩家的排名 
+ *   - LevelCtrl: {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();
+    }
+}

+ 1 - 0
assets/ch/net/NetRoom.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"be23235f-23df-4ec2-8f2a-41b1b29af740","files":[],"subMetas":{},"userData":{}}

+ 255 - 0
assets/ch/net/NetTeam.ts

@@ -0,0 +1,255 @@
+import { NetPlayer } from "./NetPlayer";
+type PickEvent<T, Prefix extends string> = Pick<T, Extract<keyof T, `${Prefix}${string}`>>;
+const C2S_MATCH = 'c2s_match';
+const C2S_MATCH_CANCEL = 'c2s_match_cancel';
+const C2S_READY = 'c2s_ready';
+const C2S_READY_CANCEL = 'c2s_ready_cancel';
+const C2S_KICK_OUT = 'c2s_kick_out';
+const C2S_TALK = 'c2s_talk';
+/**网络队伍*/
+export class NetTeam<GD> {
+    /**队伍事件*/
+    public readonly evt = chsdk.get_new_event<PickEvent<GD, 't_'>>();
+    private _status: boolean = true;
+    /**队伍是否能进*/public get status(): boolean { return this._status };
+    private _hostId: string;
+    /**队伍队长ID*/public get HostId(): string { return this._hostId };
+    private _password: string;
+    /**队伍进入口令*/public get Password(): string { return this._password };
+    private _limit: number;
+    /**队伍上限*/public get Limit(): number { return this._limit };
+    private _id: string;
+    /**队伍标识id*/public get Id(): string { return this._id };
+    private _inCollect: boolean = false;
+    /**是否在匹配中*/public get inCollect(): boolean { return this._inCollect };
+    private _own_id: string;
+    private _players: Map<string, NetPlayer<GD>> = new Map();
+    private _send: (type: string, data?: any) => void;
+    public get ownId(): string { return this._own_id };
+    constructor(ownId: string, data: any, send: (type: string, data?: any) => void) {
+        this._own_id = ownId;
+        const team = data.teamData;
+        const playerData = data.playerData;
+        this._hostId = team.hostInfo;
+        this._status = team.status;
+        this._inCollect = team.inCollect;
+        this._password = team.password;
+        this._limit = team.limit;
+        this._id = team.roomId;
+        this._players.clear();
+        let ps = Object.keys(playerData).map(key => ({ key, data: playerData[key] }));
+        for (let i = 0; i < ps.length; i++) this.addPlayer(ps[i]);
+        this._send = send;
+    }
+    /**主机开始匹配*/
+    public match(): void {
+        if (!this.isHost) return;
+        this._send(C2S_MATCH);
+    }
+    /**主机退出匹配*/
+    public exit_match(): void {
+        if (!this.isHost) return;
+        this._send(C2S_MATCH_CANCEL);
+    }
+    /**主机踢出某个玩家出小队 */
+    public kick_out(location: number): void;
+    public kick_out(id: string): void;
+    public kick_out(arg: string | number): void {
+        if (!this.isHost) return;
+        if (typeof arg === 'string') {
+            this._send(C2S_KICK_OUT, arg);
+        } else if (typeof arg === 'number') {
+            const playerToKick = this.all.find(player => player.location === arg);
+            if (playerToKick) this._send(C2S_KICK_OUT, playerToKick.Id); // 踢出找到的玩家  
+        }
+    }
+    /**主机踢出所有没有准备的玩家 */
+    public kick_out_no_readys(): void {
+        if (!this.isHost) return;
+        const list = this.notreadys;
+        for (let i = 0; i < list.length; i++) this._send(C2S_KICK_OUT, list[i].Id);
+    }
+    /**主机踢出所有不在线玩家*/
+    public kick_out_outlines(): void {
+        if (!this.isHost) return;
+        const list = this.outlines;
+        for (let i = 0; i < list.length; i++) this._send(C2S_KICK_OUT, list[i].Id);
+    }
+    /**主机踢出所有不在线或没有准备的玩家*/
+    public kick_out_badplayers(): void {
+        if (!this.isHost) return;
+        const list = this.badplayers;
+        for (let i = 0; i < list.length; i++) this._send(C2S_KICK_OUT, list[i].Id);
+    }
+    /**准备*/
+    public ready(): void {
+        //if (!this.own.ready) 
+        this._send(C2S_READY);
+    }
+    /**取消准备*/
+    public cancelReady(): void {
+        //if (this.own.ready)
+        this._send(C2S_READY_CANCEL);
+    }
+    private addPlayer(p: any, isevt: boolean = false): 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);
+        if (isevt) (this.evt as any)._emit('t_entry', id, player.location, player.nickName);
+    }
+    /**玩家离开*/
+    private removePlayer(id: string): void {
+        const p = this._players.get(id)
+        if (p) {
+            const location = p.location;
+            const nickName = p.nickName;
+            this._players.delete(id);
+            (this.evt as any)._emit('t_exit', id, location, nickName);
+        }
+    }
+    /**队伍解散*/
+    private closed(): void {
+        (this.evt as any)._emit('t_closed');
+    }
+    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('t_online', id, online, location, nickName);
+        }
+    }
+    private updatePlayerReady(id: string, ready: boolean): void {
+        const p = this.getPlayer(id);
+        if (p) {
+            (p as any).change_ready(ready);
+            const location = p.location;
+            const nickName = p.nickName;
+            (this.evt as any)._emit('t_ready', id, ready, location, nickName);
+        }
+    }
+    private match_start(): void {
+        this._inCollect = true;
+        (this.evt as any)._emit('t_matchStart');
+    }
+    private match_cancel(): void {
+        this._inCollect = false;
+        (this.evt as any)._emit('t_matchCancel');
+    }
+    private match_success(): void {
+        this._inCollect = false;
+        (this.evt as any)._emit('t_matchSuccess');
+        const ps = this.getAllPlayer();
+        for (let i = 0; i < ps.length; i++) {
+            (ps[i] as any).change_ready(false);
+        }
+    }
+    private not_ready(): void {
+        (this.evt as any)._emit('t_no_ready');
+    }
+    private changeHost(id: string): void {
+        this._hostId = id;
+        this._players.forEach((v, _) => { (v as any).set_host(v.Id === this._hostId); });
+        const p = this.getPlayer(this._hostId);
+        (this.evt as any)._emit('t_host', id, p.location, p.nickName);
+    }
+    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('t_chat', id, msg, p.location, p.nickName);
+    }
+    private doSendChat() {
+        if (this._chat_msg) {
+            this._send(C2S_TALK, this._chat_msg);
+            this._chat_msg = null;
+        }
+    }
+    /**自己是否是主机*/
+    get isHost(): boolean {
+        return this._own_id === this._hostId;
+    }
+    /**自己的所有信息*/
+    public get own(): NetPlayer<GD> {
+        return this._players.get(this._own_id);
+    }
+    /**所有玩家*/
+    public get all(): NetPlayer<GD>[] {
+        return Array.from(this._players.values());
+    }
+    /**其它玩家信息*/
+    public get others(): NetPlayer<GD>[] {
+        return Array.from(this._players.values()).filter(player => !player.isOwn);;
+    }
+    /**在线玩家信息*/
+    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 get badplayers(): NetPlayer<GD>[] {
+        return Array.from(this._players.values()).filter(player => !player.ready || !player.online);;
+    }
+    /**准备好的玩家信息*/
+    public get readys(): NetPlayer<GD>[] {
+        return Array.from(this._players.values()).filter(player => player.ready);;
+    }
+    /**没有准备的玩家信息*/
+    public get notreadys(): NetPlayer<GD>[] {
+        return Array.from(this._players.values()).filter(player => !player.ready);;
+    }
+    /**某个玩家的所有信息*/
+    public getPlayer(id: string): NetPlayer<GD> {
+        return this._players.get(id);
+    }
+    /**所有玩家*/
+    public getAllPlayer(): NetPlayer<GD>[] {
+        return this.all;
+    }
+    /**将玩家按 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;
+    }
+    /**获取除了某个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;
+    }
+    /**获准备或没有准备的所有玩家*/
+    public getReadyPlayer(ready: boolean): NetPlayer<GD>[] {
+        return ready ? this.readys : this.notreadys;
+    }
+    public dispose(): void {
+        this._send = null;
+        this._players.forEach((v, _) => {
+            v.dispose();
+        });
+        this._players.clear();
+    }
+}

+ 1 - 0
assets/ch/net/NetTeam.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"dedb90c0-677b-4983-a3b1-b1361ad4cda3","files":[],"subMetas":{},"userData":{}}

+ 114 - 0
assets/ch/net/TTWsClient.ts

@@ -0,0 +1,114 @@
+
+/**tt scoket 客户端*/
+export class TTWsClient {
+    public onConnected(): void { };
+    public onError(err: string): void { };
+    public onClosing(): void { };
+    public onClosed(code: number, reason: string): void { };
+    public onMessage(msg: string | ArrayBuffer): void { };
+    //
+    private _ws: any | null = null;
+    /** WebSocket对象*/get ws() { return this._ws };
+    /** 连接地址*/
+    private _url: string | null = null;
+    /**证书*/
+    private _ca?: string;
+    constructor(url: string, ca?: string, autoConnect?: boolean) {
+        this._ca = ca;
+        this._url = url;
+        if (autoConnect) this.connect();
+    }
+    /**
+     * 获取当前连接状态
+     * @returns 是否处于活动状态
+     */
+    get isActive(): boolean {
+        return this._ws != null;
+    }
+    /**
+     *是否正在关闭
+     */
+    private get isClosing(): boolean {
+        return this._closing;
+    }
+    /**是否已经关闭 */
+    private get isClosed(): boolean {
+        return this._ws === null;
+    }
+    /**手动连接*/
+    connect() {
+        if (this.isActive) return false;
+        const url = this._url;
+        if (!url) return false;
+        try {
+            this.close();
+            //eslint-disable-next-line @typescript-eslint/ban-ts-comment
+            //@ts-ignore
+            //eslint-disable-next-line @typescript-eslint/no-unsafe-call
+            const ws = tt.connectSocket({
+                url: url,
+                success: (res) => {
+                    chsdk.log.info("WebSocket创建成功", res);
+                },
+                fail: (res) => {
+                    chsdk.log.info("WebSocket创建失败", res);
+                },
+            });
+            ws.onOpen(() => {
+                chsdk.log.info("WebSocket 已连接");
+                this.onConnected();
+            });
+            ws.onClose((res) => {
+                this._ws = null;
+                this.onClosed(res?.code, res?.reason);
+                chsdk.log.info("WebSocket 已断开");
+            });
+            ws.onError((error) => {
+                chsdk.log.info("WebSocket 发生错误:", error);
+                this.onError(error);
+            });
+            ws.onMessage((message) => {
+                chsdk.log.info("socket message:", message);
+                this.onMessage(message.data);
+            });
+            this._ws = ws;
+        } catch (error) {
+            this.onError(error instanceof Error ? error.message : 'Unknown error');
+        }
+        return true;
+    }
+    private _closing: boolean = false;
+    /**
+     * 主动关闭WebSocket连接
+     */
+    close(code?: number, reason?: string): void {
+        if (this.isClosed || this.isClosing) return;
+        this._closing = true;
+        this.onClosing();
+        this._ws.close({
+            code: code,
+            reason: reason,
+            success: (res) => {
+                // 关闭成功的回调
+                chsdk.log.info("close success", res);
+            },
+            fail: (res) => {
+                // 关闭失败的回调
+                chsdk.log.info("close fail", res);
+            },
+        });
+    }
+    /**
+    * 发送数据
+    * @param data 指定格式数据
+    */
+    send(data: string | ArrayBuffer): void {
+        this._ws.send({
+            data: data,
+            success: (ret) => { chsdk.log.info('成功', ret) },
+            fail: (ret) => { chsdk.log.info('失败', ret) },
+            complete: (ret) => { chsdk.log.info('完成', ret) },
+        })
+    }
+}
+

+ 1 - 0
assets/ch/net/TTWsClient.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"409f1698-0486-46ca-8a3d-b279fdde18e7","files":[],"subMetas":{},"userData":{}}

+ 109 - 0
assets/ch/net/WXWsClient.ts

@@ -0,0 +1,109 @@
+
+/**Wxweb scoket 客户端*/
+export class WXWsClient {
+    public onConnected(): void { };
+    public onError(err: string): void { };
+    public onClosing(): void { };
+    public onClosed(code: number, reason: string): void { };
+    public onMessage(msg: string | ArrayBuffer): void { };
+    //
+    private _ws: any | null = null;
+    /** WebSocket对象*/get ws() { return this._ws };
+    /** 连接地址*/
+    private _url: string | null = null;
+    /**证书*/
+    private _ca?: string;
+    constructor(url: string, ca?: string, autoConnect?: boolean) {
+        this._ca = ca;
+        this._url = url;
+        if (autoConnect) this.connect();
+    }
+    /**
+     * 获取当前连接状态
+     * @returns 是否处于活动状态
+     */
+    get isActive(): boolean {
+        return this._ws != null;
+    }
+    /**
+     *是否正在关闭
+     */
+    private get isClosing(): boolean {
+        return this._closing;
+    }
+    /**是否已经关闭 */
+    private get isClosed(): boolean {
+        return this._ws === null;
+    }
+    /**手动连接*/
+    connect() {
+        if (this.isActive) return false;
+        const url = this._url;
+        if (!url) return false;
+        try {
+            this.close();
+            //eslint-disable-next-line @typescript-eslint/ban-ts-comment
+            //@ts-ignore
+            //eslint-disable-next-line @typescript-eslint/no-unsafe-call
+            const ws = wx.connectSocket({
+                url: url,
+                header: {
+                    'content-type': 'application/json'
+                }
+            })
+            ws.onClose((res) => {
+                this._ws = null;
+                this.onClosed(res.code, res.reason);
+            });
+            ws.onError((res) => {
+                this.onError(res.errMsg);
+            });
+            ws.onMessage((res) => {
+                this.onMessage(res.data);
+            })
+            ws.onOpen((res) => {
+                console.log(res.header);
+                console.log(res.profile);
+                this.onConnected();
+            })
+            this._ws = ws;
+        } catch (error) {
+            this.onError(error instanceof Error ? error.message : 'Unknown error');
+        }
+        return true;
+    }
+    private _closing: boolean = false;
+    /**
+     * 主动关闭WebSocket连接
+     */
+    close(code?: number, reason?: string): void {
+        if (this.isClosed || this.isClosing) return;
+        this._closing = true;
+        this.onClosing();
+        this._ws.close({
+            code: code,
+            reason: reason,
+            success: (res) => {
+                // 关闭成功的回调
+                chsdk.log.log("close success", res);
+            },
+            fail: (res) => {
+                // 关闭失败的回调
+                chsdk.log.log("close fail", res);
+            },
+        });
+    }
+    /**
+    * 发送数据
+    * @param data 指定格式数据
+    */
+    send(data: string | ArrayBuffer): void {
+        this._ws.send({
+            data: data,
+            success: (ret) => { console.log('成功', ret) },
+            fail: (ret) => { console.log('失败', ret) },
+            complete: (ret) => { console.log('完成', ret) },
+        })
+    }
+}
+

+ 1 - 0
assets/ch/net/WXWsClient.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"cf87de3a-3a1f-45e3-aa55-8cb737253c16","files":[],"subMetas":{},"userData":{}}

+ 105 - 0
assets/ch/net/WsClient.ts

@@ -0,0 +1,105 @@
+
+/**web scoket 客户端*/
+export class WsClient {
+    public onConnected(evt: any): void { };
+    public onError(err: any): void { };
+    public onClosing(): void { };
+    public onClosed(code: number, reason: string): void { };
+    public onMessage(msg: string | ArrayBuffer): void { };
+    //
+    private _ws: WebSocket | null = null;
+    /** WebSocket对象*/get ws() { return this._ws };
+    /** 连接地址*/
+    private _url: string | null = null;
+    /**证书*/
+    private _ca?: string;
+    constructor(url: string, ca?: string, autoConnect?: boolean) {
+        this._ca = ca;
+        this._url = url;
+        if (autoConnect) this.connect();
+    }
+    /**连接成功时的回调 */
+    private _onConnected(evt: any) {
+        this.onConnected(evt);
+    }
+    /**收到消息时的回调 */
+    private _onMessage(msg: MessageEvent) {
+        this.onMessage(msg.data);
+    }
+    /** 错误处理回调 */
+    private _onError(err: any) {
+        this.onError(err);
+    }
+    /**连接关闭时的回调 */
+    private _onClosed(event: CloseEvent) {
+        this.onClosed(event?.code, event?.reason);
+    }
+    /**
+     * 获取当前连接状态
+     * @returns 是否处于活动状态
+     */
+    get isActive(): boolean {
+        return this._ws?.readyState === WebSocket.OPEN;
+    }
+    /**
+     * 检查是否正在连接
+     * @returns 是否正在连接
+     */
+    private get isConnecting(): boolean {
+        return this._ws?.readyState === WebSocket.CONNECTING;
+    }
+    /**
+     *是否正在关闭
+     */
+    private get isClosing(): boolean {
+        return this._ws?.readyState === WebSocket.CLOSING;
+    }
+    /**是否已经关闭 */
+    private get isClosed(): boolean {
+        return this._ws?.readyState === WebSocket.CLOSED;
+    }
+    /**手动连接*/
+    connect() {
+        if (this.isConnecting) return false;
+        if (this.isActive) return false;
+        const url = this._url;
+        if (!url) return false;
+        try {
+            //eslint-disable-next-line @typescript-eslint/ban-ts-comment
+            //@ts-ignore
+            //eslint-disable-next-line @typescript-eslint/no-unsafe-call
+            const ws = this._ca && url.startsWith('wss://') ? new WebSocket(url, {}, this._ca) : new WebSocket(url)
+            ws.binaryType = 'arraybuffer';
+            ws.onmessage = this._onMessage.bind(this);
+            ws.onopen = this._onConnected.bind(this);
+            ws.onerror = this._onError.bind(this);
+            ws.onclose = this._onClosed.bind(this);
+            this._ws = ws;
+        } catch (error) {
+            this._onError(error instanceof Error ? error.message : 'Unknown error');
+        }
+        return true;
+    }
+    /**
+     * 主动关闭WebSocket连接
+     */
+    close(code?: number, reason?: string): void {
+        if (this.isClosed || this.isClosing) return;
+        if (this._ws?.readyState === WebSocket.OPEN) {
+            try {
+                this.onClosing();
+                this._ws.close(code, reason);
+            } catch (e) {
+                this.onError(e instanceof Error ? e : new Error('Close failed'));
+            }
+        }
+    }
+    /**
+     * 发送数据
+     * @param data 指定格式数据
+     */
+    send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void {
+        this._ws.send(data);
+    }
+}
+

+ 9 - 0
assets/ch/net/WsClient.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "454c6766-6024-4725-85e7-3633e92cde70",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/ch/net/modules.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "6e7a6c1c-a45d-4e10-80ea-98f4d3ede7a7",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 4 - 0
assets/ch/net/modules/msgpack.min.d.ts

@@ -0,0 +1,4 @@
+declare namespace msgpack {
+    export function encode(any: Object): Uint8Array
+    export function decode(res: Uint8Array): Object
+}

+ 9 - 0
assets/ch/net/modules/msgpack.min.d.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "1dae93a8-c33f-4a7a-94f2-80c65d0c3ae3",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

File diff suppressed because it is too large
+ 0 - 0
assets/ch/net/modules/msgpack.min.js


+ 17 - 0
assets/ch/net/modules/msgpack.min.js.meta

@@ -0,0 +1,17 @@
+{
+  "ver": "4.0.24",
+  "importer": "javascript",
+  "imported": true,
+  "uuid": "a7ab22e2-afa2-4b2b-acb5-06dfaf0421ad",
+  "files": [
+    ".js"
+  ],
+  "subMetas": {},
+  "userData": {
+    "loadPluginInEditor": false,
+    "loadPluginInWeb": true,
+    "loadPluginInNative": true,
+    "loadPluginInMiniGame": true,
+    "isPlugin": true
+  }
+}

+ 723 - 0
assets/ch/net/net.ts

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

+ 1 - 0
assets/ch/net/net.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"c337540d-025b-49a0-8359-a36c22ebf236","files":[],"subMetas":{},"userData":{}}

+ 9 - 0
assets/ch/pvp.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "b2b776da-1e87-4d83-8a5b-f0d602602fb4",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 241 - 0
assets/ch/pvp/ch_pvp.ts

@@ -0,0 +1,241 @@
+import ch_util from "../ch_util";
+/** 
+ * pvp扩展玩法 基于 chsdk
+*/
+export type pvp_player = { head: string, nickName: string, uid: number, hid: number, province: string, pvp: number, rank: number };
+export type pvp_own_data<T> = { extends: T, type: 0 | 1 | 2, time: number, own: pvp_player };
+export type pvp_data<T> = { extends: T, other: pvp_player, own: pvp_player, type?: 0 | 1 | 2, status?: 0 | 1 | 2, time?: number, reward?: number, pvpId?: string };
+export type pvp_result = { other: { curRank: number, rank: number, curScore: number, score: number }, own: { curRank: number, rank: number, curScore: number, score: number } };
+class ch_pvp {
+    private readonly _set_url: string = '/user/setPvpSceneData';
+    private readonly _match_url: string = '/user/startPvpMatch';
+    private readonly _finish_url: string = '/user/pvpFinishAction';
+    private readonly _record_url: string = '/user/pvpChallengeRecord';
+    private static _instance: ch_pvp;
+    public static getInstance(): ch_pvp {
+        if (!this._instance) this._instance = new ch_pvp();
+        return this._instance;
+    }
+    /**
+     * 设置自己的 PVP 擂台数据
+     * 该方法用于更新当前用户在 PVP 擂台上的数据。数据将根据传入的 extend 参数进行更新,  
+     * 并返回一个包含操作状态和可能错误信息的 Promise。
+     * @param extend - 一个pvp擂台数据对象,包含要更新的具体数据。
+     * @returns 一个 Promise,解析为一个包含以下属性的对象:  
+     *   - code: 操作的结果状态。0 表示成功,其他值表示不同的错误类型。  
+     *   - err:  可选字符串,指示错误信息(如果有)。     
+     */
+    public async setSceneData<T extends { [key: string]: any }>(extend: T): Promise<{ code: number, err?: string }> {
+        const check = chsdk.check_req_time('setSceneData');
+        if (check) return check;
+        const body = { extends: ch_util.stringify(extend) };
+        const ret = await chsdk.makePostTokenRequest(chsdk.getUrl(this._set_url), body);
+        if (ret.code != chsdk.code.success) {
+            chsdk.log.warn(ret);
+        } else {
+            chsdk.log.log(`PVP擂台数据上报成功`);
+            if (!this._own) {
+                await this.getOwn<T>();
+            } else {
+                this._own.extends = extend;
+            }
+        }
+        return ret;
+    }
+    /**
+    * 开始匹配
+    * @param uid (可选)具体好友的uid,默为空
+    * @returns null  匹配失败
+    * 
+    *          pvp_data<T>
+                   extends PVP 擂台数据
+    *             other   台主信息
+    *             own     自己的信息
+    *                     head:头像, nickName:名字, uid:id, hid:省id, province:省名, pvp:pvp排位分值, rank:pvp排行榜名次  
+    */
+    public async startMatch<T extends { [key: string]: any }>(uid?: number | null): Promise<pvp_data<T> | null> {
+        const check = chsdk.check_req_time('startMatch');
+        if (check) return null;
+        const ret = await chsdk.makePostTokenRequest(chsdk.getUrl(this._match_url), { uid: uid ? uid.toString() : '' });
+        if (ret.code != chsdk.code.success) {
+            chsdk.log.warn(ret);
+            return null;
+        } else {
+            const data = ret.data.data;
+            data.other.uid = Number.parseInt(data.other.uid);
+            data.other.hid = Number.parseInt(data.other.hid);
+            data.other.province = chsdk.provinceCode2Name(data.other.hid);
+            data.own.uid = Number.parseInt(data.own.uid);
+            data.own.hid = Number.parseInt(data.own.hid);
+            data.own.province = chsdk.provinceCode2Name(data.own.hid);
+            return { extends: ch_util.parse(data.extends) as T, other: data.other, own: data.own }
+        }
+    }
+    /**从一条记录开始匹配,用于复仇*/
+    public async startMatchRecord<T extends { [key: string]: any }>(pd: pvp_data<T>): Promise<pvp_data<T> | null> {
+        const check = chsdk.check_req_time('startMatch');
+        if (check) return null;
+        let pp = await this.startMatch<T>(pd.other.uid);
+        if (!pp) return null;
+        pp.extends = pd.extends;
+        pp.pvpId = pd.pvpId;
+        return pp;
+    }
+    /**
+     * 完成pvp上报
+     * @param otherUid 对方uid
+     * @param status 0:挑战失败 1:挑战胜利
+     * @param ext PVP 擂台数据
+     * @param serverData 服务器处理排行数据,订阅消息
+     * ..ownValue 增减自己排位分值
+     * ..otherValue 增减对方排位分值
+     * ..msg 自定义的订阅消息
+     * @param pvpId 复仇id
+     * @returns 对战结果
+     *          other 对方的排名和分数变化
+     *          own  自己的排名和分数变化
+     *               curRank 当前排名(变动后)
+                     curScore 当前分数(变动后)
+                     rank  名数变动
+                     score 分数变动
+     */
+    public async finish<T extends { [key: string]: any }>(otherUid: number, status: 0 | 1, ext: T,
+        serverData: { ownValue?: number, otherValue?: number, msg?: string }
+        , pvpId?: string
+    ): Promise<pvp_result> {
+        const check = chsdk.check_req_time('finish');
+        if (check) return null;
+        const body = { otherUid: otherUid.toString(), status: status, extends: ch_util.stringify(ext), serverData: ch_util.stringify(serverData), pvpId: pvpId };
+        const ret = await chsdk.makePostTokenRequest(chsdk.getUrl(this._finish_url), body);
+        if (ret.code != chsdk.code.success) {
+            chsdk.log.warn(ret);
+            return null;
+        } else {
+            const data = ret.data.data;
+            chsdk.log.log(`pvp结果数据上报成功,结算:`, data);
+            return data;
+        }
+    }
+    private _own: any;
+    /**获取自己的pvp擂台数据*/
+    public async getOwn<T extends { [key: string]: any }>(): Promise<pvp_own_data<T> | null> {
+        if (!this._own) await this.getRecords<T>(3, false);
+        return this._own;
+    }
+    //
+    private _cache_records: any | null = null;
+    private _cache_time: number;
+    private get_cache_records(): any | null {
+        if (chsdk.date.now() - this._cache_time > 6e4) return null;
+        return this._cache_records;
+    }
+    /**
+    * 获取pvp对战记录
+    * @param type  0:全部纪录 1:被挑战的记录信息(默认) 2:挑战别人的记录 3:不需要纪录
+    * @param cache 默认为true,优先缓存拿数据,缓存存在一分钟
+    * @returns own:自己的PVP  擂台数据
+    *          list:交互纪录
+    *            extends PVP  擂台数据
+    *            reward	奖励惩罚排位分值,由于serverData中得来
+    *            type	  0:自己的数据 1:被挑战的记录信息 2:挑战别人的记录
+    *            status   0:挑战失败 1:挑战胜利 2:复仇成功
+    *            time     时间
+    *            pvpId   pvp复仇id
+    *            other   对方信息
+    *            own     自己信息
+    */
+    public async getRecords<T extends { [key: string]: any }>(typeId: 0 | 1 | 2 | 3 = 1, cache: boolean = true): Promise<{
+        own: pvp_own_data<T>,
+        list: pvp_data<T>[],
+    }
+    > {
+        if (cache) {
+            const data = this.get_cache_records();
+            if (data) return data;
+        }
+        const check = chsdk.check_req_time('getRecords');
+        if (check) return null;
+        const ret = await chsdk.makePostTokenRequest(chsdk.getUrl(this._record_url), { typeId: typeId });
+        if (ret.code != chsdk.code.success) {
+            chsdk.log.warn(ret);
+            return null;
+        } else {
+            let data = ret.data.data;
+            if (data.own) {
+                const d = data.own;
+                d.extends = ch_util.parse(d.extends) as T;
+                d.own.uid = Number.parseInt(d.own.uid);
+                d.own.hid = Number.parseInt(d.own.hid);
+                d.own.province = chsdk.provinceCode2Name(d.own.hid);
+            } else {
+                data.own = null;
+            }
+            data.list ||= [];
+            for (let i = 0; i < data.list.length; i++) {
+                const d = data.list[i];
+                d.extends = ch_util.parse(d.extends) as T;
+                d.own.uid = Number.parseInt(d.own.uid);
+                d.own.hid = Number.parseInt(d.own.hid);
+                d.own.province = chsdk.provinceCode2Name(d.own.hid);
+
+                d.other.uid = Number.parseInt(d.other.uid);
+                d.other.hid = Number.parseInt(d.other.hid);
+                d.other.province = chsdk.provinceCode2Name(d.other.hid);
+            }
+            this._own = data.own;
+            this._cache_records = data;
+            this._cache_time = chsdk.date.now();
+            return data;
+        }
+    }
+    /**
+     * 获取pvp排位分值榜单
+     * @returns
+     */
+    public async getRank(): Promise<{
+        list: {
+            head: string;
+            nickName: string;
+            rank: number;
+            score: number;
+            userId: number;
+            [key: string]: any;
+        }[], ower: {
+            head: string;
+            nickName: string;
+            rank: number;
+            score: number;
+            userId: number;
+            [key: string]: any;
+        }
+    }> {
+        const d = await chsdk.loadRankData('pvp', 1, 100, true);
+        return { list: d.data.list, ower: d.data.own };
+    }
+    /**
+     * 分享pvp数据,邀请对战
+     * @param player 玩家自己信息
+     * @param extend (可选)pvp关卡等数据
+     * @param title 分享显示标题
+     * @param imageUrl 分享显示图片
+     */
+    public async sharePvp<T extends { [key: string]: any }>(player: pvp_player, extend?: T, title?: string, imageUrl?: string) {
+        chsdk.shareAppMessage(title, '', imageUrl, ch_util.stringify({ type: 'pvp', player: player, extends: extend }));
+    }
+    /**
+    *获取从好友分享的pvp数据进入游戏的数据
+    */
+    public getSharePvpExtends<T extends { [key: string]: any }>(): { player: pvp_player, extend?: T } {
+        const query = chsdk.getQuery();
+        if (!query) return null;
+        const message = query.message;
+        if (!message) return null;
+        const data = ch_util.parse(message);
+        if (!data) return null;
+        if (data.type != 'pvp') return null;
+        query.message = null;
+        return { player: data.player, extend: data.extends as T };
+    }
+}
+//
+export default ch_pvp.getInstance();

+ 1 - 0
assets/ch/pvp/ch_pvp.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"ee924f9b-e0e1-4454-b62d-1bc8e9662dfb","files":[],"subMetas":{},"userData":{}}

+ 9 - 0
assets/ch/start.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "eecdcdcc-74f7-4167-83af-96831a005042",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 22 - 0
assets/ch/start/ch_net_help.ts

@@ -0,0 +1,22 @@
+import { ch } from "../ch";
+import { ch_net, game_protocol } from "../net/net";
+export default class NetMgr<gd extends game_protocol> {
+    private _net: ch_net<gd> = null!;
+    public get net(): Readonly<ch_net<gd>> {
+        if (!this._net) throw new Error("Network not initialized");
+        return this._net;
+    }
+    constructor() {
+        this._net = ch.get_new_net<gd>();
+    }
+    public init(json: boolean = false) {
+        this._net.init(ch.wsurl,
+            {
+                query: `token=${chsdk.getToken()}`,
+                json: json,
+            });
+    }
+    public connect(): Promise<string | null> {
+        return this._net.connect();
+    }
+}

+ 1 - 0
assets/ch/start/ch_net_help.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"a51de21e-2c9b-420e-a12b-8d1a663f32db","files":[],"subMetas":{},"userData":{}}

+ 22 - 0
assets/ch/start/ch_sdk_comp.ts

@@ -0,0 +1,22 @@
+import { _decorator, Component, Game, game, Node } from 'cc';
+const { ccclass, property } = _decorator;
+
+@ccclass('ch_sdk_comp')
+export class ch_sdk_comp extends Component {
+    protected onLoad(): void {
+        game.on(Game.EVENT_SHOW, () => {
+            chsdk.sdk_event.emit(chsdk.sdk_event.key.show);
+        }, this);
+        game.on(Game.EVENT_HIDE, () => {
+            chsdk.sdk_event.emit(chsdk.sdk_event.key.hide);
+        }, this);
+        /*window.addEventListener("blur", () => {
+            chsdk.sdk_event.emit(chsdk.sdk_event.key.hide);
+        });
+        window.addEventListener("focus", () => {
+            chsdk.sdk_event.emit(chsdk.sdk_event.key.show);
+        });*/
+    }
+}
+
+

+ 9 - 0
assets/ch/start/ch_sdk_comp.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "8aeee3c1-44ef-4120-b113-31b31cbb0a64",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 224 - 0
assets/ch/start/ch_start.ts

@@ -0,0 +1,224 @@
+import { _decorator, CCInteger, CCString, Component, director, Enum, Node } from 'cc';
+import { ch } from '../ch';
+import { loadType } from '../audio/audio';
+import { ch_sdk_comp } from './ch_sdk_comp';
+const { ccclass, property } = _decorator;
+/**包id*/
+export enum pack {
+    id0 = 0,
+    id1 = 1,
+    id2 = 2,
+    id3 = 3,
+    id4 = 4,
+    id5 = 5,
+    id6 = 6,
+    id7 = 7,
+    id8 = 8,
+    id9 = 9,
+    id10 = 10,
+    id11 = 11,
+    id12 = 12,
+    id13 = 13,
+    id14 = 14,
+    id16 = 16,
+    id17 = 17,
+    id18 = 18,
+    id19 = 19,
+    id20 = 20,
+}
+@ccclass('BannerAdConf')
+class BannerAdConf {
+    @property({ displayName: "广告位id" })
+    adUnitId: string = '';
+    @property({ type: CCInteger, displayName: "刷新的间隔(S)", visible: function () { return this.adUnitId; } })
+    adIntervals: number = 120;
+    @property({ type: CCInteger, displayName: "左上角横坐标", visible: function () { return this.adUnitId; } })
+    left: number = 10;
+    @property({ type: CCInteger, displayName: "左上角纵坐标", visible: function () { return this.adUnitId; } })
+    top: number = 80;
+    @property({ type: CCInteger, displayName: "宽度", visible: function () { return this.adUnitId; } })
+    width: number = 500;
+    @property({ type: CCInteger, displayName: "高度", visible: function () { return this.adUnitId; } })
+    height: number = 60;
+}
+@ccclass('PfConf')
+class PfConf {
+    @property({ displayName: "奖励广告位id" })
+    r_adUnitId: string = '';
+    @property({ displayName: "插屏广告位id" })
+    i_adUnitId: string = '';
+    @property({ type: [BannerAdConf], displayName: "Banner广告位" })
+    banners: BannerAdConf[] = [];
+    @property({ type: [CCString], displayName: "订阅id" })
+    subscriptionIds: string[] = [];
+}
+@ccclass('PackConf')
+class PackConf {
+    //
+    @property({ displayName: '游戏名', group: { name: '基本信息', id: 'base', displayOrder: 0 } })
+    gname: string = '';
+    @property({ displayName: '游戏id', group: { name: '基本信息', id: 'base', displayOrder: 0 } })
+    gid: string = '';
+    @property({ type: Enum(chsdk.reportType), displayName: '上报类型', group: { name: '基础配置', id: 'sdk', displayOrder: 0 }, tooltip: "off 不上报 \n ch 使用ch服务器(需要在ch后台配置事件) \n platform 使用微信/抖音平台(需要在平台后台设置事件)\nch__platflorm所有平台自定义事件都上报" })
+    report: chsdk.reportType = chsdk.reportType.off;
+    @property({ type: Enum(chsdk.loglevel), displayName: '日志等级', group: { name: '基础配置', id: 'sdk', displayOrder: 0 } })
+    log: chsdk.loglevel = chsdk.loglevel.ALL;
+    //
+    @property({ type: PfConf, displayName: '抖音相关配置', group: { name: '平台配置', id: 'sdk', displayOrder: 0 } })
+    tt_conf: PfConf = new PfConf();
+    @property({ type: PfConf, displayName: '微信相关配置', group: { name: '平台配置', id: 'sdk', displayOrder: 0 } })
+    wx_conf: PfConf = new PfConf();
+    //
+    @property({ displayName: '是否单机', group: { name: '网络配置', id: 'web', displayOrder: 1 } })
+    is_local: boolean = false;
+    @property({ type: Enum(chsdk.serverType), displayName: '服务器内置地址类型', visible: function () { return !this.is_local }, tooltip: "local 局域网 \n dev 测试服 \n online 正式服" })
+    server: chsdk.serverType = chsdk.serverType.dev;
+    @property({ displayName: '配置本地测试服务器地址(,隔开)', tooltip: ",隔开,前面的为sdk基础地址(带上V1),后面的为sdk上报地址\n为空使用sdk内置地址没", visible: function () { return !this.is_local && this.server == chsdk.serverType.test } })
+    serverIP: string = 'http://192.168.1.120:8787/v1';
+    @property({ displayName: '配置测试服务器地址(,隔开)', tooltip: ",隔开,前面的为sdk基础地址(带上V1),后面的为sdk上报地址\n为空使用sdk内置地址", visible: function () { return !this.is_local && this.server == chsdk.serverType.dev } })
+    serverIP1: string = '';
+    @property({ displayName: '配置正试服务器地址(,隔开)', tooltip: ",隔开,前面的为sdk基础地址(带上V1),后面的为sdk上报地址\n为空使用sdk内置地址", visible: function () { return !this.is_local && this.server == chsdk.serverType.online } })
+    serverIP2: string = '';
+}
+@ccclass('ch_start')
+export class ch_start extends Component {
+    @property({ type: [PackConf], visible: false })
+    private _confs: PackConf[] = [];
+    @property({ visible: false })
+    private _pid: pack = pack.id0;
+    @property({ type: Enum(pack), displayName: '包id', group: { name: '选包', id: 'pack', displayOrder: 0 } })
+    get pid() { return this._pid; }
+    set pid(val) { this._pid = val; }
+    @property({ displayName: '包配置', group: { name: '选包', id: 'pack', displayOrder: 0 } })
+    get conf() { return this._confs[this._pid] ?? new PackConf(); }
+    set conf(val) { this._confs[this._pid] = val; }
+    //联机游戏模块
+    @property({ visible: false })
+    private _use_ch_net: boolean = false;
+    @property({ displayName: '是否使用ch.net模块', tooltip: "联机游戏基于chsdk的登录,如果sdk选单机将无法使用,不同的包共享此配置", visible: function () { return !this.conf.is_local }, group: { name: 'ch.net模块', id: 'net', displayOrder: 1 } })
+    get use_ch_net() { return this._use_ch_net; }
+    set use_ch_net(val) { this._use_ch_net = val; }
+    @property({ displayName: '本地webscoket完整地址', tooltip: "为空使用配置测试地址转换", visible: function () { return !this.conf.is_local && this._use_ch_net && this.conf.server == chsdk.serverType.test; }, group: { name: 'ch.net模块', id: 'net', displayOrder: 1 } })
+    wsIP: string = '';
+    @property({ displayName: '测试服webscoket完整地址', tooltip: "为空使用sdk内置地址转换", visible: function () { return !this.conf.is_local && this._use_ch_net && this.conf.server == chsdk.serverType.dev; }, group: { name: 'ch.net模块', id: 'net', displayOrder: 1 } })
+    wsIP1: string = '';
+    @property({ displayName: '正式服webscoket完整地址', tooltip: "为空使用sdk内置地址转换", visible: function () { return !this.conf.is_local && this._use_ch_net && this.conf.server == chsdk.serverType.online; }, group: { name: 'ch.net模块', id: 'net', displayOrder: 1 } })
+    wsIP2: string = '';
+    //音频模块
+    @property({ visible: false })
+    private _use_ch_audio: boolean = false;
+    @property({ type: Enum(loadType), visible: false })
+    private _ch_audio_type: loadType = loadType.bundle;
+    @property({ displayName: '是否使用ch.audio模块', tooltip: "不同的包共享此配置", group: { name: 'ch.audio模块', id: 'audio', displayOrder: 2 } })
+    get use_ch_audio() { return this._use_ch_audio; }
+    set use_ch_audio(val) { this._use_ch_audio = val; }
+    @property({ type: Enum(loadType), displayName: '音频资源加载模式', group: { name: 'ch.audio模块', id: 'audio', displayOrder: 2 }, visible: function () { return this._use_ch_audio; } })
+    get sount_load_type() { return this._ch_audio_type; }
+    set sount_load_type(val) { this._ch_audio_type = val; }
+    @property({ displayName: '音频资源默认bundle名', group: { name: 'ch.audio模块', id: 'audio', displayOrder: 2 }, visible: function () { return this._use_ch_audio && this._ch_audio_type == loadType.bundle } })
+    private sound_bundle: string = 'res';
+    @property({ displayName: '音频资源远程地址', group: { name: 'ch.audio模块', id: 'audio', displayOrder: 2 }, visible: function () { return this._use_ch_audio && this._ch_audio_type == loadType.remote } })
+    private sound_url: string = '';
+    //-------------------------------------------
+    private _real_server: string = '';
+    private _real_ws: string = '';
+    private _ready: boolean = false;
+    protected onLoad(): void {
+
+    }
+    private ready(): void {
+        if (this.conf.server === chsdk.serverType.test) {
+            this._real_server = this.conf.serverIP;
+            this._real_ws = this.wsIP;
+        } else if (this.conf.server === chsdk.serverType.dev) {
+            this._real_server = this.conf.serverIP1;
+            this._real_ws = this.wsIP1;
+        } else if (this.conf.server === chsdk.serverType.online) {
+            this._real_server = this.conf.serverIP2;
+            this._real_ws = this.wsIP2;
+        }
+        console.log('包Id:' + this.pid,
+            '游戏名:' + this.conf.gname,
+            '游戏Id:' + this.conf.gid,
+            '单机:' + this.conf.is_local,
+            '服务器:' + chsdk.serverType[this.conf.server] + '-- ' + this._real_server + '-- ' + this._real_ws
+        );
+    }
+    private getPfConf(conf: PfConf): chsdk.pf_conf {
+        const c = {
+            adUnitId: conf.r_adUnitId, multiton: false,
+            inster_unitId: conf.i_adUnitId,
+            tmplIds: conf.subscriptionIds.length > 0 ? conf.subscriptionIds : null,
+            bannerAds: []
+        }
+        for (let i = 0; i < conf.banners.length; i++) {
+            const banner = conf.banners[i];
+            c.bannerAds.push({
+                adUnitId: banner.adUnitId,
+                adIntervals: banner.adIntervals,
+                style: {
+                    left: banner.left,
+                    top: banner.top,
+                    width: banner.width,
+                    height: banner.height,
+                }
+            });
+        }
+        return c;
+    }
+    /**初始化*/
+    async init(): Promise<boolean> {
+        if (!this._ready) {
+            this._ready = true;
+            this.ready();
+        }
+        chsdk.setConf(chsdk.pf.tt, this.getPfConf(this.conf.tt_conf));
+        chsdk.setConf(chsdk.pf.wx, this.getPfConf(this.conf.wx_conf));
+        let ret = await chsdk.init_inside(this.conf.gid, this.conf.log, this._real_server ? this._real_server : this.conf.server, this.conf.is_local, this.conf.report);
+        if (ret.code == chsdk.code.success) {
+            if (this.use_ch_audio) {
+                ch.audio.init(this.sount_load_type, this.sound_bundle, this.sound_url);
+            }
+            if (this.use_ch_net) {
+                if (!this._real_ws) {
+                    const base = chsdk.getUrl('/handle');
+                    if (this.conf.server === chsdk.serverType.test) {
+                        this._real_ws = base;
+                    } else {
+                        const url = base.replace(base.split(':')[0], 'wss');
+                        this._real_ws = url;
+                    }
+                }
+                ch.wsurl = this._real_ws;
+            }
+            if (chsdk.get_pf() === chsdk.pf.web) {
+                const sdk = new Node();
+                sdk.name = '__ch_sdk__';
+                sdk.addComponent(ch_sdk_comp);
+                director.getScene().addChild(sdk);
+                director.addPersistRootNode(sdk);
+            }
+            return true;
+        } else {
+            chsdk.log.error(ret.err);
+            return false;
+        }
+    }
+    /**初化始并在遇到问题时以单机模式进入
+    * @param try_count 重试次数
+    * @param try_interval 重试间隔时间(MS)
+    */
+    async try_init(try_count: number = 5, try_interval: number = 2000): Promise<void> {
+        let ret = await this.init();
+        if (ret) return;
+        try_count--;
+        if (try_count < 0) {
+            this.conf.is_local = true;
+            await this.init();
+            return;
+        } else {
+            await new Promise(resolve => setTimeout(resolve, try_interval));
+            return this.try_init(try_count, try_interval);
+        }
+    }
+}

+ 1 - 0
assets/ch/start/ch_start.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"f76017d5-74f5-46b5-baf8-195b19fafb2f","files":[],"subMetas":{},"userData":{}}

+ 9 - 0
assets/ch/table.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "1e19d927-be41-4104-8399-46b27f7f157b",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 235 - 0
assets/ch/table/IndexedDB.ts

@@ -0,0 +1,235 @@
+export default class IndexedDB {
+    private dbName: string;
+    private storeName: string;
+    private dbVersion: number = 1;
+    constructor(dbName: string, storeName: string, version?: number) {
+        this.dbName = dbName;
+        this.storeName = storeName;
+        if (version) this.dbVersion = version;
+    }
+    //打开数据库 
+    private openDatabase(retryCount: number = 3, delay: number = 300): Promise<IDBDatabase> {
+        return new Promise<IDBDatabase>((resolve, reject) => {
+            const tryOpen = (attempt: number) => {
+                const request = window.indexedDB.open(this.dbName, this.dbVersion);
+                request.onupgradeneeded = (event) => {
+                    const target = event.target as IDBOpenDBRequest;
+                    const db = target.result;
+                    if (!db.objectStoreNames.contains(this.storeName)) {
+                        const store = db.createObjectStore(this.storeName, { keyPath: 'key' });
+                        store.createIndex('valueIndex', 'value', { unique: false });
+                    }
+                };
+                request.onsuccess = (event) => {
+                    const target = event.target as IDBOpenDBRequest;
+                    resolve(target.result);
+                };
+                request.onerror = (event) => {
+                    const target = event.target as IDBOpenDBRequest;
+                    if (attempt < retryCount) {
+                        console.warn(`数据库打开失败,第${attempt}次重试...`);
+                        setTimeout(() => tryOpen(attempt + 1), delay);
+                    } else {
+                        reject(new Error(`数据库打开失败(重试${retryCount}次): ${target.error}`));
+                    }
+                };
+            };
+            tryOpen(1);
+        });
+    }
+    /**
+     * 设置数据 (使用字符串key)
+    */
+    // 在setData等方法中添加事务错误处理
+    setData(key: string, value: any): Promise<void> {
+        return new Promise<void>((resolve, reject) => {
+            this.openDatabase()
+                .then((db) => {
+                    const transaction = db.transaction([this.storeName], 'readwrite');
+                    transaction.onerror = () => reject(new Error('Transaction failed: ' + transaction.error));
+                    const objectStore = transaction.objectStore(this.storeName);
+                    const request = objectStore.put({ key, value });
+                    request.onsuccess = () => resolve();
+                    request.onerror = () => reject(new Error('Failed to set data: ' + request.error));
+                })
+                .catch(reject);
+        });
+    }
+    /**
+     * 批量设置数据
+     * @param items 键值对数组,格式为 {key: string, value: any}[]
+     */
+    setBatchData(items: { key: string, value: any }[]): Promise<void> {
+        return new Promise<void>((resolve, reject) => {
+            this.openDatabase()
+                .then((db) => {
+                    const transaction = db.transaction([this.storeName], 'readwrite');
+                    const objectStore = transaction.objectStore(this.storeName);
+
+                    // 批量添加/更新数据
+                    items.forEach(item => {
+                        objectStore.put({ key: item.key, value: item.value });
+                    });
+
+                    transaction.oncomplete = () => resolve();
+                    transaction.onerror = () =>
+                        reject(new Error('Failed to set batch data: ' + transaction.error));
+                })
+                .catch(reject);
+        });
+    }
+    /**
+     * 获取数据 (使用字符串key)
+    */
+    getData(key: string): Promise<any> {
+        return new Promise<any>((resolve, reject) => {
+            this.openDatabase()
+                .then((db) => {
+                    const transaction = db.transaction([this.storeName], 'readonly');
+                    const objectStore = transaction.objectStore(this.storeName);
+                    const request = objectStore.get(key);
+
+                    request.onsuccess = () => resolve(request.result?.value);
+                    request.onerror = () => reject(new Error('Failed to get data: ' + request.error));
+                })
+                .catch(reject);
+        });
+    }
+    /**
+     *  删除数据 (使用字符串key)
+     * @param key   
+     * @returns 
+     */
+    deleteData(key: string): Promise<void> {
+        return new Promise<void>((resolve, reject) => {
+            this.openDatabase()
+                .then((db) => {
+                    const transaction = db.transaction([this.storeName], 'readwrite');
+                    const objectStore = transaction.objectStore(this.storeName);
+                    const request = objectStore.delete(key);
+                    request.onsuccess = () => resolve();
+                    request.onerror = () => reject(new Error('Failed to delete data: ' + request.error));
+                })
+                .catch(reject);
+        });
+    }
+    /**
+     *清空表数据
+     * @returns 
+     */
+    clearAll(): Promise<void> {
+        return new Promise<void>((resolve, reject) => {
+            this.openDatabase()
+                .then((db) => {
+                    const transaction = db.transaction([this.storeName], 'readwrite');
+                    const objectStore = transaction.objectStore(this.storeName);
+                    const request = objectStore.clear();
+
+                    request.onsuccess = () => resolve();
+                    request.onerror = () => reject(new Error('Failed to clear all data: ' + request.error));
+                })
+                .catch(reject);
+        });
+    }
+    /**
+     * 按索引查询数据
+     * @param indexName 索引名称
+     * @param value 索引值
+     */
+    getDataByIndex(indexName: string, value: any): Promise<any[]> {
+        return new Promise<any[]>((resolve, reject) => {
+            this.openDatabase()
+                .then((db) => {
+                    const transaction = db.transaction([this.storeName], 'readonly');
+                    const objectStore = transaction.objectStore(this.storeName);
+                    const index = objectStore.index(indexName);
+                    const request = index.getAll(value);
+
+                    request.onsuccess = () => resolve(request.result);
+                    request.onerror = () => reject(new Error('Failed to get data by index: ' + request.error));
+                })
+                .catch(reject);
+        });
+    }
+    /**
+     * 获取所有数据
+     */
+    getAllData(): Promise<any[]> {
+        return new Promise<any[]>((resolve, reject) => {
+            this.openDatabase()
+                .then((db) => {
+                    const transaction = db.transaction([this.storeName], 'readonly');
+                    const objectStore = transaction.objectStore(this.storeName);
+                    const request = objectStore.getAll();
+
+                    request.onsuccess = () => resolve(request.result);
+                    request.onerror = () => reject(new Error('Failed to get all data: ' + request.error));
+                })
+                .catch(reject);
+        });
+    }
+
+    /**
+     * 获取数据总数
+     */
+    count(): Promise<number> {
+        return new Promise<number>((resolve, reject) => {
+            this.openDatabase()
+                .then((db) => {
+                    const transaction = db.transaction([this.storeName], 'readonly');
+                    const objectStore = transaction.objectStore(this.storeName);
+                    const request = objectStore.count();
+
+                    request.onsuccess = () => resolve(request.result);
+                    request.onerror = () => reject(new Error('Count error: ' + request.error));
+                })
+                .catch(reject);
+        });
+    }
+    /**
+     * 使用游标遍历所有数据
+     * @param callback 处理每条数据的回调函数
+     */
+    forEach(callback: (value: any, key: string) => boolean | void): Promise<void> {
+        return new Promise<void>((resolve, reject) => {
+            this.openDatabase()
+                .then((db) => {
+                    const transaction = db.transaction([this.storeName], 'readonly');
+                    const objectStore = transaction.objectStore(this.storeName);
+                    const request = objectStore.openCursor();
+
+                    request.onsuccess = (event) => {
+                        const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
+                        if (cursor) {
+                            const shouldContinue = callback(cursor.value.value, cursor.value.key);
+                            if (shouldContinue !== false) {
+                                cursor.continue();
+                            }
+                        } else {
+                            resolve();
+                        }
+                    };
+                    request.onerror = () => reject(new Error('Cursor error: ' + request.error));
+                })
+                .catch(reject);
+        });
+    }
+}
+/*
+// 使用示例
+const storage = new IndexedDB('myDatabase', 'myStore');
+// 清空表数据
+storage.clearAll()
+    .then(() => console.log('All data cleared successfully'))
+    .catch(err => console.error('Error clearing data:', err));
+
+// 保存数据
+storage.setData('user1', { name: 'John', age: 30 })
+    .then(() => console.log('Data saved successfully'))
+    .catch(err => console.error('Error saving data:', err));
+
+// 获取数据
+storage.getData('user1')
+    .then(data => console.log('Retrieved data:', data))
+    .catch(err => console.error('Error retrieving data:', err));
+*/

+ 9 - 0
assets/ch/table/IndexedDB.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "469c934e-20d4-4b09-8a3d-81c87b5d7e8b",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 155 - 0
assets/ch/table/TableUtil.ts

@@ -0,0 +1,155 @@
+import { _decorator, AssetManager, assetManager, JsonAsset } from 'cc';
+import IndexedDB from './IndexedDB';
+type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P] };
+const ids: Map<string, Readonly<number>[]> = new Map();
+const tables: Map<string, ReadonlyMap<number, DeepReadonly<any>>> = new Map();
+export default class TableUtil {
+    private static db: IndexedDB;
+    //
+    public static local_ver = 0;
+    //
+    private static _setData(name: string, json: any) {
+        let idlist = ids.get(name);
+        if (!idlist) {
+            idlist = [];
+            ids.set(name, idlist);
+        } else {
+            idlist.length = 0;
+        }
+        let map = new Map<number, any>();
+        tables.set(name, map);
+        for (let v in json) {
+            const id = parseInt(v);
+            idlist.push(id);
+            const table = json[v];
+            table.id = id;
+            map.set(id, table);
+        }
+    }
+    /**
+     * 获取一个表集所有id
+     * @param name 表名
+     * @returns 
+     */
+    public static getIds(name: string): Readonly<number[]> | null {
+        if (ids.has(name)) {
+            return ids.get(name) as Readonly<number[]>;
+        }
+        console.error("没有配置表:" + name);
+        return null;
+    }
+    /**是否有配置*/
+    public static has(name: string): boolean {
+        return ids.has(name);
+    }
+    /**
+     * 获取一个表集所有配置
+     * @param name 表名
+     * @returns 
+     */
+    public static getTables(name: string): DeepReadonly<any>[] | null {
+        if (tables.has(name)) {
+            return Array.from(tables.get(name).values());
+        }
+        console.error("没有配置表:" + name);
+        return null;
+    }
+    /**
+     * 获取一个表集
+     * @param name 表名
+     * @param id 表id
+     * @returns
+    */
+    public static getTable(name: string, id: number): DeepReadonly<any> | null {
+        if (tables.has(name)) {
+            return tables.get(name).get(id);
+        } else {
+            console.error("没有配置表:" + name);
+            return null;
+        }
+    }
+    /**加载远程配置
+     * @param basepath 远程目录路径
+     * @param key 解密key
+    */
+    public static async preloadRemoteConfig(
+        gid: string,
+        basepath: string,
+        key: string
+    ): Promise<void> {
+        try {
+            if (!this.db) this.db = new IndexedDB(gid, 'table');
+            //1.加载版本文件
+            const asset = await new Promise<JsonAsset>((resolve, reject) => {
+                const fullPath = `${basepath}/ver.json?t=${new Date().getTime()}`;
+                assetManager.loadRemote(fullPath, (err: Error | null, asset: JsonAsset) => {
+                    err ? reject(err) : resolve(asset);
+                });
+            });
+            const { ver, tables, isMerge } = asset.json;
+            let local_ver: number = await this.db.getData(chsdk.md5HashStr('ver'));
+            chsdk.log.log(`本地版本:${local_ver},远程版本版本: ${ver}, ${tables},是否合并: ${isMerge}`);
+            this.local_ver = ver;
+            if (local_ver && local_ver === Number.parseInt(ver)) {
+                for (let i = 0; i < tables.length; i++) {
+                    const name = tables[i];
+                    const data: string = await this.db.getData(chsdk.md5HashStr(name));
+                    if (!data) {
+                        local_ver = 0;
+                        break;
+                    }
+                    this._setData(name, JSON.parse(chsdk.base64_xo_decode(data, name)));
+                }
+                if (local_ver) {
+                    chsdk.log.log(`本地配置加载完成! 版本: ${local_ver}, 共加载 ${tables.length} 个配置文件`);
+                    return;
+                }
+            }
+            //远程更新
+            await this.db.clearAll();
+            if (isMerge) {
+                //2.加载合并文件
+                const asset = await new Promise<JsonAsset>((resolve, reject) => {
+                    const fullPath = `${basepath}/merge.json?t=${new Date().getTime()}`;
+                    assetManager.loadRemote(fullPath, (err: Error | null, asset: JsonAsset) => {
+                        err ? reject(err) : resolve(asset);
+                    });
+                });
+                const jsonData = asset.json;
+                tables.map(name => {
+                    const dd = JSON.parse(jsonData[name]);
+                    const ddd = dd.encrypt ? JSON.parse(chsdk.base64_xo_decode(dd.encrypt, key)) : dd;
+                    this._setData(name, ddd);
+                    console.log(chsdk.base64_xo_encode(ddd, key));
+                    this.db.setData(chsdk.md5HashStr(name), chsdk.base64_xo_encode(JSON.stringify(ddd), name));
+                });
+                chsdk.log.log(`远程配置加载完成! 加载merge配置文件`);
+            } else {
+                // 2. 并行加载所有配置文件
+                const loadPromises = tables.map(name =>
+                    new Promise<void>((resolve, reject) => {
+                        const fullPath = `${basepath}/${name}.json?t=${new Date().getTime()}`;
+                        assetManager.loadRemote(fullPath, (err: Error | null, asset: JsonAsset) => {
+                            if (err) {
+                                chsdk.log.error(`配置文件加载失败: ${name}`, err);
+                                reject(err);
+                            } else {
+                                const jsonData = asset.json.encrypt ? JSON.parse(chsdk.base64_xo_decode(asset.json.encrypt, key)) : asset.json;
+                                this._setData(name, jsonData);
+                                this.db.setData(chsdk.md5HashStr(name), chsdk.base64_xo_encode(JSON.stringify(jsonData), name));
+                                resolve();
+                            }
+                        });
+                    })
+                );
+                await Promise.all(loadPromises);
+                chsdk.log.log(`远程配置加载完成! 共加载 ${tables.length} 个配置文件`);
+            }
+            this.db.setData(chsdk.md5HashStr('ver'), ver);
+        } catch (err) {
+            chsdk.log.error(`远程配置加载失败:`, err);
+            throw err;
+        }
+    }
+}
+

+ 1 - 0
assets/ch/table/TableUtil.ts.meta

@@ -0,0 +1 @@
+{"ver":"4.0.24","importer":"typescript","imported":true,"uuid":"1ad8d214-beb7-4f38-9a80-869069fec5b5","files":[],"subMetas":{},"userData":{}}

+ 9 - 0
assets/core_tgx.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "e0c97157-e090-42c0-957d-f03e772c1b70",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/AnnotationUtil.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "f6a8525a-4c96-490c-b181-f93fc409bc08",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/AnnotationUtil/Singleton.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "2801b5bc-1678-4912-abbd-c3d6a38819a9",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 115 - 0
assets/core_tgx/AnnotationUtil/Singleton/SingletonAnnotation.ts

@@ -0,0 +1,115 @@
+import { Constructor } from "cc"
+
+// 打包之后有问题不支持名称注入
+export class SingletonAnnotation {
+
+    private static TypeMap = new Map
+    private static initNameMap = new Map<string, { target: any, consParam?: any }>()
+    private static initTypeMap = new Map<any, { target: any, consParam?: any }>()
+    static setSingleton(_obj?: { type?: any, consParam?: any } | string) {
+      
+        return function (target: any) {
+            // 首字母小写注入
+         
+            let consParam = _obj['consParam'] || {}
+
+            SingletonAnnotation.initTypeMap.set(target, { target: target, consParam: consParam });
+            if (typeof _obj == "string") {
+                SingletonAnnotation.initNameMap.set(_obj, { target: target, consParam: consParam });
+            }
+        }
+    }
+
+    private static create(target: any, _obj?: { target?: any, consParam?: any }) {
+
+
+        let consParam = _obj.consParam
+        const instance = new target();
+        Object.keys(consParam).forEach((key) => {
+            if (typeof key == "string") {
+                if (instance.hasOwnProperty(key)) {
+                    instance[key] = consParam[key]
+                }
+            }
+        })
+
+        if (SingletonAnnotation.TypeMap.has(target)) {
+            console.error("有重复类型被注入")
+        }
+        SingletonAnnotation.TypeMap.set(target, instance);
+        return instance
+    }
+
+    static getSingleton(name?: string | Constructor) {
+       
+        let res: PropertyDecorator = function (target, prop: string, desc?) {
+            // 首字母小写注入
+            let value: any
+            // 是否初始化
+            let init;
+            return {
+                get() {
+                    if (!init) {
+                        init = true
+                        if (!name) {
+                            name = prop
+                        }
+                        value = SingletonAnnotation.getValue(name)
+                    }
+                    return value
+                },
+                set(v) { value = v },
+            }
+        }
+        return res
+
+    }
+
+    static getValue(name?: any) {
+      
+        if (!name) {
+            return undefined;
+
+        }
+        let value
+        if (typeof name != "string") {
+
+            value = this.initObjByType(name);
+
+        } else {
+            value = this.initObjByName(name);
+        }
+
+        return value
+    }
+
+    private static initObjByName(name: string) {
+
+        let v
+        let param = SingletonAnnotation.initNameMap.get(name)
+        if (param) {
+
+            v = this.initObjByType(param.target)
+        }
+
+
+        return v
+    }
+
+    private static initObjByType(name: Constructor) {
+        let v = SingletonAnnotation.TypeMap.get(name)
+        if (!v) {
+            let param = SingletonAnnotation.initTypeMap.get(name)
+            if (param) {
+                v = SingletonAnnotation.create(param.target, param)
+            }
+
+        }
+        return v
+    }
+
+    static logInfo() {
+        console.log("TypeMap", SingletonAnnotation.TypeMap)
+        console.log("initTypeMap", SingletonAnnotation.initTypeMap)
+    }
+}

+ 9 - 0
assets/core_tgx/AnnotationUtil/Singleton/SingletonAnnotation.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "bfc15a11-b048-4262-83c5-328f42c40bac",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/CachePool.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "e104065b-e9c7-4d80-8047-42cb57557157",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 132 - 0
assets/core_tgx/CachePool/CachePool.ts

@@ -0,0 +1,132 @@
+
+import {  instantiate,  Node, NodePool, Prefab, Vec3 } from "cc"
+
+
+/**
+ * @Description: resources manager and pool manager
+ * @return {*}
+ */
+export class CachePool {
+ 
+    private _dictPool: { [key: string]: NodePool } = {}
+    private _dictPrefab: { [key: string]: Prefab } = {}
+    private _loadStemp = null;
+    private _loadTime = 0;
+    private _totalTime = 0
+    public loadingRate = 0;
+    public debug = false;
+    
+
+    private static _inst: CachePool;
+    public static get inst(): CachePool {
+        if (this._inst == null) {
+            this._inst = new CachePool();
+        }
+        return this._inst;
+    }
+
+    /**
+     * @description: get the node from the pool
+     * @param {Prefab} prefab
+     * @param {Node} parent
+     * @param {Vec3} pos
+     * @return {*}
+     */
+    // 节点池 NodePool ,用于处理频繁创建和销毁的物体
+    public getNode (prefab: Prefab | string, parent?: Node, pos?: Vec3): Node {
+        let tempPre;
+        let name;
+        if (typeof prefab === 'string') {
+            tempPre = this._dictPrefab[prefab];
+            name = prefab;
+            if (!tempPre) {
+                // console.log("Pool invalid prefab name = ", name);
+                return null;
+            }
+        }
+        else {
+            tempPre = prefab;
+            name = prefab.data.name;
+        }
+        let node = null;
+        if (this._dictPool.hasOwnProperty(name)) {
+            //own this pool
+            let pool = this._dictPool[name];
+            if (pool.size() > 0) {
+                node = pool.get();
+            } else {
+                node = instantiate(tempPre);
+            }
+        } else {
+            //create new pool
+            let pool = new NodePool();
+            this._dictPool[name] = pool;
+            node = instantiate(tempPre);
+        }
+
+        if (parent) {
+            node.parent = parent;
+            node.active = true;
+            if (pos) node.position = pos;
+        }
+
+        return node;
+    }
+
+
+    /**
+     * @description: put the node into the pool
+     * @param {Node} node
+     * @param {*} isActive
+     * @return {*}
+     */
+    public putNode (node: Node | null, isActive = false) {
+        if (!node) {
+            return;
+        }
+        let name = node.name;
+        let pool = null;
+        // node.active = isActive
+        if (this._dictPool.hasOwnProperty(name)) {
+            pool = this._dictPool[name];
+        } else {
+            pool = new NodePool();
+            this._dictPool[name] = pool;
+        }
+        pool.put(node);
+    }
+
+    /**
+     * @description: clear the pool based on name
+     * @param {string} name
+     * @return {*}
+     */
+    public clearPool (name: string) {
+        if (this._dictPool.hasOwnProperty(name)) {
+            let pool = this._dictPool[name];
+            pool.clear();
+        }
+    }
+
+    public setPrefab (name: string, prefab: Prefab): void {
+        if (!this._dictPrefab[name]) {
+            this._dictPrefab[name] = prefab;
+        }
+    }
+
+    public clearDict () {
+        this._dictPrefab = {};
+    }
+
+    printTimer (name: string = "", end = false) {
+        this._loadTime = Date.now() - this._loadStemp;
+        this._loadStemp = Date.now();
+        this._totalTime += this._loadTime
+        console.log(name + ",load time===", this._loadTime, "ms")
+        if (end) {
+            console.log("Load finish, total time===", this._totalTime, "ms")
+        }
+
+    } 
+}
+

+ 9 - 0
assets/core_tgx/CachePool/CachePool.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "399a8cd8-a4a8-4863-9af0-0d56528e1373",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/CachePool/NodePool.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.23",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "81b3bbc2-29dd-4ae2-b26f-5a10d3ecf56a",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/UIAnnotation.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "8fa7cd65-75f8-4c12-9bfd-19816416648d",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 73 - 0
assets/core_tgx/UIAnnotation/UIControllerAnnotation.ts

@@ -0,0 +1,73 @@
+import { _decorator,Constructor, Node } from 'cc';
+import { UIController } from '../easy_ui_framework/UIController';
+
+// 打包之后有问题不支持名称注入
+export class UIControllerAnnotation {
+
+   
+    private static initMap = new Map<any, { bundleName?: any, path?: any,layer?:number }>()
+    private static initElement = new Map<any, Map<string,{type:any,prefix?:string,path?:string}>>()
+
+    static UI(param: { bundleName?: any, path: any } | string) {
+        return function (target: Constructor) {
+            // 首字母小写注入
+            if(typeof param == 'string'){
+                UIControllerAnnotation.initMap.set(target,{path:param})
+            }else{
+                UIControllerAnnotation.initMap.set(target,param)
+            }
+            
+
+        }
+    }
+    static getElement(target:Constructor){
+        // 这里还要找到父类的结果
+        if(!target){
+            return null
+        }
+        if(target == UIController){
+            return UIControllerAnnotation.initElement.get(target) 
+        }
+        let res = new Map
+        let parent = Object.getPrototypeOf(target)
+        let parentMap = this.getElement(parent)
+        let myMap = UIControllerAnnotation.initElement.get(target) 
+        if(parentMap){
+            parentMap.forEach((vlaue,key)=>res.set(key,vlaue))
+        }
+        if(myMap){
+            myMap.forEach((vlaue,key)=>res.set(key,vlaue))
+        }
+        return res
+  }
+
+    static getUI(ui:Constructor){
+         return UIControllerAnnotation.initMap.get(ui) 
+   }
+   static element(type: Constructor|{type:any,prefix?:string,path?:string} = Node) {
+       
+    let res: PropertyDecorator = function (target:Object, prop: string) {
+        // 首字母小写注入
+        let map =UIControllerAnnotation.initElement.get(target.constructor)
+        if(!map){
+            map= new Map
+            UIControllerAnnotation.initElement.set(target.constructor,map)
+        }
+        if(map.get(prop)){
+            console.warn("存在同名注入,请检查代码,prop==>",prop)
+        }
+        if(typeof type == 'object'){
+            map.set(prop,type)
+        }else{
+            map.set(prop,{type:type,prefix:'#'})
+        }
+        
+    }
+    return res
+
+}
+  
+
+
+}
+

+ 9 - 0
assets/core_tgx/UIAnnotation/UIControllerAnnotation.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "2e44356f-0581-4d55-8909-c0b8ab53a7e3",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/core_tgx/base.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "17eb01fe-cd06-4b89-9850-49529a65234a",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 117 - 0
assets/core_tgx/base/AudioMgr.ts

@@ -0,0 +1,117 @@
+//AudioMgr.ts
+import { Node, AudioSource, AudioClip, resources, director, assetManager } from 'cc';
+/**
+ * @en
+ * this is a sington class for audio play, can be easily called from anywhere in you project.
+ * @zh
+ * 这是一个用于播放音频的单件类,可以很方便地在项目的任何地方调用。
+ */ 
+export class AudioMgr {
+
+    private static _inst: AudioMgr;
+    public static get inst(): AudioMgr {
+        if (this._inst == null) {
+            this._inst = new AudioMgr();
+        }
+        return this._inst;
+    }
+
+    private _audioSource: AudioSource;
+    constructor() {
+        //@en create a node as audioMgr
+        //@zh 创建一个节点作为 audioMgr
+        let audioMgr = new Node();
+        audioMgr.name = '__audioMgr__';
+
+        //@en add to the scene.
+        //@zh 添加节点到场景
+        director.getScene().addChild(audioMgr);
+
+        //@en make it as a persistent node, so it won't be destroied when scene change.
+        //@zh 标记为常驻节点,这样场景切换的时候就不会被销毁了
+        director.addPersistRootNode(audioMgr);
+
+        //@en add AudioSource componrnt to play audios.
+        //@zh 添加 AudioSource 组件,用于播放音频。
+        this._audioSource = audioMgr.addComponent(AudioSource);
+    }
+
+    public get audioSource() {
+        return this._audioSource;
+    }
+
+    /**
+     * @en
+     * play short audio, such as strikes,explosions
+     * @zh
+     * 播放短音频,比如 打击音效,爆炸音效等
+     * @param sound clip or url for the audio
+     * @param volume 
+     */
+    playOneShot(sound: AudioClip | string, volume: number = 1.0, bundleName:string = 'resources') {
+        if (sound instanceof AudioClip) {
+            this._audioSource.playOneShot(sound, volume);
+        }
+        else {
+            let bundle = assetManager.getBundle(bundleName);
+            bundle.load(sound, (err, clip: AudioClip) => {
+                if (err) {
+                    console.log(err);
+                }
+                else {
+                    this._audioSource.playOneShot(clip, volume);
+                }
+            });
+        }
+    }
+
+    /**
+     * @en
+     * play long audio, such as the bg music
+     * @zh
+     * 播放长音频,比如 背景音乐
+     * @param sound clip or url for the sound
+     * @param volume 
+     */
+    play(sound: AudioClip | string, volume: number = 1.0, bundleName:string = 'resources') {
+        if (sound instanceof AudioClip) {
+            this._audioSource.clip = sound;
+            this._audioSource.play();
+            this.audioSource.volume = volume;
+        }
+        else {
+            let bundle = assetManager.getBundle(bundleName);
+            bundle.load(sound, (err, clip: AudioClip) => {
+                if (err) {
+                    console.log(err);
+                }
+                else {
+                    this._audioSource.clip = clip;
+                    this._audioSource.play();
+                    this.audioSource.volume = volume;
+                }
+            });
+        }
+    }
+
+    /**
+     * stop the audio play
+     */
+    stop() {
+        this._audioSource.stop();
+    }
+
+    /**
+     * pause the audio play
+     */
+    pause() {
+        this._audioSource.pause();
+    }
+
+    /**
+     * resume the audio play
+     */
+    resume(){
+        this._audioSource.play();
+    }
+}

+ 9 - 0
assets/core_tgx/base/AudioMgr.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "2515e3e6-fd4c-4cfa-b33a-0b66c394d67e",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 68 - 0
assets/core_tgx/base/EventDispatcher.ts

@@ -0,0 +1,68 @@
+/**
+ * @en the classes inherit from class:EventDispatcher will have the ability to dispatch events.
+ * @zh 事件派发器,继承自EventDispatcher的类将拥有事件派发能力
+ * 
+ *  */
+export class EventDispatcher {
+    private _handlersMap:any = {};
+    public on(event: string|number, cb: Function, thisArg?: any, args?: [], once?: boolean) {
+        if((!event && event!=0) || !cb){
+            return;
+        }
+        
+        let handlers = this._handlersMap[event];
+        if (!handlers) {
+            handlers = this._handlersMap[event] = [];
+        }
+
+        handlers.push({
+            event: event,
+            cb: cb,
+            thisArg: thisArg,
+            once: once,
+            args: args
+        });
+    }
+
+    public once(event: string|number, cb: Function, thisArg: any, args: []) {
+        this.on(event, cb, thisArg, args, true);
+    }
+
+    public off(event: string|number, cb: Function, thisArg?: any, once?: boolean) {
+        let handlers = this._handlersMap[event];
+        if (!handlers) {
+            return;
+        }
+        for (let i = 0; i < handlers.length; ++i) {
+            let h = handlers[i];
+            if (h.cb == cb && h.thisArg == thisArg && h.once == once) {
+                handlers.splice(i, 1);
+                return;
+            }
+        }
+    }
+
+    public clearAll(event?: string|number) {
+        if (event||event == 0) {
+            delete this._handlersMap[event];
+        }
+        else {
+            this._handlersMap = {};
+        }
+    }
+
+    public emit(event: string|number, arg0?: any, arg1?: any, arg2?: any, arg3?: any, arg4?: any) {
+
+        let handlers = this._handlersMap[event];
+        if (!handlers || !handlers.length) {
+            return;
+        }
+        let args = [arg0, arg1, arg2, arg3, arg4];
+        for (let i = 0; i < handlers.length; ++i) {
+            let h = handlers[i];
+            if (h.event == event) {
+                h.cb.apply(h.thisArg, args);
+            }
+        }
+    }
+}

+ 9 - 0
assets/core_tgx/base/EventDispatcher.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "1abc6d13-cdbc-403a-8c03-a98f7eb02768",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 41 - 0
assets/core_tgx/base/InputMgr.ts

@@ -0,0 +1,41 @@
+export class InputMgr{
+    private STATE_NORMAL = 1;
+    private STATE_KEEP = 2;
+
+    private static _inst:InputMgr = null;
+    private _flags = {};
+    private _flagsMeta = {};
+    public static get inst():InputMgr{
+        if(!this._inst){
+            this._inst = new InputMgr();
+        }
+        return this._inst;
+    }
+
+    public setFlag(flag:string,keep?:boolean,meta?:any){
+        this._flags[flag] = keep? this.STATE_KEEP:this.STATE_NORMAL;
+        if(meta != null){
+            this._flagsMeta[flag] = meta;
+        }
+    }
+
+    public removeFlag(flag:string){
+        delete this._flags[flag];
+    }
+
+    public hasFlag(flag:string):boolean{
+        return !!this._flags[flag];
+    }
+
+    public getMetaByFlag(flag:string):any{
+        return this._flagsMeta[flag];
+    }
+
+    public update(){
+        for(let k in this._flags){
+            if(this._flags[k] != this.STATE_KEEP){
+                this._flags[k] = 0;
+            }
+        }
+    }
+}

+ 13 - 0
assets/core_tgx/base/InputMgr.ts.meta

@@ -0,0 +1,13 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "36577b7d-5905-49d1-809f-5f631be455d7",
+  "files": [],
+  "subMetas": {},
+  "userData": {
+    "moduleId": "project:///assets/scripts/qfw/InputMgr.js",
+    "importerSettings": 0,
+    "simulateGlobals": []
+  }
+}

+ 37 - 0
assets/core_tgx/base/ModuleClass.ts

@@ -0,0 +1,37 @@
+
+const PROP_MODULE = '__module__name__';
+const PROP_IMPL_CLASS = '__impl__class__';
+
+let defaultModule = 'resources';
+
+export class ModuleClass {
+    public static setDefaultModule(moduleName) {
+        defaultModule = moduleName;
+    }
+
+    public static attachModule(cls, moduleName) {
+        cls[PROP_MODULE] = moduleName;
+    }
+
+    public static getModule(cls) {
+        return cls[PROP_MODULE] || defaultModule;
+    }
+
+    public static attachImplClass(cls, implCls) {
+        cls[PROP_IMPL_CLASS] = implCls;
+    }
+
+    public static attachModuleAndImplClass(cls, moduleName, implCls) {
+        cls[PROP_MODULE] = moduleName;
+        cls[PROP_IMPL_CLASS] = implCls;
+    }
+
+    public static getImplClass(cls) {
+        return cls[PROP_IMPL_CLASS] || cls;
+    }
+
+    public static createFromModule(cls) {
+        let implCls = this.getImplClass(cls) || cls;
+        return new implCls();
+    }
+}

+ 9 - 0
assets/core_tgx/base/ModuleClass.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "1d7155da-7b71-4463-97da-c0d2a614eeab",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 46 - 0
assets/core_tgx/base/ModuleContext.ts

@@ -0,0 +1,46 @@
+
+const PROP_MODULE = '__module__name__';
+const PROP_IMPL_CLASS = '__impl__class__';
+
+let defaultModule = 'resources';
+let title = 'resources';
+export class ModuleContext {
+    public static setDefaultModule(moduleName) {
+        defaultModule = moduleName;
+    }
+
+    public static attachModule(cls, moduleName) {
+        cls[PROP_MODULE] = moduleName;
+    }
+
+    public static getClassModule(cls) {
+        return cls[PROP_MODULE] || defaultModule;
+    }
+
+    public static attachImplClass(cls, implCls) {
+        cls[PROP_IMPL_CLASS] = implCls;
+    }
+
+    public static attachModuleAndImplClass(cls, moduleName, implCls) {
+        cls[PROP_MODULE] = moduleName;
+        cls[PROP_IMPL_CLASS] = implCls;
+    }
+
+    public static getImplClass(cls) {
+        return cls[PROP_IMPL_CLASS] || cls;
+    }
+
+    public static createFromModule(cls) {
+        let implCls = this.getImplClass(cls) || cls;
+        return new implCls();
+    }
+
+    public static getTitle() {
+        return title;
+    }
+
+    public static setTitle(ti) {
+         title= ti;
+    }
+
+}

+ 9 - 0
assets/core_tgx/base/ModuleContext.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "1b672f2f-f4c4-43ad-8989-5e5834bfd9fe",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

Some files were not shown because too many files changed in this diff