commit 65a0d2ed41296056d4a6039eb115ea5300fd70eb Author: JianMiau Date: Mon Aug 28 13:44:19 2023 +0800 [add] first diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..de625be --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3467cb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules +.env +package-lock.json +*.pem +.foreverignore +.vscode +/yarn.lock diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..1b7093b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +# sudo docker build -t linebotts . +# sudo docker exec -it 2e8e3995aa52 /bin/bash + +# 選擇node +FROM node:19.4.0 + +# 指定NODE_ENV為production +ENV NODE_ENV=production + +# 指定預設/工作資料夾 +WORKDIR /app + +# 只copy package.json檔案 +COPY ["package.json", "./"] + +# 安裝dependencies +# If you are building your code for production +# RUN npm ci --only=production +RUN npm install + +# copy其餘目錄及檔案 +COPY . . + +# 指定啟動container後執行命令 +CMD [ "npm", "start" ] \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6d48f59 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "line-bot-ts", + "version": "1.0.0", + "description": "", + "main": "src/app.ts", + "scripts": { + "start": "nodemon src/app.ts", + "test": "nodemon src/app.ts", + "dev": "nodemon --exec \"node --require ts-node/register --inspect=192.168.5.36:9229 src/app.ts\"", + "build": "tsc --project ./" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@types/dateformat": "^5.0.0", + "@types/express": "^4.17.15", + "@types/mysql": "^2.15.21", + "@types/node": "^18.11.18", + "typescript": "^4.9.4" + }, + "dependencies": { + "@line/bot-sdk": "^7.5.2", + "@types/ws": "^8.5.5", + "dateformat": "^4.5.1", + "dayjs": "^1.11.7", + "dotenv": "^16.0.3", + "express": "^4.18.2", + "fs": "^0.0.1-security", + "mysql": "^2.18.1", + "nodemon": "^2.0.20", + "ts-node": "^10.9.1", + "ws": "^8.13.0", + "xmlhttprequest": "^1.8.0" + } +} diff --git a/src/DBTools.ts b/src/DBTools.ts new file mode 100644 index 0000000..d052e48 --- /dev/null +++ b/src/DBTools.ts @@ -0,0 +1,44 @@ +import mysql from "mysql" +import Tools from "./Tools" + +/** + * DBTools + */ +export default class DBTools { + + //#region Custom + + public static async Query(query: string): Promise { + const conn: mysql.Connection = this.connect() + + let resp: any = null + let run: boolean = true + conn.query(query, function (err: mysql.MysqlError, rows: any, fields: mysql.FieldInfo[]): void { + if (err) { + console.error(`${query} Error: \n${err.message}`) + run = false + } + resp = rows + run = false + }) + while (run) { + await Tools.Sleep(100) + } + conn.end() + return resp + } + + private static connect(): mysql.Connection { + const conn: mysql.Connection = mysql.createConnection({ + host: process.env.DB_HOST, + port: +process.env.DB_PORT, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_DATABASE + }) + conn.connect() + return conn + } + + //#endregion +} diff --git a/src/DataReceived/MainControlData.ts b/src/DataReceived/MainControlData.ts new file mode 100644 index 0000000..46217b6 --- /dev/null +++ b/src/DataReceived/MainControlData.ts @@ -0,0 +1,25 @@ +import { INetResponse } from "../Engine/CatanEngine/NetManagerV2/Core/INetResponse" +import WebSocketServerClass from "../NetManager/WebSocketServerClass" + +export default class MainControlData { + constructor() { + WebSocketServerClass.Instance.OnDataReceived.AddCallback(this._serverData, this) + } + + /** SERVER主動通知 */ + private _serverData(resp: INetResponse): void { + if (resp.IsValid) { + switch (resp.Method) { + case "chat.send": + resp.WS.SendClient(resp) + // GiftData.Instance?.AddCommonList(resp.Data) + break + default: + // if (GameMain.Instance && GameMain.Instance.node && GameMain.Instance.node.parent) { + // GameMain.Instance.SettingBase.OnNetDataReceived(resp) + // } + break + } + } + } +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/CSharp/String.ts b/src/Engine/CatanEngine/CSharp/String.ts new file mode 100644 index 0000000..33b622e --- /dev/null +++ b/src/Engine/CatanEngine/CSharp/String.ts @@ -0,0 +1,16 @@ +interface StringConstructor { + IsNullOrEmpty: (value: string) => boolean; + Format: (format: string, ...args: any[]) => string; +} + +String.IsNullOrEmpty = function (value: string): boolean { + return value === undefined || value === null || value.trim() === ""; +}; + +String.Format = function (format: string, ...args: any[]): string { + return format.replace(/{(\d+)}/g, (match, index) => { + let value: any = args[index]; + if (value === null || value === undefined) { return ""; } + return "" + value; + }); +}; diff --git a/src/Engine/CatanEngine/CSharp/System/Action.ts b/src/Engine/CatanEngine/CSharp/System/Action.ts new file mode 100644 index 0000000..3231b81 --- /dev/null +++ b/src/Engine/CatanEngine/CSharp/System/Action.ts @@ -0,0 +1,125 @@ +/** + * 回呼函數: fnname (arg: TArg): void + */ +interface ActionCallback { + (arg: TArg): void; +} + +interface Struct { + callback: ActionCallback; + target: any; + once?: boolean; +} + +export class Action { + private _queue: Struct[] = []; + + /** + * 監聽事件 + * @param callback 回呼函數: fnname (arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallback(callback: ActionCallback, bindTarget?: any) { + let q = >{ + callback: callback, + target: bindTarget + }; + this._queue.push(q); + } + + /** + * 監聽事件 (一次性) + * @param callback 回呼函數: fnname (arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallbackOnce(callback: ActionCallback, bindTarget?: any) { + let q = >{ + callback: callback, + target: bindTarget, + once: true + }; + this._queue.push(q); + } + + /** + * 移除事件 + * @param callback + */ + RemoveByCallback(callback: ActionCallback) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.callback === callback) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param bindTarget 回呼時this綁定的對象 + */ + RemoveByBindTarget(bindTarget: any) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.target === bindTarget) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除全部事件 + */ + RemoveAllCallbacks() { + this._queue.forEach(q => q.callback = undefined); + this._queue.length = 0; + } + + /** + * 發送事件 + * @param arg 參數 + */ + DispatchCallback(arg: TArg) { + let index = this._queue.length; + if (index > 0) { + let cleanRemoved = false; + this._queue.slice().forEach(q => { + if (!q.callback) { + cleanRemoved = true; + return; + } + + if (q.target) { + q.callback.call(q.target, arg); + } else { + q.callback(arg); + } + + if (q.once) { + q.callback = undefined; + cleanRemoved = true; + } + }); + + if (cleanRemoved) { + index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback) { + this._queue.splice(index, 1); + } + } + } + } + } + } +} diff --git a/src/Engine/CatanEngine/CSharp/System/ActionWithType.ts b/src/Engine/CatanEngine/CSharp/System/ActionWithType.ts new file mode 100644 index 0000000..6a0ae28 --- /dev/null +++ b/src/Engine/CatanEngine/CSharp/System/ActionWithType.ts @@ -0,0 +1,165 @@ +/** + * 回呼函數: fnname (arg: TArg): void + */ +interface ActionCallback { + (arg: TArg): void; +} + +interface Struct { + callback: ActionCallback; + target: any; + type: TType; + once?: boolean; +} + +export class ActionWithType { + private _queue: Struct[] = []; + + /** + * 監聽事件 + * @param callback 回呼函數: fnname (arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallback(type: TType, callback: ActionCallback, bindTarget?: any) { + let q = >{ + callback: callback, + target: bindTarget, + type: type + }; + this._queue.push(q); + } + + /** + * 監聽事件 (一次性) + * @param callback 回呼函數: fnname (arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallbackOnce(type: TType, callback: ActionCallback, bindTarget?: any) { + let q = >{ + callback: callback, + target: bindTarget, + type: type, + once: true + }; + this._queue.push(q); + } + + /** + * 移除事件 + * @param callback + */ + RemoveByCallback(callback: ActionCallback) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.callback === callback) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param bindTarget 回呼時this綁定的對象 + */ + RemoveByBindTarget(bindTarget: any) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.target === bindTarget) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param type 事件類型 + */ + RemoveByType(type: TType) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.type === type) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param type 事件類型 + * @param callback + */ + RemoveCallback(type: TType, callback: ActionCallback) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || (q.type === type && q.callback === callback)) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除全部事件 + */ + RemoveAllCallbacks() { + this._queue.forEach(q => q.callback = undefined); + this._queue.length = 0; + } + + /** + * 發送事件 + * @param type 事件類型 + * @param arg 參數 + */ + DispatchCallback(type: TType, arg: TArg) { + let index = this._queue.length; + if (index > 0) { + let cleanRemoved = false; + this._queue.slice().forEach(q => { + if (!q.callback) { + cleanRemoved = true; + return; + } + if (q.type !== type) return; + + if (q.target) { + q.callback.call(q.target, arg); + } else { + q.callback(arg); + } + + if (q.once) { + q.callback = undefined; + cleanRemoved = true; + } + }); + + if (cleanRemoved) { + index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback) { + this._queue.splice(index, 1); + } + } + } + } + } + } +} diff --git a/src/Engine/CatanEngine/CSharp/System/ActionWithType2.ts b/src/Engine/CatanEngine/CSharp/System/ActionWithType2.ts new file mode 100644 index 0000000..24c19db --- /dev/null +++ b/src/Engine/CatanEngine/CSharp/System/ActionWithType2.ts @@ -0,0 +1,165 @@ +/** + * 回呼函數: fnname (type: TType, arg: TArg): void + */ +interface ActionCallback { + (type: TType, arg: TArg): void; +} + +interface Struct { + callback: ActionCallback; + target: any; + type: TType; + once?: boolean; +} + +export class ActionWithType2 { + private _queue: Struct[] = []; + + /** + * 監聽事件 + * @param callback 回呼函數: fnname (type: TType, arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallback(type: TType, callback: ActionCallback, bindTarget?: any) { + let q = >{ + callback: callback, + target: bindTarget, + type: type + }; + this._queue.push(q); + } + + /** + * 監聽事件 (一次性) + * @param callback 回呼函數: fnname (type: TType, arg: TArg): void + * @param bindTarget 回呼時this綁定的對象 + */ + AddCallbackOnce(type: TType, callback: ActionCallback, bindTarget?: any) { + let q = >{ + callback: callback, + target: bindTarget, + type: type, + once: true + }; + this._queue.push(q); + } + + /** + * 移除事件 + * @param callback + */ + RemoveByCallback(callback: ActionCallback) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.callback === callback) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param bindTarget 回呼時this綁定的對象 + */ + RemoveByBindTarget(bindTarget: any) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.target === bindTarget) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param type 事件類型 + */ + RemoveByType(type: TType) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || q.type === type) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除事件 + * @param type 事件類型 + * @param callback + */ + RemoveCallback(type: TType, callback: ActionCallback) { + let index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback || (q.type === type && q.callback === callback)) { + q.callback = undefined; + this._queue.splice(index, 1); + } + } + } + } + + /** + * 移除全部事件 + */ + RemoveAllCallbacks() { + this._queue.forEach(q => q.callback = undefined); + this._queue.length = 0; + } + + /** + * 發送事件 + * @param type 事件類型 + * @param arg 參數 + */ + DispatchCallback(type: TType, arg: TArg) { + let index = this._queue.length; + if (index > 0) { + let cleanRemoved = false; + this._queue.slice().forEach(q => { + if (!q.callback) { + cleanRemoved = true; + return; + } + if (q.type !== type) return; + + if (q.target) { + q.callback.call(q.target, type, arg); + } else { + q.callback(type, arg); + } + + if (q.once) { + q.callback = undefined; + cleanRemoved = true; + } + }); + + if (cleanRemoved) { + index = this._queue.length; + if (index > 0) { + while (index--) { + let q = this._queue[index]; + if (!q.callback) { + this._queue.splice(index, 1); + } + } + } + } + } + } +} diff --git a/src/Engine/CatanEngine/CSharp/System/Text/Encoding.ts b/src/Engine/CatanEngine/CSharp/System/Text/Encoding.ts new file mode 100644 index 0000000..37ae6c1 --- /dev/null +++ b/src/Engine/CatanEngine/CSharp/System/Text/Encoding.ts @@ -0,0 +1,75 @@ +export module Encoding.UTF8 { + + export function GetBytes(str: string) { + let len = str.length, resPos = -1; + let resArr = new Uint8Array(len * 3); + for (let point = 0, nextcode = 0, i = 0; i !== len;) { + point = str.charCodeAt(i), i += 1; + if (point >= 0xD800 && point <= 0xDBFF) { + if (i === len) { + resArr[resPos += 1] = 0xef; + resArr[resPos += 1] = 0xbf; + resArr[resPos += 1] = 0xbd; + break; + } + + nextcode = str.charCodeAt(i); + if (nextcode >= 0xDC00 && nextcode <= 0xDFFF) { + point = (point - 0xD800) * 0x400 + nextcode - 0xDC00 + 0x10000; + i += 1; + if (point > 0xffff) { + resArr[resPos += 1] = (0x1e << 3) | (point >>> 18); + resArr[resPos += 1] = (0x2 << 6) | ((point >>> 12) & 0x3f); + resArr[resPos += 1] = (0x2 << 6) | ((point >>> 6) & 0x3f); + resArr[resPos += 1] = (0x2 << 6) | (point & 0x3f); + continue; + } + } else { + resArr[resPos += 1] = 0xef; + resArr[resPos += 1] = 0xbf; + resArr[resPos += 1] = 0xbd; + continue; + } + } + if (point <= 0x007f) { + resArr[resPos += 1] = (0x0 << 7) | point; + } else if (point <= 0x07ff) { + resArr[resPos += 1] = (0x6 << 5) | (point >>> 6); + resArr[resPos += 1] = (0x2 << 6) | (point & 0x3f); + } else { + resArr[resPos += 1] = (0xe << 4) | (point >>> 12); + resArr[resPos += 1] = (0x2 << 6) | ((point >>> 6) & 0x3f); + resArr[resPos += 1] = (0x2 << 6) | (point & 0x3f); + } + } + return resArr.subarray(0, resPos + 1); + } + + export function GetString(array: Uint8Array) { + let str = ""; + let i = 0, len = array.length; + while (i < len) { + let c = array[i++]; + switch (c >> 4) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + str += String.fromCharCode(c); + break; + case 12: + case 13: + str += String.fromCharCode(((c & 0x1F) << 6) | (array[i++] & 0x3F)); + break; + case 14: + str += String.fromCharCode(((c & 0x0F) << 12) | ((array[i++] & 0x3F) << 6) | ((array[i++] & 0x3F) << 0)); + break; + } + } + return str; + } +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/CSharp/System/Text/Encoding.ts.meta b/src/Engine/CatanEngine/CSharp/System/Text/Encoding.ts.meta new file mode 100644 index 0000000..5dd8375 --- /dev/null +++ b/src/Engine/CatanEngine/CSharp/System/Text/Encoding.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "43bf5724-e939-4189-b981-c32ef694e5a5", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/CoroutineV2/CancellationTokenSource.ts b/src/Engine/CatanEngine/CoroutineV2/CancellationTokenSource.ts new file mode 100644 index 0000000..ef9ec11 --- /dev/null +++ b/src/Engine/CatanEngine/CoroutineV2/CancellationTokenSource.ts @@ -0,0 +1,44 @@ +const CANCEL = Symbol(); + +export interface CancellationToken { + readonly IsCancellationRequested: boolean; + + ThrowIfCancellationRequested(): void; +} + +export class CancellationTokenSource { + readonly Token: CancellationToken; + + constructor() { + this.Token = new CancellationTokenImpl(); + } + + Cancel() { + this.Token[CANCEL](); + } +} + +export class TaskCancelledException extends Error { + constructor() { + super("Task Cancelled"); + Reflect.setPrototypeOf(this, TaskCancelledException.prototype); + } +} + +class CancellationTokenImpl implements CancellationToken { + IsCancellationRequested: boolean; + + constructor() { + this.IsCancellationRequested = false; + } + + ThrowIfCancellationRequested() { + if (this.IsCancellationRequested) { + throw new TaskCancelledException(); + } + } + + [CANCEL]() { + this.IsCancellationRequested = true; + } +} diff --git a/src/Engine/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts b/src/Engine/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts new file mode 100644 index 0000000..0ee257f --- /dev/null +++ b/src/Engine/CatanEngine/CoroutineV2/Core/ActionEnumerator.ts @@ -0,0 +1,17 @@ +import { BaseEnumerator } from "./BaseEnumerator"; + +export class ActionEnumerator extends BaseEnumerator { + private _action: Function; + + constructor(action: Function) { + super(); + this._action = action; + } + + next(value?: any): IteratorResult { + if (this._action) { + this._action(); + } + return { done: true, value: undefined }; + } +} diff --git a/src/Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts b/src/Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts new file mode 100644 index 0000000..75c0840 --- /dev/null +++ b/src/Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator.ts @@ -0,0 +1,137 @@ +import { IEnumeratorV2, IEnumeratorV2Started } from "../IEnumeratorV2"; +import { CoroutineExecutor } from "./CoroutineExecutor"; + +let EnumeratorExecutorClass: typeof import("./EnumeratorExecutor").EnumeratorExecutor = null; +let SingleEnumeratorClass: typeof import("./SingleEnumerator").SingleEnumerator = null; +let ParallelEnumeratorClass: typeof import("./ParallelEnumerator").ParallelEnumerator = null; +let WaitTimeEnumeratorClass: typeof import("./WaitTimeEnumerator").WaitTimeEnumerator = null; +let ActionEnumeratorClass: typeof import("./ActionEnumerator").ActionEnumerator = null; + +/** + * 使用前初始場景第一個事件一定要先Init過 + * @example + * 而且不能同時間有其他onLoad在跑,放start可以 + * @example + * protected async onLoad(): Promise { + * await BaseEnumerator.Init(); + * } + */ +export abstract class BaseEnumerator implements IEnumeratorV2 { + public nextEnumerator: BaseEnumerator; + + abstract next(value?: any): IteratorResult; + + public static isInit: boolean = false; + + public static async Init(): Promise { + await Promise.all([ + (async () => { + EnumeratorExecutorClass = (await import("./EnumeratorExecutor")).EnumeratorExecutor; + })(), + (async () => { + SingleEnumeratorClass = (await import("./SingleEnumerator")).SingleEnumerator; + })(), + (async () => { + ParallelEnumeratorClass = (await import("./ParallelEnumerator")).ParallelEnumerator; + })(), + (async () => { + WaitTimeEnumeratorClass = (await import("./WaitTimeEnumerator")).WaitTimeEnumerator; + })(), + (async () => { + ActionEnumeratorClass = (await import("./ActionEnumerator")).ActionEnumerator; + })(), + ]); + BaseEnumerator.isInit = true; + } + + Start(target?: any): IEnumeratorV2Started { + let executor = LazyLoad.EnumeratorExecutor(this, target); + CoroutineExecutor.instance.StartCoroutine(executor); + return executor; + } + + Then(iterator: Iterator): IEnumeratorV2 { + if (!iterator) { + return this; + } + + if (iterator instanceof BaseEnumerator) { + BaseEnumerator.getLastEnumerator(this).nextEnumerator = iterator; + return this; + } else { + let enumerator = LazyLoad.SingleEnumerator(iterator); + BaseEnumerator.getLastEnumerator(this).nextEnumerator = enumerator; + return this; + } + } + + ThenSerial(...iterators: Iterator[]): IEnumeratorV2 { + let last = BaseEnumerator.getLastEnumerator(this); + for (let iterator of iterators) { + if (iterator instanceof BaseEnumerator) { + last.nextEnumerator = iterator; + } else { + let enumerator = LazyLoad.SingleEnumerator(iterator); + last.nextEnumerator = enumerator; + } + last = last.nextEnumerator; + } + return this; + } + + ThenParallel(...iterators: Iterator[]): IEnumeratorV2 { + return this.Then(LazyLoad.ParallelEnumerator(...iterators)); + } + + ThenAction(action: Function, delaySeconds?: number): IEnumeratorV2 { + if (delaySeconds > 0) { + return this.ThenSerial(LazyLoad.WaitTimeEnumerator(delaySeconds), LazyLoad.ActionEnumerator(action)); + } else { + return this.Then(LazyLoad.ActionEnumerator(action)); + } + } + + ThenWaitTime(seconds: number): IEnumeratorV2 { + return this.Then(LazyLoad.WaitTimeEnumerator(seconds)); + } + + static getLastEnumerator(enumerator: BaseEnumerator): BaseEnumerator { + let next = enumerator; + while (next.nextEnumerator) { + next = next.nextEnumerator; + } + return next; + } +} + +module LazyLoad { + export function EnumeratorExecutor(enumerator: BaseEnumerator, target: any) { + let newclass = new EnumeratorExecutorClass(enumerator, target); + return newclass; + // return new (require("./EnumeratorExecutor") as typeof import("./EnumeratorExecutor")).EnumeratorExecutor(enumerator, target); + } + + export function SingleEnumerator(iterator: Iterator) { + let newclass: any = new SingleEnumeratorClass(iterator); + return newclass; + // return new (require("./SingleEnumerator") as typeof import("./SingleEnumerator")).SingleEnumerator(iterator); + } + + export function ParallelEnumerator(...iterators: Iterator[]) { + let newclass: any = new ParallelEnumeratorClass(iterators); + return newclass; + // return new (require("./ParallelEnumerator") as typeof import("./ParallelEnumerator")).ParallelEnumerator(iterators); + } + + export function WaitTimeEnumerator(seconds: number) { + let newclass: any = new WaitTimeEnumeratorClass(seconds); + return newclass; + // return new (require("./WaitTimeEnumerator") as typeof import("./WaitTimeEnumerator")).WaitTimeEnumerator(seconds); + } + + export function ActionEnumerator(action: Function) { + let newclass: any = new ActionEnumeratorClass(action); + return newclass; + // return new (require("./ActionEnumerator") as typeof import("./ActionEnumerator")).ActionEnumerator(action); + } +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts b/src/Engine/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts new file mode 100644 index 0000000..ead42d9 --- /dev/null +++ b/src/Engine/CatanEngine/CoroutineV2/Core/CoroutineExecutor.ts @@ -0,0 +1,103 @@ +import { EnumeratorExecutor } from "./EnumeratorExecutor"; + +export class CoroutineExecutor { + private static _instance: CoroutineExecutor; + static get instance() { + return CoroutineExecutor._instance = CoroutineExecutor._instance || new CoroutineExecutor(); + } + + private _executors: EnumeratorExecutor[] = []; + private _nextExecutors: EnumeratorExecutor[] = []; + private _isRunning: boolean = false; + private _cleanRemoved: boolean = false; + private _scheduler: NodeJS.Timer = null; + private _time: number = 0; + + constructor() { + this._time = new Date().getTime(); + console.debug("[CoroutineV2] Coroutines Start"); + this._scheduler = setInterval(this.update.bind(this), 1 / 60); + } + + StartCoroutine(executor: EnumeratorExecutor) { + executor.next(0); + // TODO: 這邊要考量next後馬上接BaseEnumerator/Iterator的情形 + + if (!this._isRunning) { + this._executors.push(executor); + + if (!this._scheduler) { + console.debug("[CoroutineV2] Coroutines Start"); + this._time = new Date().getTime(); + this._scheduler = setInterval(this.update.bind(this), 1 / 60); + } else { + // console.debug(`[CoroutineV2] Coroutines add now: ${this._executors.length}`); + } + } else { + this._nextExecutors.push(executor); + } + } + + StopCoroutineBy(target: any) { + if (!target) return; + + for (let r of this._executors) { + if (target === r.target) { + r.Stop(); + } + } + + for (let r of this._nextExecutors) { + if (target === r.target) { + r.Stop(); + } + } + } + + update() { + const time: number = new Date().getTime(); + const delta: number = (time - this._time) / 1000; + this._time = time; + if (this._nextExecutors.length) { + this._executors.push(...this._nextExecutors); + // console.debug(`[CoroutineV2] Coroutines addNext now: ${this._executors.length}, next: ${this._nextExecutors.length}`); + this._nextExecutors.length = 0; + } + + if (this._cleanRemoved) { + // 移除[doneFlag=true]的協程 + let index = this._executors.length; + while (index--) { + let r = this._executors[index]; + if (r.doneFlag) { + this._executors.splice(index, 1); + // console.debug(`[CoroutineV2] Coroutines sub now: ${this._executors.length}`); + } + } + this._cleanRemoved = false; + } + + if (this._executors.length == 0) { + console.debug("[CoroutineV2] All Coroutines Done"); + clearInterval(this._scheduler); + this._scheduler = null; + return; + } + + this._isRunning = true; + + // 執行協程 + for (let r of this._executors) { + if (r.doneFlag || r.pauseFlag || r.childFlag) { + if (r.doneFlag) { + this._cleanRemoved = true; + } + continue; + } + + r.next(delta); + } + + this._isRunning = false; + } +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts b/src/Engine/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts new file mode 100644 index 0000000..5ebe234 --- /dev/null +++ b/src/Engine/CatanEngine/CoroutineV2/Core/EnumeratorExecutor.ts @@ -0,0 +1,167 @@ +import { IEnumeratorV2Started } from "../IEnumeratorV2"; +import { BaseEnumerator } from "./BaseEnumerator"; +import { SingleEnumerator } from "./SingleEnumerator"; + +export class EnumeratorExecutor implements IEnumeratorV2Started { + public Current: any; + + public target: any; + public pauseFlag: boolean; + public doneFlag: boolean; + public childFlag: boolean; + public asyncFlag: boolean; + public error: any; + + private _executor: EnumeratorExecutor; + private _enumerator: BaseEnumerator; + + constructor(enumerator: BaseEnumerator, target: any) { + this.target = target; + this._enumerator = enumerator; + } + + next(delta?: any): IteratorResult { + if (this._executor && this._executor.doneFlag) { + this._executor = null; + } + + if (this.doneFlag || (!this._enumerator && !this._executor)) { + this.doneFlag = true; + return { done: true, value: undefined }; + } + + if (this.asyncFlag || this.pauseFlag) return { done: false, value: undefined }; + + let result: IteratorResult; + + if (this._executor) { + result = this._executor.next(delta); + this.Current = this._executor.Current; + if (this._executor.doneFlag) { + this._executor = null; + } else { + result.done = false; + return result; + } + } + + if (!this._enumerator) { + this.doneFlag = true; + return { done: true, value: undefined }; + } + + try { + result = this._enumerator.next(delta); + let value = result.value; + let done = result.done; + + if (value) { + // Iterator + if (typeof value[Symbol.iterator] === "function") { + value = new SingleEnumerator(>value); + } + + if (value instanceof BaseEnumerator) { + if (!done) { + BaseEnumerator.getLastEnumerator(value).nextEnumerator = this._enumerator; + } + this._enumerator = value; + result = this._enumerator.next(delta); + value = result.value; + done = result.done; + + if (value) { + // Iterator again + if (typeof value[Symbol.iterator] === "function") { + value = new SingleEnumerator(>value); + } + + if (value instanceof BaseEnumerator) { + if (!done) { + BaseEnumerator.getLastEnumerator(value).nextEnumerator = this._enumerator; + } + this._enumerator = value; + result.done = false; + done = false; + } + } + } + + if (value instanceof EnumeratorExecutor) { + if (done) { + this._enumerator = this._enumerator.nextEnumerator; + } + value.childFlag = true; + result.done = false; + done = false; + this._executor = value; + } else if (Promise.resolve(value) === value) { + this.asyncFlag = true; + result.done = false; + done = false; + (>value) + .then(v => { + this.asyncFlag = false; + this.Current = v; + if (done) { + this._enumerator = this._enumerator.nextEnumerator; + } + }) + .catch(e => { + this.asyncFlag = false; + this.doneFlag = true; + this._enumerator = null; + this.error = e; + if (e instanceof Error) { + console.error(e.stack); + } else { + console.error(`Error: ${ JSON.stringify(e) }`); + } + }); + } + + this.Current = value; + } + + if (done) { + this._enumerator = this._enumerator.nextEnumerator; + if (this._enumerator) { + result.done = false; + } + } + } catch (e) { + this.doneFlag = true; + this.error = e; + if (e instanceof Error) { + console.error(e.stack); + } else { + console.error(`Error: ${ JSON.stringify(e) }`); + } + result = { done: true, value: e }; + } + + return result; + } + + Stop(): void { + this.doneFlag = true; + if (this._executor) { + this._executor.Stop(); + } + } + + Pause(): void { + this.pauseFlag = true; + if (this._executor) { + this._executor.Pause(); + } + } + + Resume(): void { + this.pauseFlag = false; + if (this._executor) { + this._executor.Resume(); + } + } + +} diff --git a/src/Engine/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts b/src/Engine/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts new file mode 100644 index 0000000..43387e4 --- /dev/null +++ b/src/Engine/CatanEngine/CoroutineV2/Core/ParallelEnumerator.ts @@ -0,0 +1,46 @@ +import { BaseEnumerator } from "./BaseEnumerator"; +import { EnumeratorExecutor } from "./EnumeratorExecutor"; +import { SingleEnumerator } from "./SingleEnumerator"; + +export class ParallelEnumerator extends BaseEnumerator { + private _executors: EnumeratorExecutor[] = []; + + constructor(iterators: Iterator[]) { + super(); + if (iterators && iterators.length) { + for (let iterator of iterators) { + if (iterator instanceof BaseEnumerator) { + this._executors.push(new EnumeratorExecutor(iterator, null)); + } else { + this._executors.push(new EnumeratorExecutor(new SingleEnumerator(iterator), null)); + } + } + } + } + + next(value?: any): IteratorResult { + if (this._executors.length) { + // 先移除[doneFlag=true]協程 + let index = this._executors.length; + while (index--) { + let r = this._executors[index]; + if (r.doneFlag) { + this._executors.splice(index, 1); + } + } + + if (this._executors.length == 0) { + return { done: true, value: undefined }; + } + + // 執行協程 + for (let r of this._executors) { + r.next(value); + } + + return { done: false, value: undefined }; + } + + return { done: true, value: undefined }; + } +} diff --git a/src/Engine/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts b/src/Engine/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts new file mode 100644 index 0000000..fc51ed0 --- /dev/null +++ b/src/Engine/CatanEngine/CoroutineV2/Core/SingleEnumerator.ts @@ -0,0 +1,18 @@ +import { BaseEnumerator } from "./BaseEnumerator"; + +export class SingleEnumerator extends BaseEnumerator { + private _iterator: Iterator; + + constructor(iterator: Iterator) { + super(); + this._iterator = iterator; + } + + next(value?: any): IteratorResult { + if (!this._iterator) { + return { done: true, value: undefined }; + } + + return this._iterator.next(value); + } +} diff --git a/src/Engine/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts b/src/Engine/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts new file mode 100644 index 0000000..604d60e --- /dev/null +++ b/src/Engine/CatanEngine/CoroutineV2/Core/WaitTimeEnumerator.ts @@ -0,0 +1,21 @@ +import { BaseEnumerator } from "./BaseEnumerator"; + +export class WaitTimeEnumerator extends BaseEnumerator { + private _seconds: number; + + constructor(seconds: number) { + super(); + this._seconds = seconds; + } + + next(value?: any): IteratorResult { + let delta = value as number; + this._seconds -= delta; + + if (this._seconds <= 0) { + return { done: true, value: 0 }; + } else { + return { done: false, value: this._seconds }; + } + } +} diff --git a/src/Engine/CatanEngine/CoroutineV2/CoroutineV2.ts b/src/Engine/CatanEngine/CoroutineV2/CoroutineV2.ts new file mode 100644 index 0000000..75d2157 --- /dev/null +++ b/src/Engine/CatanEngine/CoroutineV2/CoroutineV2.ts @@ -0,0 +1,75 @@ +import { ActionEnumerator } from "./Core/ActionEnumerator"; +import { BaseEnumerator } from "./Core/BaseEnumerator"; +import { CoroutineExecutor } from "./Core/CoroutineExecutor"; +import { ParallelEnumerator } from "./Core/ParallelEnumerator"; +import { SingleEnumerator } from "./Core/SingleEnumerator"; +import { WaitTimeEnumerator } from "./Core/WaitTimeEnumerator"; +import { IEnumeratorV2, IEnumeratorV2Started } from "./IEnumeratorV2"; + +export module CoroutineV2 { + /** + * 啟動一般協程 + */ + export function StartCoroutine(iterator: Iterator, target?: any): IEnumeratorV2Started { + return Single(iterator).Start(target); + } + + /** + * 依據IEnumeratorV2.Start(target)綁定的目標, 來停止協程 + * @param target + */ + export function StopCoroutinesBy(target: any) { + CoroutineExecutor.instance.StopCoroutineBy(target); + } + + /** + * 單一協程 + */ + export function Single(iterator: Iterator): IEnumeratorV2 { + if (iterator instanceof BaseEnumerator) { + return iterator; + } else { + return new SingleEnumerator(iterator); + } + } + + /** + * 平行協程 + */ + export function Parallel(...iterators: Iterator[]): IEnumeratorV2 { + return new ParallelEnumerator(iterators); + } + + /** + * 序列協程 + */ + export function Serial(...iterators: Iterator[]): IEnumeratorV2 { + let [iterator, ...others] = iterators; + if (iterator instanceof BaseEnumerator) { + return iterator.ThenSerial(...others); + } else { + return new SingleEnumerator(iterator).ThenSerial(...others); + } + } + + /** + * 執行方法協程 + * @param action 方法 + * @param delaySeconds 延遲秒數 + */ + export function Action(action: Function, delaySeconds?: number): IEnumeratorV2 { + if (delaySeconds > 0) { + return new WaitTimeEnumerator(delaySeconds).Then(new ActionEnumerator(action)); + } else { + return new ActionEnumerator(action); + } + } + + /** + * 等待時間協程 + * @param seconds 秒數 + */ + export function WaitTime(seconds: number): IEnumeratorV2 { + return new WaitTimeEnumerator(seconds); + } +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/CoroutineV2/IEnumeratorV2.ts b/src/Engine/CatanEngine/CoroutineV2/IEnumeratorV2.ts new file mode 100644 index 0000000..2f872cb --- /dev/null +++ b/src/Engine/CatanEngine/CoroutineV2/IEnumeratorV2.ts @@ -0,0 +1,23 @@ +export interface IEnumeratorV2 extends Iterator { + Start(target?: any): IEnumeratorV2Started; + + Then(iterator: Iterator): IEnumeratorV2; + + ThenSerial(...iterators: Iterator[]): IEnumeratorV2; + + ThenParallel(...iterators: Iterator[]): IEnumeratorV2; + + ThenAction(action: Function, delaySeconds?: number): IEnumeratorV2; + + ThenWaitTime(seconds: number): IEnumeratorV2; +} + +export interface IEnumeratorV2Started { + readonly Current: any; + + Pause(): void; + + Resume(): void; + + Stop(): void; +} diff --git a/src/Engine/CatanEngine/NetManagerV2/Core/INetConnector.ts b/src/Engine/CatanEngine/NetManagerV2/Core/INetConnector.ts new file mode 100644 index 0000000..343bb96 --- /dev/null +++ b/src/Engine/CatanEngine/NetManagerV2/Core/INetConnector.ts @@ -0,0 +1,15 @@ +import { Action } from "../../CSharp/System/Action"; +import { INetRequest } from "./INetRequest"; +import { INetResponse } from "./INetResponse"; + +export interface INetConnector { + readonly OnDataReceived: Action>; + readonly OnDisconnected: Action; + readonly IsConnected: boolean; + + SendAsync(req: INetRequest): Iterator; + + Send(req: INetRequest); + + Logout(); +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/NetManagerV2/Core/INetRequest.ts b/src/Engine/CatanEngine/NetManagerV2/Core/INetRequest.ts new file mode 100644 index 0000000..8ab4f86 --- /dev/null +++ b/src/Engine/CatanEngine/NetManagerV2/Core/INetRequest.ts @@ -0,0 +1,13 @@ +import { INetResponse } from "./INetResponse"; + +export interface INetRequest { + readonly Method: string; + readonly MethodBack: string; + + Data: TRequest; + Result: INetResponse; + + SendAsync(): Iterator; + + Send(); +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/NetManagerV2/Core/INetResponse.ts b/src/Engine/CatanEngine/NetManagerV2/Core/INetResponse.ts new file mode 100644 index 0000000..9422414 --- /dev/null +++ b/src/Engine/CatanEngine/NetManagerV2/Core/INetResponse.ts @@ -0,0 +1,9 @@ +import WebSocketClass from "../../../../NetManager/WebSocketClass" + +export interface INetResponse { + readonly Method: string + readonly WS: WebSocketClass + readonly Status: number + readonly Data: TResponse + readonly IsValid: boolean +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/NetManagerV2/NetConfig.ts b/src/Engine/CatanEngine/NetManagerV2/NetConfig.ts new file mode 100644 index 0000000..dfd5645 --- /dev/null +++ b/src/Engine/CatanEngine/NetManagerV2/NetConfig.ts @@ -0,0 +1,4 @@ +export default class NetConfig { + /** 是否顯示RPC接送JSON的LOG */ + public static ShowServerLog: boolean = true; +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/NetManagerV2/NetConnector.ts b/src/Engine/CatanEngine/NetManagerV2/NetConnector.ts new file mode 100644 index 0000000..912f8a9 --- /dev/null +++ b/src/Engine/CatanEngine/NetManagerV2/NetConnector.ts @@ -0,0 +1,266 @@ +import Event from "@/modules/event"; +import * as Define from "@/modules/player/define"; +import { BaseEnumerator } from "../CoroutineV2/Core/BaseEnumerator"; +import { Action } from "../CSharp/System/Action"; +import { Encoding } from "../CSharp/System/Text/Encoding"; +import { INetRequest } from "./Core/INetRequest"; +import { INetResponse } from "./Core/INetResponse"; +import NetConfig from "./NetConfig"; + +export class NetConnector { + /** Event */ + public readonly event: Event = new Event(); + readonly OnDataReceived: Action> = new Action>(); + readonly OnDisconnected: Action = new Action(); + readonly OnLoadUIMask: Action = new Action(); + + get IsConnected() { + return this._ws && this._ws.readyState === WebSocket.OPEN; + } + + public get ws(): WebSocket { + return this._ws; + } + + private _host: string; + private _ws: WebSocket; + private _waitings: WsRequestEnumerator[] = []; + + constructor(host: string, port: number) { + let checkHttp: string = ""; + let index: number = host.indexOf("https://"); + if (index != -1) { + checkHttp = "https"; + host = host.replace("https://", ""); + } else { + checkHttp = window.location.href.substring(0, 5); + host = host.replace("http://", ""); + } + // if (CC_DEBUG) { + console.debug("[事件]checkHttp=", checkHttp, host, port); + // } + if (checkHttp != "https") { + this._host = `ws://${host}:${port}`; + } else { + this._host = `wss://${host}:${port}`; + } + } + + ConnectAsync() { + if (this._ws) { + throw new Error("請先執行CasinoNetManager.Disconnect()中斷連線"); + } + this._ws = new WebSocket(this._host); + + this._ws.binaryType = "arraybuffer"; + this._ws.onopen = this.OnWebSocketOpen.bind(this); + this._ws.onmessage = this.OnWebSocketMessage.bind(this); + this._ws.onclose = this.OnWebSocketClose.bind(this); + this._ws.onerror = this.OnWebSocketError.bind(this); + + return new WsConnectEnumerator(this._ws); + } + + Send(req: INetRequest) { + if (!this.IsConnected) return; + + let json = [req.Method]; + if (req.Data != null && req.Data != undefined && !Number.isNaN(req.Data)) { + json[1] = req.Data; + } + + // if (CC_DEBUG && NetConfig.ShowServerLog) { + if (NetConfig.ShowServerLog) { + if (req.Data != null && req.Data != undefined && !Number.isNaN(req.Data)) { + console.debug(`[RPC] 傳送client資料: ${req.Method}(${JSON.stringify(req.Data)})`); + } else { + console.debug(`[RPC] 傳送client資料: ${req.Method}()`); + } + } + + let str = JSON.stringify(json); + if (str.length > 65535) { + throw new Error("要傳的資料太大囉"); + } + + let strary = Encoding.UTF8.GetBytes(str); + let buffer = new Uint8Array(4 + strary.byteLength); + let u16ary = new Uint16Array(buffer.buffer, 0, 3); + u16ary[0] = strary.byteLength; + buffer[3] = 0x01; + buffer.set(strary, 4); + + this._ws.send(buffer); + } + + SendAsync(req: INetRequest, mask: boolean) { + let iterator = new WsRequestEnumerator(req); + if (!this.IsConnected) { + iterator.SetResponse(ErrorResponse); + } else { + this._waitings.push(iterator); + if (mask) { + this.OnLoadUIMask.DispatchCallback(true); + } + this.Send(req); + } + return iterator; + } + + Disconnect() { + this.WebSocketEnded(); + } + + private WebSocketEnded() { + if (!this._ws) return; + + this._ws.close(); + this._ws.onopen = null; + this._ws.onmessage = null; + this._ws.onclose = () => { + }; + this._ws = null; + + this.CleanWaitings(); + this.OnDisconnected.DispatchCallback(); + } + + private CleanWaitings() { + for (let w of this._waitings) { + w.SetResponse(ErrorResponse); + this.OnLoadUIMask.DispatchCallback(false); + } + this._waitings.length = 0; + } + + private OnWebSocketOpen(e: Event) { + // if (CC_DEBUG) { + console.debug(`[RPC] ${this._host} Connected.`); + // } + this.event.emit(Define.Event.Socket.Connect); + } + + private OnWebSocketMessage(e: MessageEvent) { + if (e.data instanceof ArrayBuffer) { + this.ParseRpcMessage(e.data, e); + } else if (e.data instanceof Blob) { + let reader = new FileReader(); + reader.onload = (e) => { + this.ParseRpcMessage(reader.result, e); + reader.onload = null; + }; + reader.readAsArrayBuffer(e.data); + } else { + throw new Error(`未知的OnWebSocketMessage(e.data)類型: ${e.data}`); + } + } + + private ParseRpcMessage(buffer: ArrayBuffer, e: any) { + let startIndex = 0, byteLength = buffer.byteLength; + while (startIndex + 4 < byteLength) { + let strlen = new DataView(buffer, startIndex, 3).getUint16(0, true); + let str = Encoding.UTF8.GetString(new Uint8Array(buffer, startIndex + 4, strlen)); + startIndex += strlen + 4; + + try { + let json = JSON.parse(str); + let method = json[0]; + let status = json[1][0]; + let data = json[1][1]; + + let resp = >{ + Method: method, + Status: status, + Data: data, + IsValid: method && status === 0 + }; + + // if (CC_DEBUG && NetConfig.ShowServerLog) { + if (NetConfig.ShowServerLog) { + if (data) { + console.debug(`[RPC] 收到server呼叫:(${resp.Status}): ${resp.Method}(${JSON.stringify(resp.Data)})`); + } else { + console.debug(`[RPC] 收到server呼叫:(${resp.Status}): ${resp.Method}()`); + } + } + + let dispatch = true; + let isCocos = false; + for (let i = 0, len = this._waitings.length; i < len; i++) { + let w = this._waitings[i]; + if (w.MethodBack === resp.Method) { + dispatch = false; + this._waitings.splice(i, 1); + w.SetResponse(resp); + this.OnLoadUIMask.DispatchCallback(false); + break; + } + } + + if (dispatch) { + this.OnDataReceived.DispatchCallback(resp); + } + } catch { + throw new Error(`[RPC] 無法解析Server回應: ${str}`); + } + } + } + + private OnWebSocketClose(e: CloseEvent) { + this.WebSocketEnded(); + this.event.emit(Define.Event.Socket.Disconnect); + } + + private OnWebSocketError(e: CloseEvent) { + this.event.emit(Define.Event.Socket.Error); + } +} + +const ErrorResponse: INetResponse = { + Status: -1, + Method: "", + Data: {}, + IsValid: false, +}; + +class WsConnectEnumerator extends BaseEnumerator { + private _ws: WebSocket; + + constructor(ws: WebSocket) { + super(); + this._ws = ws; + } + + next(value?: any): IteratorResult { + return { + done: this._ws.readyState === WebSocket.OPEN || this._ws.readyState === WebSocket.CLOSED, + value: undefined + }; + } +} + +class WsRequestEnumerator extends BaseEnumerator { + readonly MethodBack: string; + + private _req: INetRequest; + private _done: boolean = false; + + constructor(req: INetRequest) { + super(); + + this._req = req; + this.MethodBack = req.MethodBack; + } + + SetResponse(resp: INetResponse) { + this._req.Result = resp; + this._done = true; + } + + next(value?: any): IteratorResult { + return { + done: this._done, + value: undefined + }; + } +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/NetManagerV2/NetManager.ts b/src/Engine/CatanEngine/NetManagerV2/NetManager.ts new file mode 100644 index 0000000..8062dfe --- /dev/null +++ b/src/Engine/CatanEngine/NetManagerV2/NetManager.ts @@ -0,0 +1,54 @@ +import { INetRequest } from "./Core/INetRequest"; +import { NetConnector } from "./NetConnector"; + +export class NetManager { + static get IsConnected() { + return this._connector && this._connector.IsConnected; + } + + static get HasInit() { + return this._connector != null; + } + + private static _connector: NetConnector; + + static Initialize(connector: NetConnector) { + this._connector = connector; + } + + static ConnectAsync() { + this.CheckConnector(); + return this._connector.ConnectAsync(); + } + + /** + * 斷線 + */ + static Disconnect() { + this.CheckConnector(); + this._connector.Disconnect(); + } + + /** + * 傳送資料給Server, 不等待回應 + * @param req + */ + static Send(req: INetRequest) { + this.CheckConnector(); + this._connector.Send(req); + } + + /** + * 傳送資料給Server, 並等待回應 + * @param req + */ + static SendAsync(req: INetRequest, mask: boolean) { + this.CheckConnector(); + return this._connector.SendAsync(req, mask); + } + + private static CheckConnector() { + if (!this._connector) throw new Error("請先呼叫CasinoNetManager.Initialize()初始化connector"); + } + +} \ No newline at end of file diff --git a/src/Engine/CatanEngine/NetManagerV2/NetRequest.ts b/src/Engine/CatanEngine/NetManagerV2/NetRequest.ts new file mode 100644 index 0000000..669342a --- /dev/null +++ b/src/Engine/CatanEngine/NetManagerV2/NetRequest.ts @@ -0,0 +1,28 @@ +import { VueNetConnector } from "@/assets/VueScript/Net/VueNetConnector"; +import { INetRequest } from "./Core/INetRequest"; + +export abstract class NetRequest implements INetRequest { + abstract get Method(): string; + + get MethodBack(): string { + return this.Method; + } + + Data: TResquest; + Result: import("./Core/INetResponse").INetResponse; + + /** + * 在大廳呼叫Cocos會收到SERVER主動通知 + * 在大廳呼叫Cocos會收到SERVER主動通知 + * 在大廳呼叫Cocos會收到SERVER主動通知 + */ + SendAsync(mask: boolean = false): Iterator { + // return NetManager.SendAsync(this, mask); + return VueNetConnector.SendAsync(this, mask); + } + + Send() { + // NetManager.Send(this); + VueNetConnector.Send(this); + } +} diff --git a/src/Engine/Utils/CCExtensions/ArrayExtension.ts b/src/Engine/Utils/CCExtensions/ArrayExtension.ts new file mode 100644 index 0000000..0dab41d --- /dev/null +++ b/src/Engine/Utils/CCExtensions/ArrayExtension.ts @@ -0,0 +1,116 @@ +declare interface Array { + /** + * 移除一個值並且回傳 + * @param index + */ + ExRemoveAt(index: number): T; + /** + * 移除全部值(注意. 參考的也會被清空) + * @example + * + * let bar: number[] = [1, 2, 3]; + * let bar2: number[] = bar; + * bar.Clear(); + * console.log(bar, bar2); + * + * // { + * // "bar": [], + * // "bar2": [] + * // } + */ + Clear(): void; + /** + * 物件陣列排序,asc&key陣列長度請一樣 + * PS. boolean 帶false是先true在false + * @link JavaScript Object 排序 http://www.eion.com.tw/Blogger/?Pid=1170#:~:text=JavaScript%20Object%20排序 + * @param asc 是否升序排列(小到大) + * @param key 需排序的key(優先順序左到右)(沒有就放空) + */ + ObjectSort(asc?: boolean[], key?: string[]): any[]; + /** + * 設計給ArrayforHoldButton使用 + * Add a non persistent listener to the UnityEvent. + * @param call Callback function. + */ + AddListener(call: Function): void; +} + +Array.prototype.ExRemoveAt || Object.defineProperty(Array.prototype, "ExRemoveAt", { + enumerable: false, + value: function (index: number): any { + let item: any = this.splice(index, 1); + return item[0]; + } +}); + +Array.prototype.Clear || Object.defineProperty(Array.prototype, "Clear", { + enumerable: false, + value: function (): void { + this.length = 0; + + // let foo: number[] = [1, 2, 3]; + // let bar: number[] = [1, 2, 3]; + // let foo2: number[] = foo; + // let bar2: number[] = bar; + // foo = []; + // bar.length = 0; + // console.log(foo, bar, foo2, bar2); + + // { + // "foo": [], + // "bar": [], + // "foo2": [ + // 1, + // 2, + // 3 + // ], + // "bar2": [] + // } + } +}); + +Array.prototype.ObjectSort || Object.defineProperty(Array.prototype, "ObjectSort", { + enumerable: false, + /** + * @param asc 是否升序排列(小到大) + * @param key 需排序的key(優先順序左到右)(沒有就放空) + */ + value: function (asc: boolean[] = [true], key?: string[]): any[] { + if (this.length === 0) { + return this; + } else if (!key || key.length === 0) { + console.error(`ObjectSort key error`); + return this; + } else if (asc.length !== key.length) { + console.error(`ObjectSort key asc error asc.length: ${asc.length}, key.length: ${key.length}`); + return this; + } + for (let i: number = 0; i < key.length; i++) { + const keyname: string = key[i]; + if (this[0][keyname] === undefined) { + console.error(`ObjectSort has not key[${i}]: ${keyname}`); + return this; + } + } + let count: number = key ? key.length : 1; + let arr: any[]; + for (let i: number = count - 1; i >= 0; i--) { + arr = this.sort(function (a: any, b: any): 1 | -1 { + let mya: any = a; + let myb: any = b; + if (key) { + mya = a[key[i]]; + myb = b[key[i]]; + } + + // 加個等於數字相同不要再去排序到 + if (asc[i]) { + return mya >= myb ? 1 : -1; + } else { + return mya <= myb ? 1 : -1; + } + }); + } + return arr; + } +}); \ No newline at end of file diff --git a/src/Engine/Utils/CCExtensions/CCExtension.ts.meta b/src/Engine/Utils/CCExtensions/CCExtension.ts.meta new file mode 100644 index 0000000..1ab1865 --- /dev/null +++ b/src/Engine/Utils/CCExtensions/CCExtension.ts.meta @@ -0,0 +1,10 @@ +{ + "ver": "1.1.0", + "uuid": "b373f805-9297-4af5-8ea6-0a250649b5b0", + "importer": "typescript", + "isPlugin": false, + "loadPluginInWeb": true, + "loadPluginInNative": true, + "loadPluginInEditor": false, + "subMetas": {} +} \ No newline at end of file diff --git a/src/Engine/Utils/CCExtensions/NumberExtension.ts b/src/Engine/Utils/CCExtensions/NumberExtension.ts new file mode 100644 index 0000000..5d39881 --- /dev/null +++ b/src/Engine/Utils/CCExtensions/NumberExtension.ts @@ -0,0 +1,189 @@ + +declare interface Number { + + /** + * 金額每三位數(千)加逗號, 並且補到小數點第2位 + * 輸出 41,038,560.00 + * @param precision 補到小數點第幾位 + * @param isPadZero 是否要補零 + * */ + ExFormatNumberWithComma(precision?: number, isPadZero?: boolean): string; + /** + * 基本4位數(9,999-999B-T) + * */ + ExTransferToBMK(precision?: number,offset?: number): string; + /** + * 數字轉字串, 頭補0 + * @param size + */ + Pad(size: number): string; + /** + * 四捨五入到小數點第X位 (同server計算規則) + * @param precision + */ + ExToNumRoundDecimal(precision: number): number; + /** + * 無條件捨去到小數點第X位 + * @param precision + */ + ExToNumFloorDecimal(precision: number): number; + /** + * 無條件捨去強制保留X位小數,如:2,會在2後面補上00.即2.00 + * @param precision 補到小數點第幾位 + * @param isPadZero 是否要補零 + */ + ExToStringFloorDecimal(precision: number, isPadZero?: boolean): string; + /** + * 取整數) + */ + ExToInt():number; + /** + * 小數轉整數(支援科學符號) + */ + Float2Fixed():number; + /** + * 數字長度(支援科學符號) + */ + DigitLength():number; + + target: number; + + +} + +Number.prototype.ExFormatNumberWithComma || Object.defineProperty(Number.prototype, 'ExFormatNumberWithComma', { + enumerable: false, + value: function (precision: number = 2, isPadZero: boolean = true) { + + // let arr = String(this).split('.'); + let arr = this.ExToStringFloorDecimal(precision, isPadZero).split('.'); + let num = arr[0], result = ''; + while (num.length > 3) { + result = ',' + num.slice(-3) + result; + num = num.slice(0, num.length - 3); + } + if (num.length > 0) result = num + result; + return arr[1] ? result + '.' + arr[1] : result; + } +}) + + +Number.prototype.ExTransferToBMK || Object.defineProperty(Number.prototype, 'ExTransferToBMK', { + enumerable: false, + value: function (precision: number=2,offset: number = 0) { + /**千 */ + let MONEY_1K: number = 1000; + /**萬 */ + // let MONEY_10K: number = 10000; + /**十萬 */ + // let MONEY_100K: number = 100000; + /**百萬 */ + let MONEY_1M: number = 1000000; + /**千萬 */ + // let MONEY_10M: number = 10000000; + /**億 */ + // let MONEY_100M: number = 100000000; + /**十億 */ + let MONEY_1B: number = 1000000000; + /**百億 */ + // let MONEY_10B: number = 10000000000; + /**千億 */ + // let MONEY_100B: number = 100000000000; + /**兆 */ + // let MONEY_1T: number = 1000000000000; + offset = Math.pow(10, offset); + // if (this >= MONEY_1T * offset) { + // //(3)1,000T + // //1T~ + // return (~~(this / MONEY_1T)).ExFormatNumberWithComma(0) + "T"; + // } + if (this >= MONEY_1B * offset) { + //1,000B~900,000B + //1B~900B + return (this / MONEY_1B).ExFormatNumberWithComma(3, false) + "B"; + } + else if (this >= MONEY_1M * offset) { + //1,000M~900,000M + //1M~900M + return (this / MONEY_1M).ExFormatNumberWithComma(3, false) + "M"; + } + else if (this >= MONEY_1K * offset) { + //1,000K~900,000K + //1K~90K + return (this / MONEY_1K).ExFormatNumberWithComma(3, false) + "K"; + } + else { + //0~9,000,000 + //0~9,000 + return this.ExFormatNumberWithComma(precision); + } + } +}) +Number.prototype.Pad || Object.defineProperty(Number.prototype, 'Pad', { + enumerable: false, + value: function (size: number) { + let s = this + ""; + while (s.length < size) s = "0" + s; + return s; + } +}) +Number.prototype.ExToNumRoundDecimal || Object.defineProperty(Number.prototype, 'ExToNumRoundDecimal', { + enumerable: false, + value: function (precision: number) { + return Math.round(Math.round(this * Math.pow(10, (precision || 0) + 1)) / 10) / Math.pow(10, (precision || 0)); + } +}) +Number.prototype.ExToInt || Object.defineProperty(Number.prototype, 'ExToInt',{ + enumerable: false, + value: function (){ + return ~~this; + } +}) +Number.prototype.ExToNumFloorDecimal || Object.defineProperty(Number.prototype, 'ExToNumFloorDecimal', { + enumerable: false, + value: function (precision: number) { + let str = this.toPrecision(12); + let dotPos = str.indexOf('.'); + return dotPos == -1 ? this : +`${str.substr(0, dotPos + 1 + precision)}`; + } +}) +Number.prototype.ExToStringFloorDecimal || Object.defineProperty(Number.prototype, 'ExToStringFloorDecimal', { + enumerable: false, + value: function (precision: number, isPadZero: boolean = true) { + // 取小數點第X位 + let f = this.ExToNumFloorDecimal(precision); + let s = f.toString(); + // 補0 + if (isPadZero) { + let rs = s.indexOf('.'); + if (rs < 0) { + rs = s.length; + s += '.'; + } + while (s.length <= rs + precision) { + s += '0'; + } + } + return s; + } +}) +Number.prototype.Float2Fixed || Object.defineProperty(Number.prototype, 'Float2Fixed', { + enumerable: false, + value: function () { + if (this.toString().indexOf('e') === -1) { + return Number(this.toString().replace('.', '')); + } + const dLen = this.DigitLength(); + return dLen > 0 ? +parseFloat((this * Math.pow(10, dLen)).toPrecision(12)) : this; + } +}) +Number.prototype.DigitLength || Object.defineProperty(Number.prototype, 'DigitLength', { + enumerable: false, + value: function () { + const eSplit = this.toString().split(/[eE]/); + const len = (eSplit[0].split('.')[1] || '').length - (+(eSplit[1] || 0)); + return len > 0 ? len : 0; + } +}) + + \ No newline at end of file diff --git a/src/Engine/Utils/Number/NumberEx.ts b/src/Engine/Utils/Number/NumberEx.ts new file mode 100644 index 0000000..b9ad5e1 --- /dev/null +++ b/src/Engine/Utils/Number/NumberEx.ts @@ -0,0 +1,84 @@ +export module NumberEx { + + /** + * 检测数字是否越界,如果越界给出提示 + * @param {*number} num 输入数 + */ + function checkBoundary(num: number) { + if (_boundaryCheckingState) { + if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) { + console.warn(`${num} is beyond boundary when transfer to integer, the results may not be accurate`); + } + } + } + + /** + * 精确乘法 + */ + export function times(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return times(times(num1, num2), others[0], ...others.slice(1)); + } + const num1Changed = num1.Float2Fixed(); + const num2Changed = num2.Float2Fixed(); + const baseNum = num1.DigitLength() + num2.DigitLength(); + const leftValue = num1Changed * num2Changed; + + checkBoundary(leftValue); + + return leftValue / Math.pow(10, baseNum); + } + + /** + * 精确加法 + */ + export function plus(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return plus(plus(num1, num2), others[0], ...others.slice(1)); + } + const baseNum = Math.pow(10, Math.max(num1.DigitLength(), num2.DigitLength())); + return (times(num1, baseNum) + times(num2, baseNum)) / baseNum; + } + + /** + * 精确减法 + */ + export function minus(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return minus(minus(num1, num2), others[0], ...others.slice(1)); + } + const baseNum = Math.pow(10, Math.max(num1.DigitLength(), num2.DigitLength())); + return (times(num1, baseNum) - times(num2, baseNum)) / baseNum; + } + + /** + * 精确除法 + */ + export function divide(num1: number, num2: number, ...others: number[]): number { + if (others.length > 0) { + return divide(divide(num1, num2), others[0], ...others.slice(1)); + } + const num1Changed = num1.Float2Fixed(); + const num2Changed = num2.Float2Fixed(); + checkBoundary(num1Changed); + checkBoundary(num2Changed); + return times((num1Changed / num2Changed), Math.pow(10, num2.DigitLength() - num1.DigitLength())); + } + + /** + * 四舍五入 + */ + export function round(num: number, ratio: number): number { + const base = Math.pow(10, ratio); + return divide(Math.round(times(num, base)), base); + } + + let _boundaryCheckingState = false; + /** + * 是否进行边界检查 + * @param flag 标记开关,true 为开启,false 为关闭 + */ + function enableBoundaryChecking(flag = true) { + _boundaryCheckingState = flag; + } +} \ No newline at end of file diff --git a/src/Engine/Utils/Number/RandomEx.ts b/src/Engine/Utils/Number/RandomEx.ts new file mode 100644 index 0000000..1bfc080 --- /dev/null +++ b/src/Engine/Utils/Number/RandomEx.ts @@ -0,0 +1,90 @@ + +export module RandomEx { + + /** + * 取得隨機布林值 + */ + export function GetBool() { + return GetInt() >= 0; + } + /** + * 取得隨機整數(回傳min ~ max - 1) + * @param min + * @param max + */ + export function GetInt(min: number = Number.MIN_VALUE, max: number = Number.MAX_VALUE): number { + return Math.floor(Math.random() * (max - min)) + min; + } + /** + * 取得隨機小數 + * @param min + * @param max + */ + export function GetFloat(min: number = Number.MIN_VALUE, max: number = Number.MAX_VALUE): number { + return Math.random() * (max - min) + min; + } + /** + * 隨機取得複數個不重複回傳 + * @param num 取得數量 + * @param items 陣列 + */ + export function GetMultiNoRepeat(num: number, items: any[]): any[] { + let result: any[] = []; + for (let i: number = 0; i < num; i++) { + let ran: number = Math.floor(Math.random() * items.length); + let item = items.splice(ran, 1)[0]; + if (result.indexOf(item) == -1) { + result.push(item); + } + }; + return result; + } + + /** + * 根據權重取得複數個不重複回傳 + * @param prize 獎項 + * @param weights 機率 + * @param count 數量 + */ + export function GetMultiNoRepeatByWeight(prize: any[], weights: number[] = null, count: number = 1): any[] { + if (weights === null) { + weights = []; + for (let i: number = 0; i < prize.length; i++) { + weights.push(1); + } + } + let target: any[] = []; + for (let i: number = 0; i < count; i++) { + let results: number[] = RandomEx.GetPrizeByWeight(prize, weights); + prize.splice(results[0], 1); + weights.splice(results[0], 1); + target.push(results[1]); + } + return target; + } + + + /** + * 根據權重隨機取值 + * @param prize 獎項 + * @param weights 機率 + */ + export function GetPrizeByWeight(prize: any[], weights: number[]): any[] { + if (prize.length !== weights.length) { + console.error(`GetWeight error -> prize.length:${prize.length} !== weights.length:${weights.length}`); + return null; + } + let totalWeight: number = 0; + for (let i: number = 0; i < weights.length; i++) { + totalWeight += weights[i]; + } + let random: number = RandomEx.GetInt(0, totalWeight) + 1; + let nowWeight: number = weights[0]; + for (let i: number = 0; i < weights.length; i++) { + if (nowWeight >= random) { + return [i, prize[i]]; + } + nowWeight += weights[i + 1]; + } + } +} diff --git a/src/Engine/Utils/Singleton/BaseSingleton.ts b/src/Engine/Utils/Singleton/BaseSingleton.ts new file mode 100644 index 0000000..9cff221 --- /dev/null +++ b/src/Engine/Utils/Singleton/BaseSingleton.ts @@ -0,0 +1,27 @@ +/** + * 單例基類(要先new在使用) + * @example + * export default class Test extends BaseSingleton() { ...... } + * new Test(); + * Test.Instance.Init(); + */ +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +export default function BaseSingleton() { + class BaseSingleton { + public constructor() { + if ((this)._instance == null) { + BaseSingleton._instance = this; + } + } + private static _instance: BaseSingleton = null; + public static get Instance(): T { + return (this)._instance; + } + + /** 銷毀 */ + public Destroy(): void { + (this)._instance = null; + } + } + return BaseSingleton; +} \ No newline at end of file diff --git a/src/GuessWhoIAmClass.ts b/src/GuessWhoIAmClass.ts new file mode 100644 index 0000000..ef919b7 --- /dev/null +++ b/src/GuessWhoIAmClass.ts @@ -0,0 +1,30 @@ +import MainControlData from "./DataReceived/MainControlData" +import WebSocketServerClass from "./NetManager/WebSocketServerClass" + +/** + * GuessWhoIAm + */ +export default class GuessWhoIAmClass { + + //#region private + + private clientCount: number = 0 + + //#endregion + + //#region Lifecycle + + /** + * + */ + constructor() { + new WebSocketServerClass() + new MainControlData() + } + + //#endregion + + //#region Custom + + //#endregion +} diff --git a/src/MainControl/MainControl.ts b/src/MainControl/MainControl.ts new file mode 100644 index 0000000..2803f70 --- /dev/null +++ b/src/MainControl/MainControl.ts @@ -0,0 +1,21 @@ +// export class MainControl { +// //#region 網路相關 + +// /**連線(目前沒有重連機制) */ +// public * ConnectAsync() { +// if (NetManager.IsConnected) { +// return +// } +// this._conn = new NetConnector(BusinessTypeSetting.UseHost, BusinessTypeSetting.UsePort/*, this._realIp*/) +// this._conn.OnDataReceived.AddCallback(this._onNetDataReceived, this) +// this._conn.OnDisconnected.AddCallback(this._onNetDisconnected, this) +// this._conn.OnLoadUIMask.AddCallback(this._oOnLoadUIMask, this) +// NetManager.Initialize(this._conn) +// cc.log("[socket] connecting...") +// // 同個connector要再次連線, 可以不用叫CasinoNetManager.Initialize(), 但要先叫CasinoNetManager.Disconnect() +// yield NetManager.ConnectAsync() +// } + +// //#endregion + +// } \ No newline at end of file diff --git a/src/NetManager/WebSocketClass.ts b/src/NetManager/WebSocketClass.ts new file mode 100644 index 0000000..75f730c --- /dev/null +++ b/src/NetManager/WebSocketClass.ts @@ -0,0 +1,106 @@ + +import _ws from "ws" +import { Encoding } from "../Engine/CatanEngine/CSharp/System/Text/Encoding" +import { INetResponse } from "../Engine/CatanEngine/NetManagerV2/Core/INetResponse" +import WebSocketServerClass from "./WebSocketServerClass" + +/** + * WebSocket + */ +export default class WebSocketClass { + + //#region private + + private ws: _ws = undefined + private clientCount: number = undefined + + //#endregion + + //#region Lifecycle + + /** + * + */ + constructor(ws: _ws, clientCount: number) { + this.ws = ws + this.clientCount = clientCount + + // 當收到client消息時 + ws.on('message', this.onMessage.bind(this)) + // 當連線關閉 + ws.on('close', this.onClose.bind(this)) + } + + //#endregion + + //#region Custom + + private onMessage(buffer: _ws.RawData): void { + // 收回來是 Buffer 格式、需轉成字串 + const dataStr: string = "[" + buffer.toString().split("[").slice(1).join("[") + const json = JSON.parse(dataStr) + const method = json[0] + let status = 0 + const data = json[1] + const resp = { + Method: method, + Status: status, + Data: data, + IsValid: method && status === 0, + WS: this + } + + if (true) { + if (data) { + console.debug(`[RPC] 收到server呼叫:(${resp.WS.clientCount}): ${resp.Method}(${JSON.stringify(resp.Data)})`) + } else { + console.debug(`[RPC] 收到server呼叫:(${resp.WS.clientCount}): ${resp.Method}()`) + } + } + + WebSocketServerClass.Instance.OnDataReceived.DispatchCallback(resp) + + // /// 發送消息給client + // this.SendClient(data) + // WebSocketServerClass.Instance.SendAllClient(data) + } + + private onClose(): void { + console.log(`Client_${this.clientCount} Close connected`) + } + + /** 發送給所有client */ + public SendClient(req: INetResponse): void { + const status = 0 + const json: any[] = [req.Method] + //@ts-ignore + if (req.Data != null && req.Data != undefined && req.Data != NaN) { + json[1] = [status, req.Data] + } + + if (true) { + //@ts-ignore + if (req.Data != null && req.Data != undefined && req.Data != NaN) { + console.log(`[RPC] 傳送client資料:(${req.WS.clientCount}): ${req.Method}(${JSON.stringify(req.Data)})`) + } else { + console.log(`[RPC] 傳送client資料:(${req.WS.clientCount}): ${req.Method}()`) + } + } + + const str = JSON.stringify(json) + if (str.length > 65535) { + throw new Error('要傳的資料太大囉') + } + + const strary = Encoding.UTF8.GetBytes(str) + const buffer = new Uint8Array(4 + strary.byteLength) + const u16ary = new Uint16Array(buffer.buffer, 0, 3) + u16ary[0] = strary.byteLength + buffer[3] = 0x01 + buffer.set(strary, 4) + + this.ws.send(buffer) + } + + //#endregion +} diff --git a/src/NetManager/WebSocketServerClass.ts b/src/NetManager/WebSocketServerClass.ts new file mode 100644 index 0000000..80d1999 --- /dev/null +++ b/src/NetManager/WebSocketServerClass.ts @@ -0,0 +1,79 @@ +import express from "express" +import fs from "fs" +import _ws from "ws" +import { Action } from "../Engine/CatanEngine/CSharp/System/Action" +import { INetResponse } from "../Engine/CatanEngine/NetManagerV2/Core/INetResponse" +import BaseSingleton from "../Engine/Utils/Singleton/BaseSingleton" +import WebSocketClass from "./WebSocketClass" +const SocketServer: typeof _ws.Server = _ws.Server + +/** + * WebSocketServer + */ +export default class WebSocketServerClass extends BaseSingleton() { + + readonly OnDataReceived: Action> = new Action>() + + //#region private + + private wss: _ws.Server = undefined + private clientCount: number = 0 + + //#endregion + + //#region Lifecycle + + /** + * + */ + constructor() { + super() + //讀取憑證及金鑰 + const prikey: string = fs.readFileSync("./certificate/RSA-privkey.pem", "utf8") + const cert: string = fs.readFileSync("./certificate/RSA-cert.pem", "utf8") + const cafile: string = fs.readFileSync("./certificate/RSA-chain.pem", "utf-8") + + //建立憑證及金鑰 + const credentials: Object = { + key: prikey, + cert: cert, + ca: cafile + } + + // 用於辨識Line Channel的資訊 + const config: any = { + channelSecret: process.env.channelSecret, + channelAccessToken: process.env.channelAccessToken || "" + } + + const port: number = +process.env.PORT || 3000 + const server = express().listen(port, () => { + console.log(`Listening on ${port}`) + }) + //將 express 交給 SocketServer 開啟 WebSocket 的服務 + this.wss = new SocketServer({ server }) + //當有 client 連線成功時 + this.wss.on('connection', this.onConnection.bind(this)) + } + + //#endregion + + //#region Custom + + private onConnection(ws: _ws): void { + const clientNum: number = this.clientCount + console.log(`Client_${clientNum} connected`) + new WebSocketClass(ws, clientNum) + this.clientCount++ + } + + /** 發送給所有client */ + public SendAllClient(data: any): void { + let clients = this.wss.clients //取得所有連接中的 client + clients.forEach(client => { + client.send(data) // 發送至每個 client + }) + } + + //#endregion +} diff --git a/src/Tools.ts b/src/Tools.ts new file mode 100644 index 0000000..ab7e94e --- /dev/null +++ b/src/Tools.ts @@ -0,0 +1,13 @@ +/** + * Tools + */ +export default class Tools { + + //#region Custom + + public static Sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } + + //#endregion +} diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..47e18fb --- /dev/null +++ b/src/app.ts @@ -0,0 +1,16 @@ +// 背景執行 forever start -c ts-node -a -l line-bot-ts.log src/app.ts +// 重新背景執行 forever restart -a -l line-bot-ts.log src/app.ts +// 監聽檔案變化 "npm start" +// 連線Debug "npm run dev" + +import dayjs from "dayjs" +import "dayjs/locale/zh-tw" +import dotenv from "dotenv" +import "./Engine/CatanEngine/CSharp/String" +import "./Engine/Utils/CCExtensions/ArrayExtension" +import "./Engine/Utils/CCExtensions/NumberExtension" +import GuessWhoIAmClass from "./GuessWhoIAmClass" + +dayjs.locale("zh-tw") +dotenv.config() +new GuessWhoIAmClass() \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..4a4ae41 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "module": "commonjs", /* Specify what module code is generated. */ + "lib": [ + "es2015", + "es2017", + "dom" + ], + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "strict": false, /* Enable all strict type-checking options. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */, + "outDir": "dist" // 將編譯過後的js檔放到dist資料夾中 + } +} \ No newline at end of file