[add] first

This commit is contained in:
建喵 2023-11-23 16:33:21 +08:00
commit bd457c7f10
89 changed files with 15504 additions and 0 deletions

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
src/FormTable
src/FormTableExt

366
.eslintrc.json Normal file
View File

@ -0,0 +1,366 @@
{
"env": {
"browser": true,
"es2021": true,
"jest": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"overrides": [],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint",
"react-hooks"
],
"rules": {
"no-alert": 0, //使alert confirm prompt
"no-bitwise": 0, //使
"no-console": "off", //使console
"no-continue": 0, //使continue
"no-debugger": 2, //使debugger
"no-delete-var": 2, //var使delete
"no-div-regex": 1, //使/=foo/
"no-dupe-args": 2, //
"no-duplicate-case": 2, //switchcase
"no-else-return": "off", //ifreturn,else
"no-empty-label": "off", //使label
"no-eq-null": "off", //null使==!=
"no-extend-native": "off", //native
"no-extra-parens": "off", //
"no-extra-semi": 2, //
"no-floating-decimal": 2, //0 .5 3.
"no-implicit-coercion": "off", //
"no-inline-comments": 0, //
"no-invalid-this": 2, //this
"no-iterator": 2, //使__iterator__
"no-lonely-if": "off", //elseif
"no-mixed-requires": [
0,
false
], //
"no-mixed-spaces-and-tabs": "off", //tab
"no-multiple-empty-lines": [
1,
{
"max": 2
}
], //2
"no-nested-ternary": 0, //使
"no-new": "off", //使new
"no-new-require": 2, //使new require
"no-param-reassign": "off", //
"no-path-concat": 0, //node使__dirname__filename
"no-plusplus": 0, //使++--
"no-process-env": 0, //使process.env
"no-process-exit": 0, //使process.exit()
"no-redeclare": "off", //
"no-restricted-modules": 0, //使
"no-return-assign": "off", //return
"no-self-compare": 2, //
"no-sequences": 0, //使
"no-shadow": "off", //
"no-sync": 0, //nodejs
"no-ternary": 0, //使
"no-this-before-super": 0, //super()使thissuper
"no-throw-literal": 2, // throw "error";
"no-undef": "off", //
"no-undef-init": "off", //undefined
"no-undefined": "off", //使undefined
"no-unexpected-multiline": 2, //
"no-underscore-dangle": "off", //_
"no-unneeded-ternary": 2, // var isYes = answer === 1 ? true : false;
"no-unused-expressions": "off", //
"no-unused-vars": "off", //使
"no-use-before-define": "off", //使
"no-useless-call": "off", //callapply
"no-void": "off", //void
"no-var": 0, //varletconst
"no-warning-comments": "off", //
"no-array-constructor": "error", // 使
"no-caller": "error", // 使arguments.callerarguments.callee
"no-catch-shadow": "error", // catch
"no-class-assign": "error", //
"no-cond-assign": [
"error",
"except-parens"
], // 使
"no-constant-condition": "error", // 使 if(true) if(1)
"no-control-regex": "error", // 使
"no-dupe-keys": "error", // {a: 1, a: 1}
"no-empty": "error", //
"no-empty-character-class": "error", // []
"no-eval": "error", // 使eval
"no-ex-assign": "error", // catch
"no-extra-bind": "error", //
"no-extra-boolean-cast": "off", // bool
"no-fallthrough": "error", // switch穿
"no-func-assign": "error", //
"no-implied-eval": "error", // 使eval
"no-inner-declarations": "off", // 使
"no-invalid-regexp": "error", //
"no-irregular-whitespace": "error", //
"no-label-var": "error", // labelvar
"no-labels": "error", //
"no-lone-blocks": "error", //
"no-loop-func": "error", // 使
"no-multi-spaces": "error", //
"no-multi-str": "error", // \
"no-native-reassign": "error", // native
"no-negated-in-lhs": "error", // in !
"no-new-func": "error", // 使new Function
"no-new-object": "error", // 使new Object()
"no-new-wrappers": "error", // 使newnew String new Boolean new Number
"no-obj-calls": "error", // Math() JSON()
"no-octal": "error", // 使(0)
"no-octal-escape": "error", // 使
"no-proto": "error", // 使__proto__(__proto__)
"no-regex-spaces": "error", // 使 /foo bar/
"no-script-url": "off", // 使javascript:void(0)
"no-shadow-restricted-names": "error", // 使
"no-spaced-func": "error", // ()
"no-sparse-arrays": "error", // [1,,2]
"no-trailing-spaces": [
"error",
{
"skipBlankLines": true
}
], // ( )
"no-unreachable": "error", //
"no-const-assign": "error", // const
"no-with": "error", // with
"comma-dangle": "off", //
"comma-spacing": "error", // 西
"curly": [
"error",
"multi-line"
], // 使 {}
"eqeqeq": "off", // 使
"indent": [
"off",
"tab",
{
"SwitchCase": 1
}
], // tab
"key-spacing": [
"error",
{
"beforeColon": false,
"afterColon": true
}
], //
"keyword-spacing": "off", //
"new-parens": "error", // new const person = new Person();
"quotes": [
"error",
"double",
{
"allowTemplateLiterals": true
}
], // ''
"semi": [
"error",
"always"
], //
"semi-spacing": [
0,
{
"before": false,
"after": true
}
], // 西
"space-before-blocks": [
"error",
"always"
], // {
// "space-before-function-paren": ["error", "never"], //
"space-infix-ops": "error", // a + b
"space-unary-ops": [
"error",
{
"words": true,
"nonwords": false
}
], // / new Foo 1++
// "spaced-comment": ["error", "always", { "markers": ["*!"] }], //
"strict": [
"error",
"global"
], // 使
"use-isnan": "error", // 使NaNisNaN()
"arrow-parens": 0, //
"arrow-spacing": 0, //=>/
"accessor-pairs": 0, //使getter/setter
"block-scoped-var": 0, //使var
"brace-style": "off", //
"callback-return": "off", //
"comma-style": [
"error",
"last"
], //
"complexity": [
0,
11
], //
"computed-property-spacing": [
0,
"never"
], //
"consistent-return": 0, //return
"consistent-this": "off", //this
"constructor-super": 0, //supersuper
"default-case": "off", //switchdefault
"dot-location": 0, //访
"dot-notation": [
0,
{
"allowKeywords": true
}
], //
"eol-last": 0, //
"func-names": 0, //
"func-style": [
0,
"declaration"
], //使/
"generator-star-spacing": 0, //*
"guard-for-in": 0, //for inif
"handle-callback-err": 0, //nodejs
"id-length": 0, //
"init-declarations": 0, //
"lines-around-comment": 0, ///
"max-depth": [
0,
4
], //
"max-len": [
0,
80,
4
], //
"max-nested-callbacks": [
0,
2
], //
"max-params": [
0,
3
], //3
"max-statements": [
0,
10
], //
"new-cap": "off", //使newnew
"newline-after-var": "off", //
"object-shorthand": 0, //
"one-var": "off", //
"operator-assignment": [
0,
"always"
], // += -=
"operator-linebreak": "off", //
"padded-blocks": 0, //
"prefer-spread": 0, //
"prefer-reflect": 0, //Reflect
"quote-props": "off", //
"radix": "off", //parseInt
"id-match": 0, //
"sort-vars": 0, //
"space-after-keywords": [
0,
"always"
], //
"space-before-function-paren": [
0,
"always"
], //
"space-in-parens": [
0,
"never"
], //
"space-return-throw-case": "off", //return throw case
"spaced-comment": 0, //
"valid-jsdoc": 0, //jsdoc
"valid-typeof": "error", //使typeof
"vars-on-top": "error", //var
"wrap-iife": [
"error",
"inside"
], //
"wrap-regex": 0, //
"yoda": [
"error",
"never"
], //
"linebreak-style": [
0,
"windows"
], //
"array-bracket-spacing": [
2,
"never"
], //
"react/react-in-jsx-scope": "off",
"camelcase": "off",
"block-spacing": "error",
"no-duplicate-imports": "error",
"require-yield": "off",
"prefer-const": "off",
"object-curly-spacing": [
"error",
"always"
],
"react/jsx-curly-spacing": [
"error",
"never"
],
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/prefer-namespace-keyword": "off",
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-empty-interface": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/explicit-function-return-type": [
"off",
{
"allowExpressions": true
}
],
"@typescript-eslint/typedef": [
"warn",
{
"arrayDestructuring": false,
"arrowParameter": false,
"objectDestructuring": false,
"memberVariableDeclaration": true,
"parameter": true,
"propertyDeclaration": true,
"variableDeclaration": false,
"variableDeclarationIgnoreFunction": true
}
]
},
"settings": {
"import/resolver": {
"typescript": {}
},
"react": {
"version": "detect"
}
}
}

30
.gitignore vendored Normal file
View File

@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
web-mobile
node_modules
build
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
/.vscode
/src/FormTable
/src/FormTable/*
/src/FormTableExt
/src/FormTableExt/*

5
auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
// Generated by 'unplugin-auto-import'
export {}
declare global {
}

BIN
build-templates/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

124
build.cjs Normal file
View File

@ -0,0 +1,124 @@
//build.js文件
// let exec = require('child_process').exec // 异步子进程
let fs = require('fs')
const path = "./build-templates/version.json"
let packageJSON = require(path)
/** package.json文件的version参数 */
let version = packageJSON.version
// /** 命令行的所有参数 */
// let options = process.argv
// /** 命令行的type参数 */
// let type = null
// /** 新的version参数 */
// let newVersion = null
const dt = new Date();
const Year = +((dt.getFullYear() + "")[2] + (dt.getFullYear() + "")[3]);
const Month = +(dt.getMonth() + 1);
const Day = +(dt.getDate());
let MajorVersion = +(version.MajorVersion);
let MinorVersion = +(version.MinorVersion);
let BuildVersion = +(version.BuildVersion);
let Revision = +(version.Revision);
if (!MajorVersion || !MinorVersion || !BuildVersion || !Revision || Year != MajorVersion) {
MajorVersion = Year;
MinorVersion = Month;
BuildVersion = Day;
Revision = 1;
} else if (Month != MinorVersion) {
MinorVersion = Month;
BuildVersion = Day;
Revision = 1;
} else if (Day != BuildVersion) {
BuildVersion = Day;
Revision = 1;
} else {
Revision++;
}
var data_new = {
version: {
MajorVersion,
MinorVersion,
BuildVersion,
Revision
}
}
let VersionNew = JSON.stringify(data_new);
console.log(`version: ${MajorVersion}.${MinorVersion}.${BuildVersion}.${Revision}`)
//同步寫入package.json文件
fs.writeFileSync(path, VersionNew)
return;
// //判断命令行是否存在type参数或version参数进行逻辑处理
// for (let i = 0; i < options.length; i++) {
// if (options[i].indexOf('type') > -1) {
// //存在type参数
// type = options[i].split('=')[1]
// } else if (options[i].indexOf('version') > -1) {
// //存在version参数
// newVersion = options[i].split('=')[1]
// } else {
// //code
// }
// }
// if (newVersion) {
// //存在设置version参数则改变原来的version
// version = newVersion
// } else if (type) {
// //不设置version则根据type来进行修改version
// version = handleType(version, type)
// } else {
// version = null
// console.log('-----------没有改变version-----------')
// }
// //修改了version则写入
// if (version) {
// packageJSON.version = version
// //同步写入package.json文件
// fs.writeFileSync('package.json', JSON.stringify(packageJSON, null, 2))
// console.log('-----------更新package的version为%s参数成功-----------', version)
// // handleGitAdd('./package.json')
// // pullRemote()
// }
// /**
// * 根据分支类型处理版本号version
// * @param {string} oldVersion 旧的版本号
// * @param {string} type 分支类型
// * @private
// */
// function handleType(oldVersion, type) {
// let oldVersionArr = oldVersion.split('.')
// //版本号第一位 如1.2.3 则为 1
// let firstNum = +oldVersionArr[0]
// //版本号第二位 如1.2.3 则为 2
// let secondNum = +oldVersionArr[1]
// //版本号第三位 如1.2.3 则为 3
// let thirdNum = +oldVersionArr[2]
// switch (type) {
// case 'release':
// //release分支的处理逻辑
// ++secondNum
// thirdNum = 0
// break
// case 'hotfix':
// //hotfix分支的处理逻辑
// ++thirdNum
// break
// default:
// // 默认按照最小版本处理
// ++thirdNum
// break
// }
// return firstNum + '.' + secondNum + '.' + thirdNum
// }

24
index.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover">
<script>
history.pushState(null, null, location.href);
window.onpopstate = function () {
history.go(1);
};
</script>
<title>來博娛樂城</title>
</head>
<body oncontextmenu="return false">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

8946
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

99
package.json Normal file
View File

@ -0,0 +1,99 @@
{
"name": "lp-react",
"private": true,
"version": "0.0.1",
"type": "module",
"homepage": "./",
"scripts": {
"start": "npm run dev",
"dev": "vite --host --port 8000",
"build": "tsc && vite build"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.4.0",
"@jridgewell/sourcemap-codec": "^1.4.15",
"@line/liff": "^2.22.3",
"@types/lodash": "^4.14.197",
"@types/node": "^20.2.5",
"antd": "^5.11.3",
"axios": "^1.5.0",
"bootstrap": "^5.3.0",
"breakpoint-sass": "^3.0.0",
"buffer": "^6.0.3",
"classnames": "^2.3.2",
"date-fns": "^2.30.0",
"dayjs": "^1.11.9",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-unused-imports": "^3.0.0",
"events": "^3.3.0",
"firebase": "^10.5.2",
"gsap": "^3.12.2",
"immer": "^9.0.21",
"lodash": "^4.17.21",
"lz-string": "^1.5.0",
"miragejs": "^0.1.47",
"moment": "^2.29.4",
"nosleep.js": "^0.12.0",
"numeral": "^2.0.6",
"path": "^0.12.7",
"pure-react-carousel": "^1.30.1",
"react": "^18.2.0",
"react-countdown": "^2.3.5",
"react-device-detect": "^2.2.3",
"react-dom": "^18.2.0",
"react-draggable": "^4.4.5",
"react-fast-marquee": "^1.6.0",
"react-fitty": "^1.0.1",
"react-iframe": "^1.8.5",
"react-infinite-scroll-component": "^6.1.0",
"react-query": "^3.39.3",
"react-router-dom": "^6.15.0",
"react-scroll": "^1.8.9",
"react-spinners": "^0.13.8",
"react-timer-hook": "^3.0.7",
"react-use": "^17.4.0",
"rxjs": "^7.8.1",
"sass": "^1.66.1",
"string-width": "^6.1.0",
"styled-components": "^5.3.6",
"swiper": "^8.4.5",
"ts-key-enum": "^2.0.12",
"use-immer": "^0.9.0",
"use-onclickoutside": "^0.4.1",
"use-reducer-async": "^2.1.1",
"uuid": "^9.0.0",
"vconsole": "^3.15.1"
},
"devDependencies": {
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@types/styled-components": "^5.1.26",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react-swc": "^3.3.2",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"typescript": "^5.0.2",
"vite": "^4.4.5"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

View File

@ -0,0 +1,242 @@
import { INetResponse } from "@/Engine/CatanEngine/NetManagerV2/Core/INetResponse";
import CSSettingsV3 from "@/FormTable/CSSettingsV3";
import { confirmModalObj } from "@/UI/MessageBox/ConfirmModalContext";
import { modalObj } from "@/UIControl/ModalContext";
import { Cocos } from "@/assets/VueScript/Cocos";
import { CommonEventCallBack, JackpotPoolCallBack } from "@/assets/VueScript/CocosVueScript";
import GameData_Cocos from "@/assets/VueScript/share/GameData_Cocos";
import { chatLog, chatMessage, setChatBanTime } from "@/components/ModalContent/ChatRoomModal/chatUtils";
import Config from "@/define/Config";
import Player from "@/modules/player";
import UserData from "@/modules/player/UserData";
import { State } from "@/modules/player/define/data";
import { ILineShareData, ModalContentType } from "@/types";
import { CommonEventType } from "@/utils";
import { NumberEx } from "@/utils/Number/NumberEx";
import { activityComSync, backpackInfo, friendAllowList, friendDenyList, profileInfo, txnCenter, txnNew, txnTrade, txnUserAdd, vipInfo } from "@/utils/setRPCData";
import MainControl from "../MainControl/MainControl";
import CSMessage from "../Message/CSMessage";
import CSResource from "../ResourceItem/CSResource";
import IResourceItem from "../ResourceItem/IResourceItem";
export default class MainControlData {
/**
Normal = 0, //一般斷線or登出
RpcError = 1, //RPC錯誤
SystemError = 2, //系統錯誤
UserError = 3, //使用者造成的錯誤
DbaError = 4, //query時回傳錯誤
SendBufferError = 5, //儲存傳送資料的buff錯誤
Kick = 6, //指定踢除
ConnectLimit = 7, //連線限制
RequireHardLimit = 8, //機台請求逾時
SignalWallet = 9,
RepeatLogin = 101, //重覆登入
LoginFailed = 102, //登入失敗
AppVerError = 103, //Client版本過舊
Maintain = 104, //維護中
Ban = 105, //帳號鎖定
SlotClose = 106, //機台關閉
ResourceWait = 107, //使用者資料尚未寫入
IsDelete = 108, // 被刪除的帳號
Delete = 109, // 刪除主動斷線
SafeDisconnect = 201, // 安全離線
*/
private _disconnectErrorType: number = null;
constructor() {
Player.DataReceivedEvent.AddCallback(this._dataReceivedEvent, this);
}
private _dataReceivedEvent(param: any[] = null): void {
let type: MainControl.DataType = param[0];
let data: any = param[1];
switch (type) {
case MainControl.DataType.ServerData:
this._serverData(data);
break;
case MainControl.DataType.NetDisconnected:
this._netDisconnected();
break;
default:
break;
}
}
// =======================================================================================
/** SERVER主動通知 */
private _serverData(resp: INetResponse<any>): void {
if (resp.IsValid) {
switch (resp.Method) {
case "activity_com.sync": {
activityComSync(resp.Data);
break;
}
case "backpack.info": {
backpackInfo(resp.Data);
break;
}
case "chat.ban": {
setChatBanTime(resp.Data);
break;
}
case "chat.message": {
chatMessage(resp.Data);
break;
}
case "chat.log": {
chatLog(resp.Data);
break;
}
case "friend.allow_list": {
friendAllowList(resp.Data);
break;
}
case "friend.deny_list": {
friendDenyList(resp.Data);
break;
}
case "game.share": {
const { openLineShareGame } = confirmModalObj;
const data: ILineShareData = {
slotID: resp.Data[0],
winMilt: resp.Data[1]
};
openLineShareGame(data);
break;
}
case "game.sync": {
Cocos.VicKing_Bridge.InGameGetUUID = true;
break;
}
case "jackpot.get": {
const { handleOpen, setModalType } = modalObj;
const playerData: State = Player.data.getState();
playerData.account.jackpotGet.push(...resp.Data);
Player.data.setState(playerData);
setModalType(ModalContentType.jackpot);
handleOpen();
break;
}
case "jackpot.pool": {
const playerData: State = Player.data.getState();
if (!Config.IsVite || MainControl.Instance.IsInGame) {
for (let i = 0; i < playerData.game.jackpotPool.length; i++) {
const oldJpData: [gameId: number, maxJPId: number, jp: number] = playerData.game.jackpotPool[i];
let newJpData: [gameId: number, maxJPId: number, jp: number] = null;
for (let j = 0; j < resp.Data.length; j++) {
const [gameId, maxJPId, jp] = resp.Data[j];
if (gameId === oldJpData[0]) {
newJpData = resp.Data[j];
break;
}
}
if (!newJpData) {
JackpotPoolCallBack.DispatchCallback(oldJpData[0], 0);
}
}
}
playerData.game.jackpotPool = resp.Data;
Player.data.setState(playerData);
if (!Config.IsVite || MainControl.Instance.IsInGame) {
for (let i = 0; i < playerData.game.jackpotPool.length; i++) {
const jpData: [gameId: number, maxJPId: number, jp: number] = playerData.game.jackpotPool[i];
JackpotPoolCallBack.DispatchCallback(jpData[0], jpData[2]);
}
} else {
Cocos.CocosEventListener.DispatchCallback(GameData_Cocos.CELT.UpdateJPPool, null);
}
break;
}
case "maintain.info": {
const playerData: State = Player.data.getState();
if (resp.Data) {
resp.Data[3] = NumberEx.plus(MainControl.Instance.NowTime, resp.Data[3]);
playerData.maintain = resp.Data;
CommonEventCallBack.DispatchCallback(CommonEventType.Maintenance, null);
} else {
playerData.maintain = undefined;
}
Player.data.setState(playerData);
break;
}
case "profile.info": {
profileInfo(resp.Data);
break;
}
case "resource.update": {
const resourceItems: IResourceItem[] = CSResource.GetResourceItemsFromServer(resp.Data);
UserData.DoResUpdate(resourceItems);
break;
}
case "resource.bankruptcy": {
Cocos.CocosEventListener.DispatchCallback(GameData_Cocos.CELT.Bankruptcy, null);
break;
}
case "system.disconnect": {
this._disconnectErrorType = +resp.Data["c"];
break;
}
case "txn.new": {
txnNew(resp.Data);
break;
}
case "txn.center": {
txnCenter(resp.Data);
break;
}
case "txn.trade": {
txnTrade(resp.Data);
break;
}
case "txn.user_add": {
txnUserAdd(resp.Data);
break;
}
case "vip.info": {
vipInfo(resp.Data);
break;
}
case "vip.level": {
const playerData: State = Player.data.getState();
playerData.vip.level = resp.Data;
Player.data.setState(playerData);
break;
}
default:
break;
}
} else {
switch (resp.Method) {
case "chat.log": {
chatLog([null, null]);
break;
}
default:
break;
}
}
}
// =======================================================================================
/** SOCKET斷線 */
private _netDisconnected(): void {
console.debug("Disconnected Error Type : " + this._disconnectErrorType);
let str: string = null;
if (this._disconnectErrorType < 10 && this._disconnectErrorType >= 0) {
str = CSSettingsV3.prototype.CommonString(55 + this._disconnectErrorType);
} else if (this._disconnectErrorType > 100 && this._disconnectErrorType < 110) {
str = CSSettingsV3.prototype.CommonString(65 + this._disconnectErrorType - 101);
} else {
str = "Server Disconnected";
}
CSMessage.CreateYesMsg(
str,
this._disconnectedReload
);
}
private _disconnectedReload(): void {
window.location.reload();
}
// =======================================================================================
}

View File

@ -0,0 +1,29 @@
import BaseSingleton from "@/Engine/Utils/Singleton/BaseSingleton";
export class MainControl extends BaseSingleton<MainControl>() {
/** 每次啟動APP */
public IsFirstEnteringLobby: boolean = true;
// /** 登入成功判斷 */
// public IsLogin: boolean = false;
// /** 選桌頁內的機台號碼 */
// public TableID: number = 0;
/** 最後遊玩的一個廠商 */
public LastPlayComponyID: number = 0;
/** 最後遊玩的一個遊戲 */
public LastPlayGameID: number = 0;
// /** 主動斷線=字串.被動斷線=null(因為SERVER有時候會先主動斷線所以有的要預先設定字串)*/
// public IsLogoutStr: string = null;
public IsInGame: boolean = false;
/** 現在時間 */
public get NowTime(): number { return Date.now(); }
}
export module MainControl {
export enum DataType {
ServerData,
ChangeDire,
NetDisconnected,
}
}
export default MainControl;

53
src/Common/Mask/CSMask.ts Normal file
View File

@ -0,0 +1,53 @@
import LoadMaskBase from "./Group/LoadMaskBase";
export class CSMask {
/** 遮罩Dic */
private static _dicMask: Map<CSMask.MaskType, LoadMaskBase> = new Map();
public static Initialize(masks: LoadMaskBase[]): void {
this._dicMask.clear();
for (let i = 0; i < masks.length; i++) {
const mask: LoadMaskBase = masks[i];
this._dicMask.set(mask.MaskType, mask);
}
}
public static GetMask(type: CSMask.MaskType): LoadMaskBase {
if (this._dicMask.has(type)) {
return this._dicMask.get(type);
}
else {
return null;
}
}
public static ShowMask(type: CSMask.MaskType, ...param: any[]): void {
let mask = this.GetMask(type);
if (mask != null) {
mask.Show(...param);
}
}
public static HideMask(type: CSMask.MaskType, ...param: any[]): void {
let mask = this.GetMask(type);
if (mask != null) {
mask.Hide(...param);
}
}
public static HideAllMask(): void {
this._dicMask.forEach((k, type) => {
let mask: LoadMaskBase = this.GetMask(type);
if (mask != null) {
mask.Reset();
}
});
}
}
export module CSMask {
export enum MaskType {
/** Loading */
LoadingMask
}
}
export default CSMask;

View File

@ -0,0 +1,35 @@
import { loadingModalObj } from "@/context/LoadingModalContext";
import CSMask from "../CSMask";
export default abstract class LoadMaskBase {
/** 一般遮罩記數 */
private _count: number = 0;
public get MaskType(): CSMask.MaskType { throw new Error(`請初始化MaskType`); }
onLoad() {
this._count = 0;
}
public Show(...param: any[]): void {
if (this._count == 0) {
const { handleOpen } = loadingModalObj;
handleOpen();
}
this._count++;
}
public Hide(...param: any[]): void {
this._count--;
if (this._count <= 0) {
const { handleClose } = loadingModalObj;
handleClose();
this._count = 0;
}
}
public Reset(): void {
this._count = 0;
const { handleClose } = loadingModalObj;
handleClose();
}
}

View File

@ -0,0 +1,6 @@
import CSMask from "../CSMask";
import LoadMaskBase from "./LoadMaskBase";
export default class LoadingMask extends LoadMaskBase {
public get MaskType(): CSMask.MaskType { return CSMask.MaskType.LoadingMask; }
}

View File

@ -0,0 +1,49 @@
import CSSettingsV3 from "@/FormTable/CSSettingsV3";
import { IConfirmMessageData } from "@/UI/MessageBox/ConfirmMessage";
import { confirmModalObj } from "@/UI/MessageBox/ConfirmModalContext";
import { StringFormKey } from "@/define/formkey";
/** 訊息框相關 */
export default class CSMessage {
public static Record: IConfirmMessageData[] = [];
/** 一個按鈕的訊息框 */
public static CreateYesMsg(content: string, yesCallback: () => void = null, enterStr: string = null, title: string = null, textAlign: "center" | "left" | "right" = null) {
enterStr = enterStr ? enterStr : CSSettingsV3.prototype.CommonString(StringFormKey.String.Confirm);
let data: IConfirmMessageData = {
title: title,
content: content,
isShowCancel: false,
handleConfirm: yesCallback,
enterStr: enterStr,
textAlign: textAlign
};
const { openOtherConfirm } = confirmModalObj;
openOtherConfirm(data);
}
/** 兩個按鈕的訊息框 */
public static CreateYesNoMsg(content: string, yesCallback: () => void = null, noCallback: () => void = null, enterStr: string = null, title: string = null, cancelStr: string = null, textAlign: "center" | "left" | "right" = null) {
enterStr = enterStr ? enterStr : CSSettingsV3.prototype.CommonString(StringFormKey.String.Confirm);
cancelStr = cancelStr ? cancelStr : CSSettingsV3.prototype.CommonString(StringFormKey.String.Cancel);
let data: IConfirmMessageData = {
title: title,
content: content,
isShowCancel: true,
handleConfirm: yesCallback,
handleCancel: noCallback,
enterStr: enterStr,
cancelStr: cancelStr,
textAlign: textAlign
};
const { openOtherConfirm } = confirmModalObj;
openOtherConfirm(data);
}
/** 網路錯誤訊息 */
public static NetError(method: string, state: number, str: string = ""): void {
let error = String.Format("[{0}] state:{1} {2}", method, state, str);
console.debug("網路錯誤訊息: ", error);
}
}

View File

@ -0,0 +1,92 @@
import { ResourceInfo } from "@/define/resource";
import { ActionWithType } from "../../Engine/CatanEngine/CSharp/System/ActionWithType";
import IResourceItem from "./IResourceItem";
import CardCouponItem from "./Items/CardCouponItem";
import LpPointItem from "./Items/LpPointItem";
import MoneyItem from "./Items/MoneyItem";
import { ResourceItemType } from "./ResourceItemType";
class UpdateResourceEvent extends ActionWithType<ResourceItemType, any[]> {
}
export default class CSResource {
private static _event: UpdateResourceEvent;
/** 更新資源事件 */
public static get Event() {
return CSResource._event;
}
public static Initialize(): void {
CSResource._event = new UpdateResourceEvent();
// Cocos.VicKing_Bridge.GetCSResourceEvent = () => { return CSResource._event; };
// Cocos.VicKing_Bridge.AddResourceItemFromServer = (data: JSON) => { return CSResource.AddResourceItemFromServer(data); };
}
/**
* (Server來的)
* @param data ex [1, 15] -->
*/
public static AddResourceItemFromServer(data: ResourceInfo | JSON): IResourceItem {
let item = this.GetResourceItemFromServer(data);
item.AddToResource();
return item;
}
/**
* (Server來的)
* @param data ex: [[1, 15], [2, 30], [28, ["0000123", [124, 3, 0, 0, 0, 0, 0, 0, 0, 1000, 0, 0, 1, 1, 0.000, 0.00, 1, 0, 0, 0, 0, 0, 3]]]] --> [, , ]
*/
public static AddResourceItemsFromServer(data: ResourceInfo[] | JSON): IResourceItem[] {
let itemList: IResourceItem[] = [];
for (let i = 0, count = Object.keys(data).length; i < count; i++) {
let itemData: JSON = data[i];
itemList.push(this.AddResourceItemFromServer(itemData));
}
return itemList;
}
/**
* Server資料還原對應的資源物件
* @param data ex: [1, 15] --> , [2, 30] -->
*/
public static GetResourceItemFromServer(data: ResourceInfo | JSON): IResourceItem {
let type = data[0];
switch (type) {
case ResourceItemType.Money:
return new MoneyItem(data);
case ResourceItemType.Card_Coupon:
return new CardCouponItem(data);
case ResourceItemType.LpPoint:
return new LpPointItem(data);
default:
throw new Error("沒有對應[" + type + "]類型的Server資源類別");
}
}
/**
* Server資料還原對應的資源物件
* @param data ex: [[1, 15], [2, 30], [45, [1, 10], ...] --> [, , , ...]
*/
public static GetResourceItemsFromServer(data: JSON): IResourceItem[] {
let itemList: IResourceItem[] = [];
for (let i = 0, count = Object.keys(data).length; i < count; i++) {
let itemData: JSON = data[i];
itemList.push(this.GetResourceItemFromServer(itemData));
}
return itemList;
}
/**
* ()
* @param items
*/
public static AddResourceItems(items: IResourceItem[]): void {
for (let item of items) {
item.AddToResource();
}
}
// #endregion
}

View File

@ -0,0 +1,16 @@
import { ResourceItemType } from "./ResourceItemType";
interface IResourceItem {
/** 取得資源名稱 */
readonly Name: string;
/** 取得資源類型 */
readonly ResourceType: ResourceItemType;
/** 資源編號 */
readonly ID: number;
/** 數量 */
Count: number;
/** 加入到玩家資源中 */
AddToResource(): void;
}
export default IResourceItem;

View File

@ -0,0 +1,28 @@
import CSSettingsV3 from "@/FormTable/CSSettingsV3";
import { LanguageManager } from "@/FormTableExt/Manage/Language/LanguageManager";
import { ResourceInfo } from "@/define/resource";
import { backpackInfo } from "@/utils/setRPCData";
import CSResource from "../CSResource";
import IResourceItem from "../IResourceItem";
import { ResourceItemType } from "../ResourceItemType";
export default class CardCouponItem implements IResourceItem {
public get Name(): string { return CSSettingsV3.ItemSetting.StringDetail[+CSSettingsV3.ItemSetting.CouponSetting[this.ID].CardName][LanguageManager.GetMsgId()]; }
public get ResourceType(): ResourceItemType { return ResourceItemType.Card_Coupon; }
public ID: number;
public Count: number;
public AddToResource(): void {
backpackInfo([[this.ResourceType, [this.ID, this.Count]]], true);
CSResource.Event.DispatchCallback(ResourceItemType.Card_Coupon, [this.ResourceType, [this.ID, this.Count]]);
}
/** 從db來的格式建立可數資源物件 */
public constructor(db: ResourceInfo | JSON) {
if (!db[1]) {
return;
}
this.ID = db[1][0];
this.Count = <number>db[1][1];
}
}

View File

@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "0cab7ce7-c8b2-4c65-a80b-502bb8eddc53",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "5a6fedda-63d3-4fb7-b8f7-7ca1889cf7db",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -0,0 +1,28 @@
import CSSettingsV3 from "@/FormTable/CSSettingsV3";
import { Shop } from "@/define/formkey";
import { ResourceInfo } from "@/define/resource";
import Player from "@/modules/player";
import { State } from "@/modules/player/define/data";
import { NumberEx } from "@/utils/Number/NumberEx";
import CSResource from "../CSResource";
import IResourceItem from "../IResourceItem";
import { ResourceItemType } from "../ResourceItemType";
export default class LpPointItem implements IResourceItem {
public get Name(): string { return CSSettingsV3.prototype.ShopString(Shop.String.LpPoint); }
public get ResourceType(): ResourceItemType { return ResourceItemType.LpPoint; }
public ID: number;
public Count: number;
public AddToResource(): void {
const playerData: State = Player.data.getState();
playerData.account.lpPoint = NumberEx.plus(playerData.account.lpPoint, this.Count);
Player.data.setState(playerData);
CSResource.Event.DispatchCallback(ResourceItemType.LpPoint, [playerData.account.lpPoint, this.Count]);
}
/** 從db來的格式建立可數資源物件 */
public constructor(db: ResourceInfo | JSON) {
this.Count = <number>db[1];
}
}

View File

@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "db699d15-9603-4466-8ffd-d34fde6abb5f",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -0,0 +1,31 @@
import CSSettingsV3 from "@/FormTable/CSSettingsV3";
import Config from "@/define/Config";
import { Shop } from "@/define/formkey";
import { ResourceInfo } from "@/define/resource";
import Player from "@/modules/player";
import { NumberEx } from "@/utils/Number/NumberEx";
import CSResource from "../CSResource";
import IResourceItem from "../IResourceItem";
import { ResourceItemType } from "../ResourceItemType";
export default class MoneyItem implements IResourceItem {
public get Name(): string { return CSSettingsV3.prototype.ShopString(Shop.String.Money); }
public get ResourceType(): ResourceItemType { return ResourceItemType.Money; }
public ID: number;
public Count: number;
public AddToResource(): void {
const playerData = Player.data.getState();
playerData.account.money = NumberEx.plus(playerData.account.money, this.Count);
Player.data.setState(playerData);
if (Config.ShowMoneyLog) {
console.debug(String.Format("[{0}], {1}, {2}", "Money_" + ResourceItemType.Money, playerData.account.money, this.Count));
}
CSResource.Event.DispatchCallback(ResourceItemType.Money, [playerData.account.money, this.Count]);
}
/** 從db來的格式建立可數資源物件 */
public constructor(db: ResourceInfo | JSON) {
this.Count = <number>db[1];
}
}

View File

@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "e03637c9-24be-42ff-960f-b3377e20efdc",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -0,0 +1,12 @@
export enum ResourceItemType {
/** 錢 */
Money = 1,
/** 等級 */
Level = 2,
/** 卡片 */
Card = 5,
/** 卡片優惠券 */
Card_Coupon = 101,
/** 來亨卷 */
LpPoint = 102,
}

View File

@ -0,0 +1,22 @@
import SystemEventManager from "./SystemEventManager";
/** 系統事件底層 */
export default class SystemEventBase {
constructor() {
SystemEventManager.AddSystem(this);
}
public get name(): string { return this.constructor.name; }
/** 首次進入大廳 */
public ImplementFirstEnteringLobby(): void { }
/** 進入大廳 */
public ImplementEnteringLobby(): void { }
/** 關閉商城頁 */
public ImplementCloseMall(): void { }
/** 離開機台 */
public ImplementLeaveSlot(slotID: number): void { }
}

View File

@ -0,0 +1,34 @@
import SystemEventBase from "./SystemEventBase";
/** 系統事件管理 */
export default class SystemEventManager {
private static systems: Map<string, SystemEventBase> = new Map();
public static AddSystem(system: SystemEventBase): void {
this.systems.set(system.name, system);
}
public static DestroySystem(system: SystemEventBase): void {
this.systems.delete(system.name);
}
/** 首次進入大廳 */
public static FirstEnteringLobby(): void {
SystemEventManager.systems.forEach(system => system.ImplementFirstEnteringLobby());
}
/** 進入大廳 */
public static EnteringLobby(): void {
SystemEventManager.systems.forEach(system => system.ImplementEnteringLobby());
}
/** 關閉商城頁 */
public static CloseMall(): void {
SystemEventManager.systems.forEach(system => system.ImplementCloseMall());
}
/** 離開機台 */
public static LeaveSlot(slotID: number): void {
SystemEventManager.systems.forEach(system => system.ImplementLeaveSlot(slotID));
}
}

View File

@ -0,0 +1,125 @@
/**
* 回呼函數: fnname (arg: TArg): void
*/
interface ActionCallback<TArg> {
(arg: TArg): void;
}
interface Struct<TArg> {
callback: ActionCallback<TArg>;
target: any;
once?: boolean;
}
export class Action<TArg> {
private _queue: Struct<TArg>[] = [];
/**
*
* @param callback 回呼函數: fnname (arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallback(callback: ActionCallback<TArg>, bindTarget?: any) {
let q = <Struct<TArg>>{
callback: callback,
target: bindTarget
};
this._queue.push(q);
}
/**
* ()
* @param callback 回呼函數: fnname (arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallbackOnce(callback: ActionCallback<TArg>, bindTarget?: any) {
let q = <Struct<TArg>>{
callback: callback,
target: bindTarget,
once: true
};
this._queue.push(q);
}
/**
*
* @param callback
*/
RemoveByCallback(callback: ActionCallback<TArg>) {
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);
}
}
}
}
}
}
}

View File

@ -0,0 +1,165 @@
/**
* 回呼函數: fnname (arg: TArg): void
*/
interface ActionCallback<TArg> {
(arg: TArg): void;
}
interface Struct<TType, TArg> {
callback: ActionCallback<TArg>;
target: any;
type: TType;
once?: boolean;
}
export class ActionWithType<TType, TArg> {
private _queue: Struct<TType, TArg>[] = [];
/**
*
* @param callback 回呼函數: fnname (arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallback(type: TType, callback: ActionCallback<TArg>, bindTarget?: any) {
let q = <Struct<TType, TArg>>{
callback: callback,
target: bindTarget,
type: type
};
this._queue.push(q);
}
/**
* ()
* @param callback 回呼函數: fnname (arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallbackOnce(type: TType, callback: ActionCallback<TArg>, bindTarget?: any) {
let q = <Struct<TType, TArg>>{
callback: callback,
target: bindTarget,
type: type,
once: true
};
this._queue.push(q);
}
/**
*
* @param callback
*/
RemoveByCallback(callback: ActionCallback<TArg>) {
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<TArg>) {
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);
}
}
}
}
}
}
}

View File

@ -0,0 +1,165 @@
/**
* 回呼函數: fnname (type: TType, arg: TArg): void
*/
interface ActionCallback<TType, TArg> {
(type: TType, arg: TArg): void;
}
interface Struct<TType, TArg> {
callback: ActionCallback<TType, TArg>;
target: any;
type: TType;
once?: boolean;
}
export class ActionWithType2<TType, TArg> {
private _queue: Struct<TType, TArg>[] = [];
/**
*
* @param callback 回呼函數: fnname (type: TType, arg: TArg): void
* @param bindTarget this綁定的對象
*/
AddCallback(type: TType, callback: ActionCallback<TType, TArg>, bindTarget?: any) {
let q = <Struct<TType, TArg>>{
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<TType, TArg>, bindTarget?: any) {
let q = <Struct<TType, TArg>>{
callback: callback,
target: bindTarget,
type: type,
once: true
};
this._queue.push(q);
}
/**
*
* @param callback
*/
RemoveByCallback(callback: ActionCallback<TType, TArg>) {
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<TType, TArg>) {
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);
}
}
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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": {}
}

View File

@ -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;
}
}

View File

@ -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<any> {
if (this._action) {
this._action();
}
return { done: true, value: undefined };
}
}

View File

@ -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<void> {
* await BaseEnumerator.Init();
* }
*/
export abstract class BaseEnumerator implements IEnumeratorV2 {
public nextEnumerator: BaseEnumerator;
abstract next(value?: any): IteratorResult<any>;
public static isInit: boolean = false;
public static async Init(): Promise<any> {
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<any>): 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<any>[]): 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<any>[]): 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<any>) {
let newclass: any = new SingleEnumeratorClass(iterator);
return newclass;
// return new (require("./SingleEnumerator") as typeof import("./SingleEnumerator")).SingleEnumerator(iterator);
}
export function ParallelEnumerator(...iterators: Iterator<any>[]) {
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);
}
}

View File

@ -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.Timeout = 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;
}
}

View File

@ -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<any> {
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<any>;
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(<Iterator<any>>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(<Iterator<any>>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;
(<Promise<any>>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();
}
}
}

View File

@ -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<any>[]) {
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<any> {
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 };
}
}

View File

@ -0,0 +1,18 @@
import { BaseEnumerator } from "./BaseEnumerator";
export class SingleEnumerator extends BaseEnumerator {
private _iterator: Iterator<any>;
constructor(iterator: Iterator<any>) {
super();
this._iterator = iterator;
}
next(value?: any): IteratorResult<any> {
if (!this._iterator) {
return { done: true, value: undefined };
}
return this._iterator.next(value);
}
}

View File

@ -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<any> {
let delta = value as number;
this._seconds -= delta;
if (this._seconds <= 0) {
return { done: true, value: 0 };
} else {
return { done: false, value: this._seconds };
}
}
}

View File

@ -0,0 +1,133 @@
import { CoroutineV2 } from "./CoroutineV2";
export default class CoroutineExample {
private _obj: Object = { "a": true };
private _obj2: Object = { "b": true };
private _num: number = 3;
/**
*
*/
constructor() {
CoroutineV2.Single(this.Test1_1()).Start();
}
*Test1_1() {
yield null;
yield* this.Test1_2();
// CoroutineV2.Single(this.Test1_3()).Start(this);
yield this.Test1_3();
}
*Test1_2() {
yield null;
}
*Test1_3() {
yield this.Test1_3_1();
yield CoroutineV2.Single(this.Test1_4()).Start(this._obj);
// yield CoroutineV2.Single(this.Test1_4()); //.Start(this);
// yield *this.Test1_4();
console.log("main wait 3");
yield CoroutineV2.WaitTime(2);
console.log("done");
}
*Test1_3_1() {
yield this.Test1_3_2();
yield CoroutineV2.WaitTime(1);
console.log("Test1_3_1.1");
yield CoroutineV2.WaitTime(1);
console.log("Test1_3_1.2");
}
*Test1_3_2() {
yield this.Test1_3_3();
yield CoroutineV2.WaitTime(1);
console.log("Test1_3_2.1");
yield CoroutineV2.WaitTime(1);
console.log("Test1_3_2.2");
yield CoroutineV2.WaitTime(1);
console.log("Test1_3_2.3");
}
*Test1_3_3() {
yield CoroutineV2.WaitTime(1);
console.log("Test1_3_3.1");
yield CoroutineV2.WaitTime(1);
console.log("Test1_3_3.2");
yield CoroutineV2.WaitTime(1);
console.log("Test1_3_3.3");
}
*Test1_4() {
this._num++;
console.log(`WaitTime2 ${this._num}`);
yield CoroutineV2.WaitTime(2).Start(this._obj2);
this._num++;
console.log(`WaitTime2 ${this._num}`);
yield CoroutineV2.WaitTime(2).Start(this._obj2);
this._num++;
console.log(`WaitTime2 ${this._num}`);
}
*Test2_1() {
console.log("111");
CoroutineV2.Single(this.Test2_2()).Start(this);
console.log("333");
}
*Test2_2() {
console.log("222");
return;
}
*Coroutine1(start: number, end: number) {
for (let i = start; i <= end; i++) {
// yield CoroutineV2.WaitTime(1).Start(); // Start()可以省略, 會由外層啟動
// yield CoroutineV2.WaitTime(1).Start(this); // target也可以省略, 由外層的target控制
yield CoroutineV2.WaitTime(1).Start();
console.log(`C1 => ${i}`);
// 嵌套
yield CoroutineV2
.WaitTime(1)
.ThenParallel(
// 再嵌套
CoroutineV2.Action(() => console.log("start parallel")),
this.Coroutine2(10, 2),
this.Coroutine2(20, 2),
)
.ThenAction(() => console.log("end parallel"))
.Start();
// Promise
yield this.loadItemAsync("settings.json");
}
}
*Coroutine2(num: number, repeat: number) {
for (let i = 0; i < repeat; i++) {
//yield CoroutineV2.WaitTime(2);
yield 0;
console.log(`C2: ${num}`);
// yield CoroutineV2.WaitTime(1);
}
}
actionCallback() {
console.log("action callback 2");
}
loadItemAsync(id: string): Promise<{ id: string }> {
return new Promise((resolve) => {
console.log("loading item start:", id);
setTimeout(() => {
resolve({ id: id });
console.log("loading item done:", id);
}, 3000);
});
}
}

View File

@ -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<any>, 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<any>): IEnumeratorV2 {
if (iterator instanceof BaseEnumerator) {
return iterator;
} else {
return new SingleEnumerator(iterator);
}
}
/**
*
*/
export function Parallel(...iterators: Iterator<any>[]): IEnumeratorV2 {
return new ParallelEnumerator(iterators);
}
/**
*
*/
export function Serial(...iterators: Iterator<any>[]): 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);
}
}

View File

@ -0,0 +1,23 @@
export interface IEnumeratorV2 extends Iterator<any> {
Start(target?: any): IEnumeratorV2Started;
Then(iterator: Iterator<any>): IEnumeratorV2;
ThenSerial(...iterators: Iterator<any>[]): IEnumeratorV2;
ThenParallel(...iterators: Iterator<any>[]): IEnumeratorV2;
ThenAction(action: Function, delaySeconds?: number): IEnumeratorV2;
ThenWaitTime(seconds: number): IEnumeratorV2;
}
export interface IEnumeratorV2Started {
readonly Current: any;
Pause(): void;
Resume(): void;
Stop(): void;
}

View File

@ -0,0 +1,15 @@
import { Action } from "../../CSharp/System/Action";
import { INetRequest } from "./INetRequest";
import { INetResponse } from "./INetResponse";
export interface INetConnector {
readonly OnDataReceived: Action<INetResponse<any>>;
readonly OnDisconnected: Action<void>;
readonly IsConnected: boolean;
SendAsync<TRequest, TResponse>(req: INetRequest<TRequest, TResponse>): Iterator<any>;
Send<TRequest, TResponse>(req: INetRequest<TRequest, TResponse>);
Logout();
}

View File

@ -0,0 +1,13 @@
import { INetResponse } from "./INetResponse";
export interface INetRequest<TRequest, TResponse> {
readonly Method: string;
readonly MethodBack: string;
Data: TRequest;
Result: INetResponse<TResponse>;
SendAsync(): Iterator<any>;
Send();
}

View File

@ -0,0 +1,6 @@
export interface INetResponse<TResponse> {
readonly Method: string;
readonly Status: number;
readonly Data: TResponse;
readonly IsValid: boolean;
}

View File

@ -0,0 +1,4 @@
export default class NetConfig {
/** 是否顯示RPC接送JSON的LOG */
public static ShowServerLog: boolean = true;
}

View File

@ -0,0 +1,279 @@
import Event from "@/modules/event";
import { Action } from "../CSharp/System/Action";
import { Encoding } from "../CSharp/System/Text/Encoding";
import { BaseEnumerator } from "../CoroutineV2/Core/BaseEnumerator";
import { INetRequest } from "./Core/INetRequest";
import { INetResponse } from "./Core/INetResponse";
import NetConfig from "./NetConfig";
export namespace Socket {
export const Connect = Symbol("socket.connect");
export const Message = Symbol("socket.message");
export const Disconnect = Symbol("socket.disconnect");
export const Error = Symbol("socket.error");
}
export interface Func {
[Socket.Connect]: () => void,
[Socket.Message]: (e) => void,
[Socket.Disconnect]: () => void,
[Socket.Error]: () => void,
}
export class NetConnector {
/** Event */
public readonly event: Event<Func> = new Event<Func>();
readonly OnDataReceived: Action<INetResponse<any>> = new Action<INetResponse<any>>();
readonly OnDisconnected: Action<void> = new Action<void>();
readonly OnLoadUIMask: Action<boolean> = new Action<boolean>();
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<any, any>) {
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] 傳送server資料: ${req.Method}(${JSON.stringify(req.Data)})`);
} else {
console.debug(`[RPC] 傳送server資料: ${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<any, any>, 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(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(<ArrayBuffer>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 = <string>json[0];
let status = <number>json[1][0];
let data = json[1][1];
let resp = <INetResponse<any>>{
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(Socket.Disconnect);
}
private OnWebSocketError(e: CloseEvent) {
this.event.emit(Socket.Error);
}
}
const ErrorResponse: INetResponse<any> = {
Status: -1,
Method: "",
Data: {},
IsValid: false,
};
class WsConnectEnumerator extends BaseEnumerator {
private _ws: WebSocket;
constructor(ws: WebSocket) {
super();
this._ws = ws;
}
next(value?: any): IteratorResult<any> {
return {
done: this._ws.readyState === WebSocket.OPEN || this._ws.readyState === WebSocket.CLOSED,
value: undefined
};
}
}
class WsRequestEnumerator extends BaseEnumerator {
readonly MethodBack: string;
private _req: INetRequest<any, any>;
private _done: boolean = false;
constructor(req: INetRequest<any, any>) {
super();
this._req = req;
this.MethodBack = req.MethodBack;
}
SetResponse(resp: INetResponse<any>) {
this._req.Result = resp;
this._done = true;
}
next(value?: any): IteratorResult<any> {
return {
done: this._done,
value: undefined
};
}
}

View File

@ -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<any, any>) {
this.CheckConnector();
this._connector.Send(req);
}
/**
* Server,
* @param req
*/
static SendAsync(req: INetRequest<any, any>, mask: boolean) {
this.CheckConnector();
return this._connector.SendAsync(req, mask);
}
private static CheckConnector() {
if (!this._connector) throw new Error("請先呼叫CasinoNetManager.Initialize()初始化connector");
}
}

View File

@ -0,0 +1,28 @@
import { VueNetConnector } from "@/assets/VueScript/Net/VueNetConnector";
import { INetRequest } from "./Core/INetRequest";
export abstract class NetRequest<TResquest, TResponse> implements INetRequest<TResquest, TResponse> {
abstract get Method(): string;
get MethodBack(): string {
return this.Method;
}
Data: TResquest;
Result: import("./Core/INetResponse").INetResponse<TResponse>;
/**
* Cocos會收到SERVER主動通知
* Cocos會收到SERVER主動通知
* Cocos會收到SERVER主動通知
*/
SendAsync(mask: boolean = false): Iterator<any> {
// return NetManager.SendAsync(this, mask);
return VueNetConnector.SendAsync(this, mask);
}
Send() {
// NetManager.Send(this);
VueNetConnector.Send(this);
}
}

View File

@ -0,0 +1,4 @@
export interface ITableJson {
cols: string[],
rows: any[],
}

View File

@ -0,0 +1,10 @@
export interface ITableRow {
Id: number;
}
/**
*
*/
export class WithoutRow implements ITableRow {
Id: number;
}

View File

@ -0,0 +1,30 @@
import { ITableRow } from "./ITableRow";
export abstract class TableBase<TRow extends ITableRow> extends Array<TRow> {
constructor() {
super();
Object.setPrototypeOf(this, new.target.prototype);
}
/** 欄位數量 */
public get Count(): number {
return this.length;
}
/** 取得全部鍵值 */
public get Keys(): string[] {
return Object.keys(this);
}
/** 取得全部欄位值 */
public get Rows(): Array<TRow> {
return Object["values"](this);
}
// public get Rows(): Array<TRow> { return this; }
/** 是否包含該Id值的欄位 */
public ContainsRow(id: number): boolean {
return id in this;
}
}

View File

@ -0,0 +1,13 @@
import { TableManager } from "../TableManager";
import { StringExampleTableRow, StringTableExample } from "./Tables/StringTableExample";
export default class CSSettingsV3Example {
private static _stringExample: StringTableExample;
/** 共用_字串表#string.xlsx */
public static get StringExample(): StringTableExample {
return this._stringExample = this._stringExample || TableManager.InitTable("#string", StringTableExample, StringExampleTableRow);
}
}

View File

@ -0,0 +1,78 @@
import CSSettingsV3Example from "./CSSettingsV3Example";
import { StringExampleTable } from "./Tables/StringTableExample";
export default class TableUseExample {
start() {
// #region StringExample表
console.log("----------------#stringExample");
console.log(CSSettingsV3Example.StringExample instanceof StringExampleTable); // true
console.log(Array.isArray(CSSettingsV3Example.StringExample)); // true, 所以Array相關的方法都可以拿來操作
console.log(CSSettingsV3Example.StringExample.length);
console.log(CSSettingsV3Example.StringExample.Count); // 跟length一樣
console.log(CSSettingsV3Example.StringExample.ContainsRow(11)); // 是否包含id=11的Row
console.log(11 in CSSettingsV3Example.StringExample); // 同上
console.log(CSSettingsV3Example.StringExample[1].MsgZnCh);
console.log(CSSettingsV3Example.StringExample[1]["MsgZnCh"]); // 同上
console.log(CSSettingsV3Example["StringExample"][1]["MsgZnCh"]); // 同上
console.log("----------------");
for (let row of CSSettingsV3Example.StringExample) {
if (row) { // 如果Row沒有連號, 那有可能取到undefined值, 要先判斷, 不想判斷就用 CSSettings.StringExample.Rows
console.log(row.Id, row.MsgZnCh);
}
}
console.log("----------------");
for (let id of CSSettingsV3Example.StringExample.Keys) {
console.log(id); // 只會列出有值的id, undefined會跳過
}
console.log("----------------");
for (let row of CSSettingsV3Example.StringExample.Rows) {
console.log(row.Id, row.MsgZnCh); // 只會列出有值的Row, undefined會跳過
}
// #endregion
// #region StringExample表 #StringFilter表
console.log("----------------#stringExample#string_filter");
// console.log(CSSettings.StringExample.StringFilter instanceof StringFilterTable); // true
console.log(Array.isArray(CSSettingsV3Example.StringExample.StringFilter)); // true, 所以Array相關的方法都可以拿來操作
console.log(CSSettingsV3Example.StringExample.StringFilter.length);
console.log(CSSettingsV3Example.StringExample.StringFilter.Count); // 跟length一樣
console.log(CSSettingsV3Example.StringExample.StringFilter.ContainsRow(11)); // 是否包含id=11的Row
console.log(11 in CSSettingsV3Example.StringExample.StringFilter); // 同上
console.log(CSSettingsV3Example.StringExample.StringFilter[1].FilterWord);
console.log(CSSettingsV3Example.StringExample.StringFilter[1]["FilterWord"]); // 同上
console.log(CSSettingsV3Example["StringExample"]["StringFilter"][1]["FilterWord"]); // 同上
console.log("----------------");
for (let row of CSSettingsV3Example.StringExample.StringFilter) {
if (row) { // 如果Row沒有連號, 那有可能取到undefined值, 要先判斷, 不想判斷就用 CSSettings.StringExample.StringFilter.Rows
console.log(row.Id, row.FilterWord);
}
}
console.log("----------------");
for (let id of CSSettingsV3Example.StringExample.StringFilter.Keys) {
console.log(id); // 只會列出有值的id, undefined會跳過
}
console.log("----------------");
for (let row of CSSettingsV3Example.StringExample.StringFilter.Rows) {
console.log(row.Id, row.FilterWord); // 只會列出有值的Row, undefined會跳過
}
// #endregion
console.log("----------------");
// CSSettingsV3.ResetTables(); // 重置表
}
}

View File

@ -0,0 +1,50 @@
import { ITableRow } from "../../Core/ITableRow";
import { TableBase } from "../../Core/TableBase";
import { TableManager } from "../../TableManager";
/**
* _字串表#string.xlsx
* ##, ##
*/
export class StringTableExample extends TableBase<StringExampleTableRow> {
private _stringFilter: StringFilterTable;
/** 共用_字串表#string.xlsx > #string_filter */
public get StringFilter(): StringFilterTable {
return this._stringFilter = this._stringFilter || TableManager.InitTable("#string#string_filter", StringFilterTable, StringFilterTableRow);
}
}
/**
* #string
*/
export class StringExampleTable extends TableBase<StringExampleTableRow> {
}
export class StringExampleTableRow implements ITableRow {
/** 編號 */
Id: number;
/** 英文訊息 */
MsgEn: string;
/** 繁體中文訊息 */
MsgZnTw: string;
/** 簡體中文讯息 */
MsgZnCh: string;
/** 越南文讯息 */
MsgVi: string;
/** 泰文讯息 */
MsgTh: string;
}
/**
* #string_filter
*/
export class StringFilterTable extends TableBase<StringFilterTableRow> {
}
export class StringFilterTableRow implements ITableRow {
/** 編號 */
Id: number;
/** 過濾字串 */
FilterWord: string;
}

View File

@ -0,0 +1,53 @@
import { ITableJson } from "./Core/ITableJson";
import { ITableRow } from "./Core/ITableRow";
export class TableManager {
private static _tableJsons: { [key: string]: ITableJson } = {};
public static AddJsonAssets(jsonAssets: JSON[]) {
if (!jsonAssets) return;
const newAssets: JSON[] = jsonAssets.concat();
for (const jsonAsset of newAssets) {
this.AddJsonAsset(jsonAsset);
}
}
public static AddJsonAsset(jsonAsset: JSON) {
if (!jsonAsset) {
return;
}
for (const tableName in jsonAsset) {
console.debug(`TableV3 [${ tableName }] json loaded`);
this._tableJsons[tableName] = jsonAsset[tableName];
}
}
public static GetTable(name: string): ITableJson {
return this._tableJsons[name];
}
public static InitTable<T extends Array<ITableRow>>(
name: string,
tableType: { new(): T },
rowType: { new(): ITableRow },
): T {
const json = this._tableJsons[name];
if (!json) {
return null;
// throw new Error(`TableV3 [${name}] 尚未載入json檔`);
}
const table = new tableType();
const cols = json.cols;
const colLength = cols.length;
const rows = json.rows;
for (const r of rows) {
const trow = new rowType();
for (let i = 0; i < colLength; i++) {
trow[cols[i]] = r[i];
}
table[trow.Id] = trow;
}
// console.log(`TableV3 [${name}] init done`);
return table;
}
}

View File

@ -0,0 +1,57 @@
/**
* (EX記錄音效開關)
*/
export default class LocalStorageData {
private static _instance: LocalStorageData = null;
public static get Instance(): LocalStorageData {
return LocalStorageData._instance;
}
constructor() {
LocalStorageData._instance = this;
}
// =======================================================================================
//
public get CompileVersion(): string { return cc.sys.localStorage.getItem("CompileVersion"); }
public set CompileVersion(value: string) { cc.sys.localStorage.setItem("CompileVersion", value.toString()); }
//
public get RemoteVerList(): string { return cc.sys.localStorage.getItem("RemoteVerList"); }
public set RemoteVerList(value: string) { cc.sys.localStorage.setItem("RemoteVerList", value); }
//
public get LocalVerList(): string { return cc.sys.localStorage.getItem("LocalVerList"); }
public set LocalVerList(value: string) { cc.sys.localStorage.setItem("LocalVerList", value); }
//
public get ComboDeviceID(): string { return cc.sys.localStorage.getItem("ComboDeviceID") || ""; }
public set ComboDeviceID(value: string) { cc.sys.localStorage.setItem("ComboDeviceID", value); }
//
public get BundleUrl(): string { return cc.sys.localStorage.getItem("BundleUrl"); }
public set BundleUrl(value: string) { cc.sys.localStorage.setItem("BundleUrl", value); }
//
public get Language(): string { return cc.sys.localStorage.getItem("language"); }
public set Language(value: string) { cc.sys.localStorage.setItem("language", value); }
//
public get MusicType(): string { return cc.sys.localStorage.getItem("MusicType"); }
public set MusicType(value: string) { cc.sys.localStorage.setItem("MusicType", value); }
//
public get SoundType(): string { return cc.sys.localStorage.getItem("SoundType"); }
public set SoundType(value: string) { cc.sys.localStorage.setItem("SoundType", value); }
//
public get LvUpNotifyType(): boolean { return JSON.parse(cc.sys.localStorage.getItem("LvUpNotifyType")); }
public set LvUpNotifyType(value: boolean) { cc.sys.localStorage.setItem("LvUpNotifyType", JSON.stringify(value)); }
//
public get WinNotifyType(): boolean { return JSON.parse(cc.sys.localStorage.getItem("WinNotifyType")); }
public set WinNotifyType(value: boolean) { cc.sys.localStorage.setItem("WinNotifyType", JSON.stringify(value)); }
//
public get DownloadList_Preview(): string { return cc.sys.localStorage.getItem("DownloadList_Preview"); }
public set DownloadList_Preview(value: string) { cc.sys.localStorage.setItem("DownloadList_Preview", value); }
//
public get AutoLogin(): number { return Number.parseInt(cc.sys.localStorage.getItem("AutoLogin")); }
public set AutoLogin(value: number) { cc.sys.localStorage.setItem("AutoLogin", value); }
//
public get GameInfoData(): string[] { return JSON.parse(cc.sys.localStorage.getItem("GameInfoData")); }
public set GameInfoData(value: string[]) { cc.sys.localStorage.setItem("GameInfoData", JSON.stringify(value)); }
//
public get LoginDays(): string[] { return JSON.parse(cc.sys.localStorage.getItem("LoginDays")); }
public set LoginDays(value: string[]) { cc.sys.localStorage.setItem("LoginDays", JSON.stringify(value)); }
}

192
src/Engine/Timer/Timer.ts Normal file
View File

@ -0,0 +1,192 @@
import { NumberEx } from "@/utils/Number/NumberEx";
import { CoroutineV2 } from "../CatanEngine/CoroutineV2/CoroutineV2";
import { ActionWithType } from "../CatanEngine/CSharp/System/ActionWithType";
class TimerEvent extends ActionWithType<number, any> { }
/**
* (使CoroutineV2)
*/
export class Timer {
//#region private
/** 訊息資料 */
private static Group: Map<any, TimerDataClass> = new Map<any, TimerDataClass>();
//#endregion
//#region static
/**
*
* @param {number} time (seconds)
* @param {Function} callback Function
* @param {any} type () 西
* @param {any} bindTarget () this綁定的對象
* @example
* Timer.Start(1, () => { console.log(`example`); });
* Timer.Start(1, () => { console.log(`example`); }, "example");
* Timer.Start(1, () => { console.log(`example`); }, "example", this);
*/
public static Start(time: number, callback: Function, bindTarget?: any, type?: any): void {
let self: typeof Timer = this;
let thisType: any = type;
if (!type) {
thisType = callback;
}
if (Timer.Group.has(thisType)) {
console.error(`Timer Start Error Timer.Group.has(${thisType})`);
return;
}
let timerData: TimerDataClass = new TimerDataClass(thisType, time, callback, bindTarget);
Timer.Group.set(thisType, timerData);
let CoroutineFN: () => IterableIterator<any> = function* (): IterableIterator<any> {
yield CoroutineV2.WaitTime(time).Start(bindTarget);
if (Timer.Group.has(thisType)) {
self._callback(timerData.Type, timerData.Callback, timerData.BindTarget);
}
};
CoroutineV2.Single(CoroutineFN()).Start(bindTarget);
}
/**
* By Target
* @param {any} target target
* @example
* Timer.ClearByTarget(this);
*/
public static ClearByTarget(target: any): void {
let timerDataGroup: TimerDataClass[] = [];
Timer.Group.forEach(timerData => {
if (timerData.BindTarget === target) {
timerDataGroup.push(timerData);
}
});
if (timerDataGroup.length === 0) {
console.warn(`Timer Clear Error Timer.Group.has not target`);
return;
}
for (let i: number = 0; i < timerDataGroup.length; i++) {
let timerData: TimerDataClass = timerDataGroup[i];
let type: any = timerData.Type;
Timer.Group.delete(type);
timerData = null;
}
CoroutineV2.StopCoroutinesBy(target);
}
/**
* By Type
* @param PS
* @param {any} type type
* @example
* Timer.ClearByType("example");
*/
public static ClearByType(type: any): void {
let timerDataGroup: TimerDataClass[] = [];
Timer.Group.forEach(timerData => {
if (timerData.Type === type) {
timerDataGroup.push(timerData);
}
});
if (timerDataGroup.length === 0) {
console.warn(`Timer Clear Error Timer.Group.has not type`);
return;
}
for (let i: number = 0; i < timerDataGroup.length; i++) {
let timerData: TimerDataClass = timerDataGroup[i];
let type: any = timerData.Type;
Timer.Group.delete(type);
}
}
/**
* callback
* @param {Function} callback Function
*/
private static _callback(type: any, callback: Function, bindTarget: any): void {
if (Timer.Group.has(type)) {
Timer.Group.delete(type);
}
if (bindTarget) {
callback.bind(bindTarget)();
} else {
callback();
}
}
/**
* ( updateTime秒跑一次fn)
* @param startNum index
* @param endNum index
* @param updateTime
* @param callbackfn
* @example
* let startNum: number = 10;
* let endNum: number = 0;
* let updateTime: number = 1;
* yield CoroutineV2.Single(Timer.Timing(
* startNum,
* endNum,
* updateTime,
* (x: number) => {
* console.log(`x: ${x}`);
* }
* )).Start(this);
*/
public static *Timing(startNum: number, endNum: number, updateTime: number, callbackfn: Function): IterableIterator<any> {
let isIncrease: boolean = endNum >= startNum;
let totalCount: number = Math.abs(endNum - startNum) + 1;
let nowCount: number = NumberEx.divide(totalCount, updateTime);
let diff: number = NumberEx.divide(totalCount, nowCount) * (isIncrease ? 1 : -1);
let tempScore: number = startNum;
callbackfn(startNum);
while (true) {
if (endNum !== tempScore) {
yield CoroutineV2.WaitTime(updateTime);
tempScore += diff;
// 遞增
if (isIncrease && tempScore > endNum) {
tempScore = endNum;
}
// 遞減
if (!isIncrease && tempScore < endNum) {
tempScore = endNum;
}
callbackfn(Math.floor(tempScore));
} else {
break;
}
}
}
//#endregion
}
//#region Class
/** Timer資料 */
export class TimerDataClass {
/** Type */
public Type: any = null;
/** Time */
public Time: number = null;
/** Callback */
public Callback: Function = null;
/** BindTarget */
public BindTarget?: any = null;
constructor(type: any, time: number, callback: Function, bindTarget?: any) {
this.Type = type;
this.Time = time;
this.Callback = callback;
this.BindTarget = bindTarget;
}
}
// //#endregion

View File

@ -0,0 +1,5 @@
export default class CSAudio {
private static _instance: CSAudio = null;
public static get Instance(): CSAudio { return this._instance; }
public AddClipsInfo(clips: Map<number, cc.AudioClip>, pathes: Map<number, string>): void { }
}

120
src/Engine/Utils/PSPS.ts Normal file
View File

@ -0,0 +1,120 @@
//#region Class
import { CoroutineV2 } from "../../Engine/CatanEngine/CoroutineV2/CoroutineV2";
/** 表演節目序列處理系統(playShow Sequence Processing System) */
export default class PSPS {
//#region public
public ShowData: ShowDataClass[] = [];
public IsRun: boolean = false;
// /** 可以插隊時間 */
// public CanCutInLineTime: number = null;
//#endregion
//#region private
private _playShowFunction: (data: any) => IterableIterator<any> = null;
//#endregion
//#region Lifecycle
/**
* (PlayShow Sequence Processing System)
* @param playShowFunction
* @example
* let CoroutineFunction: (data: any) => IterableIterator<any> = function* (data: any): IterableIterator<any> {}
* new PSPS(this.CoroutineFunction.bind(this));
*/
constructor(playShowFunction: (data: any) => IterableIterator<any>) {
this.SetPlayShowFunction(playShowFunction);
}
public SetPlayShowFunction(playShowFunction: (data: any) => IterableIterator<any>): void {
this._playShowFunction = playShowFunction;
}
//#endregion
//#region playShow
/** 增加表演資料 */
public PushPlayShowData(data: any, priority: number = 0): void {
const playShowData: ShowDataClass = new ShowDataClass(data, priority);
this.ShowData.push(playShowData);
this.ShowData.ObjectSort([true], ["Priority"]);
if (!this.IsRun) {
CoroutineV2.Single(this._performanceShowData()).Start(this);
}
}
/** 表演 */
private *_performanceShowData(): IterableIterator<any> {
this.IsRun = true;
if (this._playShowFunction) {
const showData: ShowDataClass = this.ShowData.shift();
const data: any = showData.Data;
yield* this._playShowFunction(data);
}
if (this.ShowData.length > 0) {
CoroutineV2.Single(this._performanceShowData()).Start(this);
return;
}
this.StopPerformance();
}
/** 停止表演 */
public StopPerformance(): void {
this.IsRun = false;
CoroutineV2.StopCoroutinesBy(this);
}
public ClearFromPriority(priority: any): void {
let deleteDatas: ShowDataClass[] = [];
for (let i: number = 0; i < this.ShowData.length; i++) {
const showData: ShowDataClass = this.ShowData[i];
if (showData.Priority === priority) {
deleteDatas.push(showData);
}
}
for (let i: number = 0; i < deleteDatas.length; i++) {
const deleteData: ShowDataClass = deleteDatas[i];
for (let j: number = 0; j < this.ShowData.length; j++) {
const showData: ShowDataClass = this.ShowData[j];
if (showData.Priority === deleteData.Priority) {
this.ShowData.splice(j, 1);
break;
}
}
}
}
/** 清除所有表演 */
public ClearPerformance(): void {
this.IsRun = false;
this.ShowData = [];
CoroutineV2.StopCoroutinesBy(this);
}
//#endregion
}
/** ShowDataClass */
export class ShowDataClass {
/** 優先度(越低越前面) */
public Priority: number = 0;
/** Data */
public Data: any = null;
constructor(data: any, priority: number) {
this.Data = data;
this.Priority = priority;
}
}
//#endregion

View File

@ -0,0 +1,27 @@
/**
* (new在使用)
* @example
* export default class Test extends BaseSingleton<Test>() { ...... }
* new Test();
* Test.Instance.Init();
*/
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default function BaseSingleton<T>() {
class BaseSingleton {
public constructor() {
if ((<any>this)._instance == null) {
BaseSingleton._instance = <any>this;
}
}
private static _instance: BaseSingleton = null;
public static get Instance(): T {
return (<any>this)._instance;
}
/** 銷毀 */
public Destroy(): void {
(<any>this)._instance = null;
}
}
return BaseSingleton;
}

7
src/UI/Game.tsx Normal file
View File

@ -0,0 +1,7 @@
const Game = () => {
return (
<>Game</>
);
};
export default Game;

7
src/UI/Lobby.tsx Normal file
View File

@ -0,0 +1,7 @@
const Lobby = () => {
return (
<>Lobby</>
);
};
export default Lobby;

53
src/UI/Login.tsx Normal file
View File

@ -0,0 +1,53 @@
import { BusinessEnum } from "@/_BusinessTypeSetting/BusinessTypeSetting";
import { useGameItems } from "@/context/GameItemsContext";
import { Button, Cascader } from "antd";
import React, { useState } from "react";
interface Option {
value: string;
label: string;
children?: Option[];
}
const Login = () => {
const { onLoad } = useGameItems();
const serverType: typeof BusinessEnum.ServerType = BusinessEnum.ServerType;
const [type, setType] = useState<number>(BusinessEnum.ServerType.Internal_Dev);
const [isLogin, setIsLogin] = useState<boolean>(false);
const options: Option[] = [];
for (let i = 0, names: string[] = Object.keys(serverType); i < names.length; i++) {
const key: string = names[i];
if (!Number.isNaN(+key)) {
options.push({
value: key,
label: serverType[key],
});
}
}
async function login() {
setIsLogin(true);
await onLoad(type);
}
return (
<>{!isLogin &&
<div style={boxStyle}>
<Cascader defaultValue={[BusinessEnum.ServerType[BusinessEnum.ServerType.Internal_Dev]]} options={options} onChange={(v: string[]) => setType(+v[0])} />
<Button type="primary" onClick={login}></Button>
</div>
}</>
);
};
export default Login;
const boxStyle: React.CSSProperties = {
width: "100%",
height: "100vh",
borderRadius: 6,
border: "1px solid #40a9ff",
display: "flex",
alignItems: "center",
justifyContent: "center",
};

View File

@ -0,0 +1,255 @@
export module BusinessEnum {
export enum BusinessType {
Type1 = "LP1",
Type2 = "LP2"
}
export enum ServerType {
/** 外版 */
Out = 2,
/** 內版開發(內網&4G) */
Internal_Dev = 5,
/** 外部商業DEMO(B2B) */
Out_B2B = 6,
/** QA */
QA = 7,
/** Test */
Test = 8
}
export enum LogoType {
/** 完美(目前只有WEB) */
// WM = 1
}
}
/**
@explain .GIT並設定新測試環境
@explain
*/
export default class BusinessTypeSetting {
/** 產品商業類別字串(組合判斷用) */
public static readonly TYPE_BUSINESS: string = BusinessEnum.BusinessType.Type1;
/** 必要JSON載入結束 */
public static GetLoadInitEnd(): boolean {
return this.SetLoadInitEnd;
}
public static SetLoadInitEnd: boolean = false;
/**
* ProductEnum.ServerType
* @description Cocos掛載Loading的面板去設定ServerType
*/
public static UseServerType: BusinessEnum.ServerType = BusinessEnum.ServerType[import.meta.env.VITE_Deploy] as unknown as BusinessEnum.ServerType;
/** 連線IP(網頁版會接網址參數所以要多開變數直接指定) */
public static UseHost: string = BusinessTypeSetting.GetHostUrl(BusinessTypeSetting.UseServerType);
/** 連接阜(網頁版會接網址參數所以要多開變數直接指定) */
public static UsePort: number = BusinessTypeSetting.GetPortNum(BusinessTypeSetting.UseServerType);
/** 資源伺服器網址 */
public static UsePatch: string = BusinessTypeSetting.GetPatchUrl(BusinessTypeSetting.UseServerType);
/** 靜態伺服器網址 */
public static UseDownloadUrl: string = BusinessTypeSetting.GetDownloadUrl(BusinessTypeSetting.UseServerType);
/** Line Liff */
public static UseLiffID: string = BusinessTypeSetting.GetLiffID(BusinessTypeSetting.UseServerType);
/** Line Liff */
public static UseLineID: string = BusinessTypeSetting.GetLineID(BusinessTypeSetting.UseServerType);
/** 編譯版本 */
public static get COMPILE_VERSION(): string {
return BusinessTypeSetting.SET_COMPILE_VERSION;
}
public static SET_COMPILE_VERSION: string = null;
// =======================================================================================
/** 網頁測試讀取對應資源的位置 */
public static readonly FolderUrl: string = "shared/";
public static readonly FolderUrlImg: string = "shared/img/";
public static readonly FolderUrlBg: string = "shared/bg/";
public static readonly FolderUrlJson: string = "shared/jsons/";
public static readonly FolderUrlTxt: string = "shared/txt/";
public static readonly FolderUrlLoading: string = "shared/loading/";
public static readonly FolderUrlMp3: string = "shared/";
/** Line */
public static readonly LineFriendUrl: string = "https://line.me/R/ti/p/@";
/** 原始Liff開啟方式(電腦也支援) */
public static readonly LiffUrl: string = "https://liff.line.me/";
/** 電腦開會導向Line官網 */
public static readonly LiffUrlTypeA: string = "https://line.me/R/app/";
/** 電腦無法打開 */
public static readonly LiffUrlTypeB: string = "line://app/";
/**
* PATH資原路徑
* @param type ()
* @returns
*/
public static GetPatchUrl(type: BusinessEnum.ServerType): string {
switch (type) {
case BusinessEnum.ServerType.Out:
return "https://patch.lybobet.com/";
case BusinessEnum.ServerType.Internal_Dev:
return "https://patch-dev.lybobet.com/";
case BusinessEnum.ServerType.Out_B2B:
return "https://patch-b2b.lybobet.com/";
case BusinessEnum.ServerType.QA:
return "https://patch-qa.lybobet.com/";
case BusinessEnum.ServerType.Test:
return "https://patch-testing.lybobet.com/";
}
}
/**
* IP
* @param type
* @returns
*/
public static GetHostUrl(type: BusinessEnum.ServerType): string {
switch (type) {
case BusinessEnum.ServerType.Out:
return "https://game.lybobet.com";
case BusinessEnum.ServerType.Internal_Dev:
return "https://dev.lybobet.com";
case BusinessEnum.ServerType.QA:
return "https://qa.lybobet.com";
case BusinessEnum.ServerType.Test:
return "https://testing.lybobet.com";
case BusinessEnum.ServerType.Out_B2B:
return "https://b2b.lybobet.com";
}
}
/**
*
* @param type
* @returns
*/
public static GetPortNum(type: BusinessEnum.ServerType): number {
switch (type) {
case BusinessEnum.ServerType.Out:
return 9005;
case BusinessEnum.ServerType.Internal_Dev:
return 9005;
case BusinessEnum.ServerType.QA:
return 9005;
case BusinessEnum.ServerType.Test:
return 9005;
case BusinessEnum.ServerType.Out_B2B:
return 9005;
}
}
public static GetDownloadUrl(type: BusinessEnum.ServerType): string {
switch (type) {
case BusinessEnum.ServerType.Out:
return "https://static.lybobet.com/";
case BusinessEnum.ServerType.Internal_Dev:
return "https://static-dev.lybobet.com/";
case BusinessEnum.ServerType.QA:
return "https://static-qa.lybobet.com/";
case BusinessEnum.ServerType.Test:
return "https://static-testing.lybobet.com/";
case BusinessEnum.ServerType.Out_B2B:
return "https://static-b2b.lybobet.com/";
}
}
public static GetUploadUrl(type: BusinessEnum.ServerType): string {
let port: string = ":9080";
switch (type) {
case BusinessEnum.ServerType.Internal_Dev: {
let url: string = this.GetHostUrl(type);
url = url.replace("http://", "");
url = url.replace("https://", "");
return "https://static-" + url + port;
}
default:
return this.GetHostUrl(type) + port;
}
}
public static GetLiffID(type: BusinessEnum.ServerType): string {
switch (type) {
case BusinessEnum.ServerType.Out:
return "1657864491-kA7gnVMp";
case BusinessEnum.ServerType.Internal_Dev:
return "1657713613-we8Gk929";
case BusinessEnum.ServerType.QA:
return "1657864462-xM7dgPGK";
case BusinessEnum.ServerType.Test:
return "1657864500-N3YEgz6p";
case BusinessEnum.ServerType.Out_B2B:
return "1657864484-YeqWEV9O";
}
}
public static GetLineID(type: BusinessEnum.ServerType): string {
switch (type) {
case BusinessEnum.ServerType.QA:
case BusinessEnum.ServerType.Test:
case BusinessEnum.ServerType.Out:
return "070hdlum";
case BusinessEnum.ServerType.Internal_Dev:
return "349pbusa";
case BusinessEnum.ServerType.Out_B2B:
return "114pcwux";
}
}
// =======================================================================================
}
export enum FileType {
PNG = ".png",
JPG = ".jpg",
MP3 = ".mp3",
TXT = ".txt",
}
export enum FolderName {
Logo = "Logo/",
SlotImg = "SlotImg/",
Avatar = "Avatar/",
Report = "report/",
PA = "pa/",
LobbyAd = "lobby_ad/",
CoverAd = "inter_ad/",
Activity = "Activity/",
Game = "game/",
Dlygo = "dlygo/",
Event = "event/",
}

View File

@ -0,0 +1,45 @@
import { BusinessEnum } from "@/_BusinessTypeSetting/BusinessTypeSetting";
import { IGameItems } from "@/types";
import { LineTools } from "@/utils/LineTools";
import { ReactNode, createContext, useContext, useState } from "react";
type GameItemsProviderProps = {
children: ReactNode;
};
const GameItemsContext = createContext<IGameItems>(undefined);
export function useGameItems() {
return useContext(GameItemsContext);
}
export let gameObj: IGameItems = null;
export function GameItemsProvider({ children }: GameItemsProviderProps) {
const [gameId, setGameId] = useState<number>(null);
const game: IGameItems = gameObj = {
onLoad,
gameId,
setGameId
};
async function onLoad(serverType: BusinessEnum.ServerType) {
await Promise.all([
// // 設定執行環境
// setBusinessType(),
// // 設定LineTools環境
// await setLineTools(),
]);
}
/** 設定LineTools環境 */
async function setLineTools() {
await LineTools.onLoad();
}
return (
<GameItemsContext.Provider value={game}>
{children}
</GameItemsContext.Provider>
);
}

5
src/index.css Normal file
View File

@ -0,0 +1,5 @@
body {
margin: 0;
padding: 0;
height: 100vh;
}

38
src/index.tsx Normal file
View File

@ -0,0 +1,38 @@
import "@/FormTableExt/TableExt/CSSettingsV3Ext";
import { GameItemsProvider } from "@/context/GameItemsContext";
import "@/utils/ArrayExtension";
import "@/utils/NumberExtension";
import "@/utils/String";
import type { Router } from "@remix-run/router";
import dayjs from "dayjs";
import "dayjs/locale/zh-tw";
import ReactDOM from "react-dom/client";
import { RouterProvider, createHashRouter } from "react-router-dom";
import { BaseEnumerator } from "./Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator";
import Game from "./UI/Game";
import Lobby from "./UI/Lobby";
import Login from "./UI/Login";
import "./index.css";
BaseEnumerator.Init();
dayjs.locale("zh-tw");
const hashRouter: Router = createHashRouter([
{
path: "/",
element: <Login />,
},
{
path: "/lobby",
element: <Lobby />,
},
{
path: "/game/:id",
element: <Game />,
},
]);
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<GameItemsProvider>
<RouterProvider router={hashRouter} />
</GameItemsProvider>
);

7
src/types/index.ts Normal file
View File

@ -0,0 +1,7 @@
import { BusinessEnum } from "@/_BusinessTypeSetting/BusinessTypeSetting";
export interface IGameItems {
onLoad: (serverType: BusinessEnum.ServerType) => Promise<void>;
gameId: number;
setGameId: (gameId: number) => void;
}

140
src/utils/ArrayExtension.ts Normal file
View File

@ -0,0 +1,140 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
declare interface Array<T> {
/**
*
* @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
/**
*
* PS. pass by value
*/
Copy(): T[]
/**
* 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[]): T[]
/**
* Array<cc.Component.EventHandler>forHoldButton使用
* Add a none persistent listener to the UnityEvent.
* @param call Callback function.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
AddListener(call: Function): void
}
Array.prototype.ExRemoveAt ||
Object.defineProperty(Array.prototype, "ExRemoveAt", {
enumerable: false,
value: function (index: number): any {
const 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.Copy ||
Object.defineProperty(Array.prototype, "Copy", {
enumerable: false,
value: function (): any[] {
return Array.from(this);
},
});
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;
}
}
const 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;
},
});

565
src/utils/LineTools.ts Normal file
View File

@ -0,0 +1,565 @@
import BusinessTypeSetting from "@/_BusinessTypeSetting/BusinessTypeSetting";
import liff from "@line/liff";
/**
* Line工具
* @doc https://developers.line.biz/en/docs/messaging-api/message-types
*/
export class LineTools {
//#region Lifecycle
public static async onLoad(): Promise<void> {
const hasToken: boolean = location.search.includes("token=") || location.hash.includes("token=");
if (hasToken) {
return;
}
await LineTools.init();
const isLoggedIn: boolean = await LineTools.checkLogin();
if (!isLoggedIn) {
await LineTools.login();
return;
}
console.debug(`[Line] Line is Login`);
}
//#endregion
//#region Custom
public static GetAccessToken(): string {
const accessToken: string = liff.getAccessToken();
return accessToken;
}
//#endregion
//#region FriendShip
/** 確認是否加官方賬號好友 */
public static async CheckAddFriend(): Promise<boolean> {
const ans: boolean = (await liff.getFriendship()).friendFlag;
return ans;
}
//#endregion
//#region Custom Message
/**
* Text message
* @param {string} text Message text. Max character limit: 5000
* @doc https://developers.line.biz/en/reference/messaging-api/#text-message
* @example LineTools.MakeText("Hello, world");
*/
public static async MakeText(text: string): Promise<boolean> {
if (!text) {
return false;
}
const data: any[] = [{
"type": "text",
"text": text
}];
return await LineTools._sendMessages(data);
}
/**
* Text message
* @deprecated
* @param {string} text Message text. Max character limit: 5000
* @doc https://developers.line.biz/en/reference/messaging-api/#text-message
* @LINE_Emoji https://developers.line.biz/en/docs/messaging-api/emoji-list/#line-emoji-definitions
* @example LineTools.MakeText("Hello, world");
*/
public static async MakeTextEmoji(text: string): Promise<boolean> {
if (!text) {
return false;
}
const data: any[] = [{
"type": "text",
"text": "$$$ LINE emoji",
"emojis": [
{
"index": 0,
"productId": "5ac21a8c040ab15980c9b43f",
"emojiId": "004"
},
{
"index": 1,
"productId": "5ac21a8c040ab15980c9b43f",
"emojiId": "001"
},
{
"index": 2,
"productId": "5ac21a8c040ab15980c9b43f",
"emojiId": "025"
}
]
}];
return await LineTools._sendMessages(data);
}
/**
* Sticker message
* @param packageId Package ID for a set of stickers. For information on package IDs
* @param stickerId Sticker ID. For a list of sticker IDs for stickers that can be sent with the Messaging API
* @param isAnim isAnim
* @doc https://developers.line.biz/en/reference/messaging-api/#audio-message
* @example LineTools.MakeSticker(26162, 505588336, true);
*/
public static async MakeSticker(packageId: string, stickerId: string, isAnim: boolean = false): Promise<boolean> {
if (!packageId || !stickerId) {
return false;
}
let pngtype: string = "";
if (isAnim) {
pngtype = "/IOS/sticker@2x.png";
} else {
pngtype = "/IOS/sticker_animation@2x.png";
}
const data: any[] = [{
"type": "template",
"altText": "Sticker",
"template": {
"type": "image_carousel",
"columns": [{
"imageUrl": "https://stickershop.line-scdn.net/stickershop/v1/sticker/" + stickerId + pngtype,
"action": {
"type": "uri",
"uri": "line://shop/sticker/detail/" + packageId
}
}]
}
}];
return await LineTools._sendMessages(data);
}
/**
* Image message
* @param {string} originalContentUrl Image URL (Max character limit: 2000) HTTPS over TLS 1.2 or later JPEG or PNG Max image size: No limits Max file size: 10 MB
* @param {string} previewImageUrl Preview image URL (Max character limit: 2000) HTTPS over TLS 1.2 or later JPEG or PNG Max image size: No limits Max file size: 1 MB
* @doc https://developers.line.biz/en/reference/messaging-api/#image-message
* @example LineTools.MakeImage("https://example.com/original.jpg", "https://example.com/preview.jpg");
*/
public static async MakeImage(originalContentUrl: string, previewImageUrl: string = originalContentUrl): Promise<boolean> {
if (!originalContentUrl) {
return false;
}
const data: any[] = [{
"type": "image",
"originalContentUrl": originalContentUrl,
"previewImageUrl": previewImageUrl
}];
return await LineTools._sendMessages(data);
}
/**
* Video message
* If the video isn't playing properly, make sure the video is a supported file type and the HTTP server hosting the video supports HTTP range requests.
* @param {string} originalContentUrl URL of video file (Max character limit: 2000) HTTPS over TLS 1.2 or later mp4 Max file size: 200 MB
* @param {string} previewImageUrl URL of preview image (Max character limit: 2000) HTTPS over TLS 1.2 or later JPEG or PNG Max file size: 1 MB
* @doc https://developers.line.biz/en/reference/messaging-api/#video-message
*/
public static async MakeVideo(originalContentUrl: string, previewImageUrl: string): Promise<boolean> {
if (!originalContentUrl || !previewImageUrl) {
return false;
}
const data: any[] = [{
"type": "video",
"originalContentUrl": originalContentUrl,
"previewImageUrl": previewImageUrl
}];
return await LineTools._sendMessages(data);
}
/**
* Audio message
* @param {string} originalContentUrl URL of audio file (Max character limit: 2000) HTTPS over TLS 1.2 or later m4a Max file size: 200 MB
* @param {number} duration Length of audio file (milliseconds)
* @doc https://developers.line.biz/en/reference/messaging-api/#audio-message
*/
public static async MakeAudio(originalContentUrl: string, duration: number = 60000): Promise<boolean> {
if (!originalContentUrl) {
return false;
}
const data: any[] = [{
"type": "audio",
"originalContentUrl": originalContentUrl,
"duration": duration
}];
return await LineTools._sendMessages(data);
}
/**
* Buttons template
* @param altText Alternative text. When a user receives a message, it will appear as an alternative to the template message in the notification or chat list of their device. Max character limit: 400
* @param thumbnailImageUrl Image URL (Max character limit: 2,000) HTTPS over TLS 1.2 or later JPEG or PNG Max width: 1024px Max file size: 10 MB
* @param title Title Max character limit: 40
* @param text Message text Max character limit: 160 (no image or title) Max character limit: 60 (message with an image or title)
* @param defaultAction Action when image, title or text area is tapped.
* @param actions Action when tapped Max objects: 4
* @param imageAspectRatio rectangle: 1.51:1
* @param imageAspectRatio square: 1:1
* @param imageSize cover: The image fills the entire image area. Parts of the image that do not fit in the area are not displayed.
* @param imageSize contain: The entire image is displayed in the image area. A background is displayed in the unused areas to the left and right of vertical images and in the areas above and below horizontal images.
* @doc https://developers.line.biz/en/reference/messaging-api/#buttons
*/
public static async MakeTemplate(altText: string, thumbnailImageUrl: string, title: string, text: string, defaultAction: any = null, actions: any[] = [], imageAspectRatio: string = "square", imageSize: string = "cover"): Promise<boolean> {
if (actions.length === 0) {
return false;
}
const data: any[] = [{
"type": "template",
"altText": altText,
"template": {
"type": "buttons",
"thumbnailImageUrl": thumbnailImageUrl,
"imageAspectRatio": imageAspectRatio,
"imageSize": imageSize,
"title": title,
"text": text
}
}];
if (defaultAction) {
data["defaultAction"] = defaultAction;
// data["defaultAction"] = {
// "type": "uri",
// "label": "View detail",
// "uri": "http://example.com/page/123"
// };
}
if (actions) {
data["actions"] = actions;
// data["actions"] = [{
// "type": "uri",
// "label": "立即玩",
// "uri": "https://liff.line.me/1657713613-we8Gk929"
// }];
}
return await LineTools._sendMessages(data);
}
//#region SendImageTemplate
/**
* MeProfile
* @param altText Alternative text. When a user receives a message, it will appear as an alternative to the template message in the notification or chat list of their device. Max character limit: 400
* @param text Message text Max character limit: 160 (no image or title) Max character limit: 60 (message with an image or title)
* @simulator https://developers.line.biz/flex-simulator/
*/
public static async MakeImageTemplate(altText: string, text?: string): Promise<boolean> {
let data: any[] = [{
"type": "flex",
"altText": altText,
"contents":
{
"type": "bubble",
"size": "giga",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "image",
"url": "https://scontent.ftpe8-3.fna.fbcdn.net/v/t39.30808-6/313961998_137927952344738_4107082514038089088_n.jpg?stp=dst-jpg_p526x296&_nc_cat=111&ccb=1-7&_nc_sid=730e14&_nc_ohc=3jmqu3srEAYAX9drH1H&_nc_ht=scontent.ftpe8-3.fna&oh=00_AfDe36ZgvY6aqmN3nge4Fmw9ZGuOwxdS5fj9eAMLe6wtBg&oe=63A4C088",
"size": "full",
"aspectMode": "cover"
}
],
"paddingAll": "none"
},
"action": {
"type": "uri",
"label": "action",
"uri": `https://liff.line.me/${BusinessTypeSetting.UseLiffID}}`
}
}
}];
if (text) {
data[0].contents.body.contents.push({
"type": "text",
"text": text,
"size": "md",
"align": "center",
"color": "#0000FF",
"margin": "50px",
"gravity": "center",
"offsetBottom": "20px"
});
}
return await LineTools._sendMessages(data);
}
//#endregion
//#region MakeShareBigWinGame
/**
*
* @param {number} slotID
* @param {number} ratio
* @param {string} altText
* @param {string} text
* @param {string} btnText
* @param {string} btnUrl
*/
public static async MakeShareBigWinGame(slotID: number, ratio: number, altText: string, text: string, btnText: string, ...param: any[]): Promise<boolean> {
text = String.Format(text, ratio);
let data: any[] = [{
"type": "flex",
"altText": altText,
"contents":
{
"type": "bubble",
"size": "mega",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "image",
"url": `${BusinessTypeSetting.UseDownloadUrl}game/${slotID}/s`,
"position": "absolute",
"offsetTop": "11%",
"offsetStart": "71%",
"size": "75px"
},
{
"type": "image",
"url": `${BusinessTypeSetting.UsePatch}shared/img/LineShareUI/ShareAward01.png`,
"size": "full",
"aspectRatio": "20:13",
"aspectMode": "cover"
}
],
"paddingAll": "none"
},
{
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": text,
"weight": "bold",
"size": "md",
"align": "center",
"wrap": true
}
],
"margin": "xl"
}
],
"paddingAll": "none"
},
"footer": {
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "button",
"style": "link",
"height": "sm",
"action": {
"type": "uri",
"label": btnText,
"uri": `https://line.me/R/app/${BusinessTypeSetting.UseLiffID}/?gamein=${slotID}`
}
}
],
"flex": 0
},
"action": {
"type": "uri",
"label": btnText,
"uri": `https://line.me/R/app/${BusinessTypeSetting.UseLiffID}/?gamein=${slotID}`
}
}
}];
return await LineTools._sendMessages(data);
}
//#endregion
//#region SelfProfile
/**
* MeProfile
* @simulator https://developers.line.biz/flex-simulator/
*/
public static async MeProfile(): Promise<void> {
const altText: string = "立即玩爆機娛樂城";
// eslint-disable-next-line @typescript-eslint/typedef
liff.getProfile().then(function (profileData): void {
let statusMessage: string = profileData.statusMessage ?? "";
if (statusMessage.length > 60) {
statusMessage = "Status Message is to long! Max 60 words";
}
const data: any[] = [
{
"type": "flex",
"altText": altText,
"contents":
{
"type": "bubble",
// "size": "giga",
"size": "kilo",
"body": {
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "image",
"url": profileData.pictureUrl,
"size": "full",
"aspectMode": "cover"
},
{
"type": "box",
"layout": "vertical",
"contents": [
{
"type": "text",
"text": profileData.displayName,
"weight": "bold",
"size": "lg",
"margin": "lg"
},
{
"type": "text",
"text": statusMessage,
"size": "sm",
"margin": "md",
"wrap": true
}
],
"paddingEnd": "5%",
"paddingStart": "5%"
},
{
"type": "text",
"text": "立即玩",
"size": "md",
"align": "center",
"color": "#0000FF",
"margin": "50px",
"gravity": "center",
"offsetBottom": "20px"
}
],
"paddingAll": "none"
},
"action": {
"type": "uri",
"label": "action",
"uri": `https://liff.line.me/${BusinessTypeSetting.UseLiffID}`
}
}
}
];
LineTools._sendMessages(data);
}).catch(function (error: any): void {
alert(`[Line] Failed to getProfile: \n${error}`);
});
}
public static async GetLineProfile(): Promise<any> {
return await liff.getProfile();
}
//#endregion
/**
*
* @param {SendMessagesParams} messages
* @param {boolean} isMultiple If you set the isMultiple property to true, the user can select multiple message recipients in the target picker. If you set it to false, the user can select only one friend as the message recipient. The default value is true.
*/
private static async _sendMessages(messages: any[], isMultiple: boolean = true): Promise<boolean> {
// 這邊是為了防止token過期
const isLoggedIn: boolean = await LineTools.checkLogin();
if (!isLoggedIn) {
await LineTools.login();
}
let isSuccess: boolean = false;
if (liff.isApiAvailable("shareTargetPicker")) {
try {
const res = await liff.shareTargetPicker(messages, { isMultiple: isMultiple });
if (res) {
if (res.status === "success") {
isSuccess = true;
console.log(`[Line] 分享成功`);
} else {
console.error(`[Line] 分享失敗: \n${JSON.stringify(res)}`);
}
} else {
console.log(`[Line] 分享取消`);
}
} catch (error) {
console.error(`[Line] Failed to launch ShareTargetPicker: \n${error}`);
}
} else {
alert("[Line] 你的 LINE App 暫時不支援 Share Target Picker");
}
return isSuccess;
}
private static sleep(ms: any): Promise<any> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
private static async init(): Promise<void> {
const liffId: string = BusinessTypeSetting.UseLiffID;
const myLIFF_STORE: Object = LineTools.getLIFF_STORE();
await liff.init({ liffId: liffId });
LineTools.setLIFF_STORE(myLIFF_STORE);
}
private static async checkLogin(): Promise<boolean> {
let isLoggedIn: boolean = liff.isLoggedIn();
try {
const a = await liff.getProfile();
} catch (error) {
isLoggedIn = false;
}
return isLoggedIn;
}
private static async login(): Promise<void> {
if (liff.isInClient()) {
liff.login({ redirectUri: parent.location.href });
} else {
const search: string = location.search ? location.search : location.hash;
const callbackURL: string = BusinessTypeSetting.UsePatch + "/" + search;
liff.login({ redirectUri: callbackURL });
}
}
private static getLIFF_STORE(): Object {
const LIFF_STORE: Object = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key.includes("LIFF_STORE")) {
LIFF_STORE[key] = localStorage.getItem(key);
}
}
return LIFF_STORE;
}
private static setLIFF_STORE(LIFF_STORE: Object) {
for (let i = 0, keys = Object.keys(LIFF_STORE); i < keys.length; i++) {
const key = keys[i];
const item = LIFF_STORE[key];
localStorage.setItem(key, item);
}
}
}

View File

@ -0,0 +1,181 @@
import { CoroutineV2 } from "@/Engine/CatanEngine/CoroutineV2/CoroutineV2";
import { RandomEx } from "./RandomEx";
export module NumberEx {
/**
*
* @param startNum
* @param endNum
* @param callbackfn
* @param toInt (FALSE)
*/
export function* ChangeScore(startNum: number, endNum: number, callbackfn: (num: number) => void, sec: number, toInt: boolean = true) {
let fps = 30;
let waitTime = 0.03;
let changeRate = sec * fps;
changeRate = changeRate - 1 <= 0 ? changeRate : changeRate - 1;
changeRate = 1 / changeRate;
let diff = endNum - startNum;
let isIncrease = endNum >= startNum;
let tempScore = startNum;
let counter = 0;
while (true) {
if (endNum != tempScore) {
tempScore += diff * changeRate;
// 遞增
if (isIncrease && tempScore > endNum) {
tempScore = endNum;
}
// 遞減
if (!isIncrease && tempScore < endNum) {
tempScore = endNum;
}
if (toInt) {
callbackfn(tempScore.ExToInt());
} else {
callbackfn(tempScore);
}
counter++;
yield CoroutineV2.WaitTime(waitTime);
}
else {
callbackfn(endNum);
break;
}
}
}
/**
* (時間內循環EX:1~4=>1.2.3.4.1.2.3.4.1.2.3.4...)
* @param minNum
* @param maxNum
* @param callbackfn callbackfn
* @param sec
*/
export function* BeatScore(minNum: number, maxNum: number, endNum: number, callbackfn: (num: number) => void, sec: number): IterableIterator<any> {
let fps: number = 13;
let waitTime: number = 0.07;
let changeRate: number = sec * fps; // -1為了讓changeRate數字能混亂點
changeRate = changeRate - 1 <= 0 ? changeRate : changeRate - 1;
changeRate = 1 / changeRate;
let diff: number = maxNum - minNum;
let isIncrease: boolean = maxNum >= minNum;
let tempScore: number = minNum;
let counter: number = 0;
let randomRate: number = 0;
let lastNum: number = minNum;
let nowNum: number = minNum;
while (true) {
if (maxNum !== tempScore) {
if (counter % 2 === 0) {
if (isIncrease) {
randomRate = RandomEx.GetFloat(0, diff * changeRate).ExToNumFloorDecimal(2);
} else {
randomRate = RandomEx.GetFloat(0, -diff * changeRate).ExToNumFloorDecimal(2);
}
} else {
randomRate = -randomRate;
}
tempScore += diff * changeRate + randomRate;
// 遞增
if (isIncrease && tempScore > maxNum) {
tempScore = maxNum;
}
// 遞減
if (!isIncrease && tempScore < maxNum) {
tempScore = maxNum;
}
while (nowNum === lastNum) {
nowNum = RandomEx.GetInt(minNum, maxNum + 1);
}
lastNum = nowNum;
callbackfn(nowNum);
// yield null;
counter++;
yield CoroutineV2.WaitTime(waitTime);
} else {
callbackfn(endNum);
break;
}
}
}
/**是否进行边界检查 */
let _boundaryCheckingState = false;
/**
*
* @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);
}
}

View File

@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "363f5f7f-0623-4013-8571-0bb5c1dc95e6",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -0,0 +1,92 @@
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];
}
}
}

View File

@ -0,0 +1,10 @@
{
"ver": "1.1.0",
"uuid": "ba4dee5b-ca5b-4435-a068-c4f5dd832bab",
"importer": "typescript",
"isPlugin": false,
"loadPluginInWeb": true,
"loadPluginInNative": true,
"loadPluginInEditor": false,
"subMetas": {}
}

View File

@ -0,0 +1,192 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
declare interface Number {
/**
* (), 2
* 41,038,560.00
* @param precision
* @param isPadZero (FALSE)
* */
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位小數2200.2.00
* @param precision
* @param isPadZero (FALSE)
*/
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 = false) {
// let arr = String(this).split('.');
const 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) {
/** 千 */
const MONEY_1K: number = 1000;
/** 萬 */
// let MONEY_10K: number = 10000;
/** 十萬 */
// let MONEY_100K: number = 100000;
/** 百萬 */
const MONEY_1M: number = 1000000;
/** 千萬 */
// let MONEY_10M: number = 10000000;
/** 億 */
// let MONEY_100M: number = 100000000;
/** 十億 */
const 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) {
const str = this.toPrecision(12);
const 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 = false) {
// 取小數點第X位
const 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;
}
});

View File

@ -0,0 +1,99 @@
import BusinessTypeSetting, { BusinessEnum } from "@/_BusinessTypeSetting/BusinessTypeSetting";
import { initializeApp } from "firebase/app";
import { getMessaging, getToken } from "firebase/messaging";
// Public Key:
// BPxB0rLAHuETo-CFw6xe2_ZlQ8qm6WAg-i45UTStiYYU0pPYR1wuO0jwt8S_gRl_hYFNoI1l0l4vFksncHC5yUs
// Private Key:
// 2-dDtLj8ibIOj51RD9ASV3DtTjkmFQyn_zId3V4OtwA
const applicationServerPublicKey: string = `BPxB0rLAHuETo-CFw6xe2_ZlQ8qm6WAg-i45UTStiYYU0pPYR1wuO0jwt8S_gRl_hYFNoI1l0l4vFksncHC5yUs`;
const swjsPath: string = "./assets/sw.js";
export function PWAOnLoad() {
if (BusinessTypeSetting.UseServerType !== BusinessEnum.ServerType.Internal_Dev) {
return;
}
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register(swjsPath)
.then((reg: ServiceWorkerRegistration) => {
console.debug("Service Worker Registered");
askForNotificationPermission();
setFCM(reg);
});
}
}
/**
*
* @url https://stackoverflow.com/questions/50399170/what-bearer-token-should-i-be-using-for-firebase-cloud-messaging-testing
*/
function setFCM(reg: ServiceWorkerRegistration) {
// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
apiKey: "AIzaSyAZ1Hkr-tmF5xqnNWy0jprXDy1xEZUyYy8",
authDomain: "jm-webpush.firebaseapp.com",
projectId: "jm-webpush",
storageBucket: "jm-webpush.appspot.com",
messagingSenderId: "903093229309",
appId: "1:903093229309:web:54a3006e1e1afdeaefd094",
measurementId: "G-SBKR7HBFBB"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
// const analytics = getAnalytics(app);
// Initialize Firebase Cloud Messaging and get a reference to the service
const messaging = getMessaging(app);
// Add the public key generated from the console here.
getToken(messaging, { vapidKey: applicationServerPublicKey, serviceWorkerRegistration: reg }).then((currentToken) => {
if (currentToken) {
// Send the token to your server and update the UI if necessary
console.debug(`[Test] Firebase Token: ${currentToken}`);
} else {
// Show permission request UI
console.debug("[Test] No registration token available. Request permission to generate one.");
// ...
}
}).catch((err) => {
console.debug("[Test] An error occurred while retrieving token. ", err);
// ...
});
}
export function askForNotificationPermission() {
if ("Notification" in window) {
// console.log("Notification permission default status:", Notification.permission);
Notification.requestPermission(function (result: NotificationPermission) {
// 這裡result只會有兩種結果一個是用戶允許(granted),另一個是用戶封鎖(denied)
// console.log("Notification permission status:", status);
if (result !== "granted") {
console.log("No notification permission granted!");
} else {
displayConfirmNotification();
}
});
}
}
function displayConfirmNotification() {
// pushNotification("成功訂閱!! (from Service Worker)", "您已成功訂閱我們的推播服務!");
}
export function pushNotification(title: string, content: string) {
if ("serviceWorker" in navigator && Notification.permission == "granted") {
const options = {
body: content,
icon: "../img/jpg/logo512.jpg"
};
// navigator.serviceWorker.ready.then(function (swreg: ServiceWorkerRegistration) {
navigator.serviceWorker.getRegistration(swjsPath).then(function (swreg: ServiceWorkerRegistration) {
swreg.showNotification(title, options);
});
}
}

20
src/utils/String.ts Normal file
View File

@ -0,0 +1,20 @@
declare global {
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) => {
const value = args[index];
if (value === null || value === undefined) return "";
return "" + value;
});
};
export {};

48
src/utils/catan.ts Normal file
View File

@ -0,0 +1,48 @@
declare namespace cc { }
declare namespace cc._decorator {
// export function ccclass(name?: string): Function;
// export function ccclass(_class?: Function): void;
}
declare let CC_PREVIEW: boolean;
namespace cc._decorator {
// export function ccclass(name?: string): Function { return () => { }; }
export const ccclass: any = undefined;
}
namespace cc {
export class path {
static basename(pathStr: string, extname?: string): any { }
}
export class sys {
static localStorage: any;
}
export class AudioClip {
name: string;
}
export class Prefab {
}
export class Asset {
constructor(name?: string) { }
name: string;
}
export class assetManager {
static loadRemote<T extends cc.Asset>(url: string, onComplete: (err: Error, asset: T) => void): void { }
}
export class Node extends Asset {
getChildByName(name: string): Node { return undefined; }
ExAddChild(childObj: cc.Prefab | cc.Node, childActive?: boolean): cc.Node { return undefined; }
setParent(value: Node): void { }
active: boolean;
children: Node[];
}
export function log(msg: string | any, ...subst: any[]): void {
console.log(msg, ...subst);
}
export function warn(msg: string | any, ...subst: any[]): void {
console.warn(msg, ...subst);
}
export function error(msg: string | any, ...subst: any[]): void {
console.error(msg, ...subst);
}
}

47
src/utils/iframeUtils.ts Normal file
View File

@ -0,0 +1,47 @@
import { CSSProperties } from "react";
import { sleep } from ".";
/**
* iframe
* @param parent
* @param url
* @param style CSSProperties
*/
export function createIframe(parent: string, url: string, style?: CSSProperties): HTMLIFrameElement {
const iframe: HTMLIFrameElement = document.createElement("iframe");
iframe.src = url;
iframe.frameBorder = "0";
iframe.scrolling = "0";
for (let i: number = 0, keys: string[] = Object.keys(style); i < keys.length; i++) {
const key: string = keys[i];
iframe.style[key] = style[key];
}
document.getElementById(parent).appendChild(iframe);
return iframe;
}
/**
* iframe
* @param parent
*/
export async function destroyIframe(parent: string) {
const iframeContent: HTMLElement = document.getElementById(parent);
for (let i = 0; i < iframeContent.children.length; i++) {
const iframeNode: HTMLIFrameElement = iframeContent.children[i] as HTMLIFrameElement;
try {
iframeNode.src = "about:blank";
await sleep(10);
const iframeWindow = iframeNode.contentWindow;
iframeWindow.document.open();
iframeWindow.document.write("");
iframeWindow.document.clear && iframeWindow.document.clear();
iframeWindow.document.close();
} catch (error) {
console.warn(error);
}
iframeNode.remove();
iframeNode.parentNode && iframeContent.removeChild(iframeNode);
// Cocos.CocosEventListener.RemoveAllCallbacks();
// await sleep(2000);
}
}

652
src/utils/index.ts Normal file
View File

@ -0,0 +1,652 @@
import { ResourceItemType } from "@/Common/ResourceItem/ResourceItemType";
import { BaseEnumerator } from "@/Engine/CatanEngine/CoroutineV2/Core/BaseEnumerator";
import { TableManager } from "@/Engine/CatanEngine/TableV3/TableManager";
import CSSettingsV3 from "@/FormTable/CSSettingsV3";
import { ShopMycardTableRow, ShopShow2TableRow } from "@/FormTable/Tables/ShopTable";
import { Cocos } from "@/assets/VueScript/Cocos";
import { CocosVueScript } from "@/assets/VueScript/CocosVueScript";
import GameData_Cocos from "@/assets/VueScript/share/GameData_Cocos";
import { FriendRequest } from "@/define/Request/FriendRequest";
import { TxnRequest } from "@/define/Request/TxnRequest";
import { VIPLevelMapForChat } from "@/map";
import Player from "@/modules/player";
import { UserBindFlag } from "@/modules/player/define/userbind_flag";
import { ChatRoomRole, Games, ItemCodeList, ItemPropsType, ItemSize, PriceList, SelectorItemProps, StringContentType, TagTypes, TxnCenterData } from "@/types";
import liff from "@line/liff";
import axios, { AxiosResponse } from "axios";
import { isMobile } from "react-device-detect";
import * as Scroll from "react-scroll";
import stringWidth from "string-width";
import BusinessTypeSetting, { FolderName } from "../_BusinessTypeSetting/BusinessTypeSetting";
export const transArray = <T>(array: T[], split: number): T[][] => {
const newArr = [];
const copiedArr = array ? array?.slice() : [];
const length = copiedArr?.length;
for (let i = 0, j = 0; i < length; i += split, j++) {
newArr[j] = copiedArr.splice(0, split);
}
return newArr;
};
/** 去掉英文+百分比% */
export function onlyNumber(stringText: string): number {
const str = +stringText.replace(/[A-Za-z%]/g, "");
return str;
}
export function wordsLimit(limit: number, string: string) {
const length = string?.length;
const isOverLimit = length > limit;
const result = isOverLimit ? "..." : "";
return string?.substring(0, limit) + result;
}
export const capitalize = (str: string) =>
`${str.charAt(0).toUpperCase()}${str.slice(1)}`;
export function checkForUnique(str: string): boolean {
const chineseCharacterMatch =
/[\p{Unified_Ideograph}\u3006\u3007][\ufe00-\ufe0f\u{e0100}-\u{e01ef}]?/gmu;
const arr = str.match(chineseCharacterMatch);
if (arr === null) return true;
return !hasDuplicates(arr);
}
export function onlyChinese(str: string) {
const chineseCharacterMatch =
/[\p{Unified_Ideograph}\u3006\u3007][\ufe00-\ufe0f\u{e0100}-\u{e01ef}]?/gmu;
const arr = str.match(chineseCharacterMatch);
return arr.join("");
}
export function hasDuplicates(array: string[]): boolean {
return new Set(array).size !== array.length;
}
export function generatePriceList(
arr: [ID: number, ProductId: number, ShowMoney: number][],
items: { [id: string]: [ID: number, price: number] },
priceRef: ShopMycardTableRow[],
itemCodeList?: Map<number, ItemCodeList>,
): PriceList[] {
if (itemCodeList) {
arr = arr.filter((item) => itemCodeList.has(item[0]));
}
const MyCardPriceRefMap = new Map<number, number>(
priceRef.map((item) => [item.Id, item.Price]),
);
const newArr: [ID: number, ProductId: number, ShowMoney: number][] = [];
for (const item of Object.values(items)) {
arr.forEach((v: [ID: number, ProductId: number, ShowMoney: number]) => {
if (v[1] === item[0]) {
newArr.push(v);
}
});
}
return newArr
.map((item) => ({
ID: item[0],
points: item[2],
price: MyCardPriceRefMap.get(item[1]),
}))
.sort((a, b) => b.price - a.price);
}
/** 支付列表 */
export function generatePaymentList(
arr: string[],
priceRef: ShopShow2TableRow[],
): ShopShow2TableRow[] {
const newArr: ShopShow2TableRow[] = [];
for (const item of Object.values(priceRef)) {
arr.forEach((x) => {
if (item.Show && x === item.Key) newArr.push(item);
});
}
// priceRef.forEach((value: ShopShow2TableRow) => {
// arr.includes(value.Key);
// })
return newArr;
}
/** getQueryParameters */
export function getQueryParameters(v: string): string {
const queryParameters = new URLSearchParams(location.search);
return queryParameters.get(v);
}
export function createMap(obj: unknown) {
return new Map(Object.entries(obj));
}
export function isArray(itemCode: string | string[]): boolean {
return Array.isArray(itemCode);
}
export function formatTime(date: Date): string {
const hours = date.getHours();
const minutes = date.getMinutes();
const amPm = hours >= 12 ? "pm" : "am";
const formattedHours = hours % 12 === 0 ? 12 : hours % 12;
const formattedMinutes = minutes < 10 ? "0" + minutes : minutes;
return `${formattedHours}:${formattedMinutes} ${amPm}`;
}
export function getCurrentLocalTime() {
const date = new Date();
const utcTime = date.getTime();
return new Date(utcTime).toLocaleTimeString();
}
export function transferColorText(str: string): string {
const regex = /color=/g;
const replacement = "span style=color:";
const result = str.replace(regex, replacement);
const regex2 = /color>/g;
const replacement2 = "span>";
return result.replace(regex2, replacement2);
}
export function generateItemsData(
games: Map<string, number[]>,
favoriteGames: number[],
): ItemPropsType[] {
let itemsData: boolean | ItemPropsType[] = [];
const s = new Set(favoriteGames);
if (games.size) {
// @ts-ignore
for (const [gameID, [vendorID, id, VIPLimit, status, tag]] of games) {
const dataObj: ItemPropsType = {
id: gameID,
vendorID: vendorID.toString(),
img: {
url: `${BusinessTypeSetting.UseDownloadUrl}${FolderName.Game}${id}/b`,
},
size: ItemSize.small,
tag: tag as unknown as TagTypes,
lockBtn: VIPLimit,
like: s.has(id),
status,
};
itemsData.push(dataObj);
}
} else {
itemsData = [];
}
return itemsData;
}
export function generateItemsDataMap(games: Games): Map<string, number[]> {
const map = new Map();
for (const [gameID, value] of Object.entries(games)) {
map.set(gameID, value);
}
return map;
}
export function sortedGames(sorts: number[], map: Map<string, number[]>) {
const obj = new Map();
sorts?.forEach((gameID) => {
obj.set("" + gameID, map?.get("" + gameID));
});
return obj;
}
export function addPropertiesToDefaultItem(
games: any[],
selectedID: number,
): SelectorItemProps[] {
const index = games.findIndex((item) => parseInt(item.id) === selectedID);
const newGames = games.slice();
return newGames.map((g, i) =>
i === index
? { ...g, selected: true, defaultItem: true }
: {
...g,
selected: false,
defaultItem: false,
},
);
}
export function switchObjToMap(games: Games) {
// for (const [gameID, value] of Object.entries(games)) {
// map.set(gameID, value)
// }
return new Map(Object.entries(games));
}
export function hexToRgb(
hex: string,
// eslint-disable-next-line @typescript-eslint/typedef
result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex),
) {
const x = result ? result.map((i) => parseInt(i, 16)).slice(1) : null;
return {
r: x[0],
g: x[1],
b: x[2],
};
}
export function replaceBorderColor(string: string, color: string) {
const hasComma = string.includes(",");
if (hasComma) {
const result = string.split(",").map((s, i) => {
const index = s.indexOf("#");
const replaceStr = s.substring(index);
return s.replace(replaceStr, i === 0 ? "black" : color);
});
return result.join(",");
} else {
const index = string.indexOf("#");
const replaceStr = string.substring(index);
return string.replace(replaceStr, color);
}
}
export function generateMessage(message: string) {
return {
AID: "10000000063",
nickName: "masterkai",
profileIMG: "./img/png/avatar.png",
role: ChatRoomRole.player,
message: message,
created: getCurrentLocalTime(),
vip: VIPLevelMapForChat.get(4),
};
}
export function getVIPLevelFromStr(vipStr: string) {
const arr = vipStr.split(".");
const str = arr[0];
return str.at(-1);
}
interface Accumulator {
[AID: number]: {
AID: number;
nickName: string;
avatar: number;
role: number;
vip: number;
messages: {
message: string;
created: number;
}[];
};
}
export function sleep(ms: any): Promise<any> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export async function downloadJSON(formname: string) {
const patchUrl: string =
BusinessTypeSetting.UsePatch + BusinessTypeSetting.FolderUrlJson;
let fileUrl: string = `${patchUrl}${formname}.json`;
fileUrl = fileUrl + "?v=" + Date.now();
let resp: AxiosResponse<any, any> = null;
axios.get(fileUrl).then((res: AxiosResponse<any, any>) => {
loadJsonProcess(null, res, formname);
resp = res;
});
while (!resp) {
await sleep(0.1);
}
}
function loadJsonProcess(
err: any,
res: AxiosResponse<any, any>,
formname: string,
) {
res["name"] = formname;
TableManager.AddJsonAsset(res.data);
}
export function getProfileImgUrl(avatar: number, aId: number) {
return avatar === 1
? `${BusinessTypeSetting.UseDownloadUrl}avatar/${aId}`
: "./img/common/DefaultAvatar.png";
}
export const CssStringContent = (
StringKey: number,
StringID: number | string,
): string => {
switch (StringKey) {
case StringContentType.String:
return StringID?.toString();
case StringContentType.CSSString:
return CSSettingsV3.prototype.CommonString(+StringID);
case StringContentType.CSSMailString:
return CSSettingsV3.prototype.CSSMailString(+StringID);
case StringContentType.CSSNetworkString:
return ""; // CSSettingsV3.Network.Priority[StringID][LanguageManager.GetMsgId()];
case StringContentType.HallString:
/* 廳管1~4 但表格是從2號位開始 */
return CSSettingsV3.prototype.LobbyString(1 + +StringID);
default:
break;
}
return;
};
export function txnDataTransformer(
arr: TxnRequest.TxnInfo[],
playerAid: number,
): TxnCenterData[] {
return arr.map(
([
sn,
time,
giver,
receiver,
status,
fee,
[[category, categoryId, quantity]],
]) => ({
serialNum: sn,
createdAt: time,
giverAid: giver[0],
giverName: giver[1],
isGiver: giver[0] === playerAid,
receiverAid: receiver[0],
receiverName: receiver[1],
status: +status,
quantity,
fee,
}),
);
}
export function calculatedReward(rewards: [number, number][]) {
const maximum = 6;
const result = [];
const couponType = ResourceItemType.Card_Coupon;
rewards.forEach(([type, quantity]) => {
if (type === couponType && quantity > maximum) {
const packNum = Math.floor(quantity / maximum);
const returnPack = Array.from({ length: packNum }, () => [
couponType,
maximum,
]);
const remainder = quantity % maximum;
const returnRemainder = remainder ? [couponType, remainder] : null;
if (returnRemainder) {
returnPack.push(returnRemainder);
}
returnPack.forEach((item) => result.push(item));
} else {
result.push([type, quantity]);
}
});
return result;
}
export const random = (min: number, max: number) =>
Math.floor(Math.random() * (max - min)) + min;
// Default color is a bright yellow
const DEFAULT_COLOR = "hsl(50deg, 100%, 50%)";
export const generateSparkle = (color = DEFAULT_COLOR) => {
return {
id: "" + random(10000, 99999),
createdAt: Date.now(),
// Bright yellow color:
color,
size: random(20, 60),
style: {
// Pick a random spot in the available space
top: random(0, 100) + "%",
left: random(0, 100) + "%",
// Float sparkles above sibling content
zIndex: 2,
},
};
};
export const range = (start, end, step = 1) => {
const output = [];
if (typeof end === "undefined") {
end = start;
start = 0;
}
for (let i = start; i < end; i += step) {
output.push(i);
}
return output;
};
export async function waitSetBusinessType() {
while (!BusinessTypeSetting.UseHost) {
await sleep(100);
}
}
/**
*
* @param {number} aId AID
*/
export function isMyFriend(aId: number): boolean {
let isTrue: boolean = false;
const playerData = Player.data.getState();
const lists: FriendRequest.ListFriendData = playerData.account.allowList;
for (let i = 0; i < lists.length; i++) {
const list: FriendRequest.SingleFriendData = lists[i];
if (list[0] === aId) {
isTrue = true;
break;
}
}
return isTrue;
}
/**
*
* @param {number} aId AID
*/
export function isMyDeny(aId: number): boolean {
let isTrue: boolean = false;
const playerData = Player.data.getState();
const lists: FriendRequest.ListFriendData = playerData.account.denyList;
for (let i = 0; i < lists.length; i++) {
const list: FriendRequest.SingleFriendData = lists[i];
if (list[0] === aId) {
isTrue = true;
break;
}
}
return isTrue;
}
export const sheetNameResourceTypeSwitcher = (
resourceType: ResourceItemType,
) => {
switch (resourceType) {
case ResourceItemType.Card_Coupon:
return "CouponSetting";
case ResourceItemType.Card:
return "Card1Setting";
default:
return "Card1Setting";
}
};
export function escapeCodesNToBr(v: string): string {
return v.replace(/\n/g, "<br>");
}
/**
*
* @param str
* @param showBytes (12BYTES)
* @returns
*/
export function trimString(
str: string,
showBytes: number = 12,
ellipses: boolean = true,
): string {
if (!str) {
return str;
}
let bytes: number = stringWidth(str);
if (bytes <= showBytes) {
return str;
}
let byteAmount: number = 0;
let strLength: number = str.length;
for (let i: number = 0; i < strLength; i++) {
let word: string = str[i];
bytes = stringWidth(word);
byteAmount += bytes;
if (byteAmount > showBytes) {
let checkStr: string = str.substring(0, i + 1);
let checkByte: number = stringWidth(checkStr);
if (checkByte < showBytes) {
byteAmount = checkByte;
continue;
}
let result: string = str.substring(0, i);
if (ellipses) {
return result + "...";
} else {
return result;
}
}
}
console.error("Trim Nickname Error.");
return str;
}
/** CommonEventType */
export enum CommonEventType {
/** Maintenance */
Maintenance,
/**ActivityReRender */
ActivityReRender,
}
export function responsiveText(characters: number): number {
if (characters <= 10) return 1.125;
if (characters > 10 && characters <= 20) return 0.9;
if (characters > 20) return 0.8;
}
export function discount(numberOff: number): number {
return (100 - numberOff) / 100;
}
/** 預載字體 */
export function PreloadFont(fonts: string[]): void {
// Check if API exists
if (document && document.fonts) {
// Do not block page loading
setTimeout(function (): void {
let successCount: number = 0;
for (let i: number = 0; i < fonts.length; i++) {
const font: string = fonts[i];
// eslint-disable-next-line no-loop-func
document.fonts.load(`16px ${font}`).then(() => {
// Make font using elements visible
successCount++;
if (successCount === fonts.length) {
document.documentElement.classList.add("font-loaded");
}
});
}
}, 0);
} else {
// Fallback if API does not exist
document.documentElement.classList.add("font-loaded");
}
}
// PWA
/** BeforeInstallPromptEvent */
export let deferredPrompt: any;
window.addEventListener("beforeinstallprompt", (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// Update UI to notify the user they can add to home screen
Cocos.CocosEventListener.DispatchCallback(GameData_Cocos.CELT.PWAInitOK, e);
});
export function addToHomeScreen(): void {
if (isMobile) {
let url: string =
BusinessTypeSetting.UsePatch +
"addtohomescreen/index.html" +
"?v=" +
Date.now();
liff.openWindow({
url: url,
external: true,
});
} else {
if (deferredPrompt) {
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === "accepted") {
// console.log("User accepted the A2HS prompt");
Cocos.CocosEventListener.DispatchCallback(GameData_Cocos.CELT.PWAInitOK, false);
} else {
// console.log("User dismissed the A2HS prompt");
}
deferredPrompt = null;
});
}
}
}
export function getArray(count: number): number[] {
const array: number[] = [];
for (let i: number = 0; i < count; i++) {
array.push(i);
}
return array;
}
/** 判斷登入並且Line綁定完 */
export function checkWait(): boolean {
const playerData = Player.data.getState();
const isLineBind: boolean = Player.hasUserBindFlag(UserBindFlag.LineBind);
if (!BaseEnumerator.isInit) {
return true;
} else if (!CocosVueScript.Instance || !CocosVueScript.Instance?.GetLoginData()) {
return true;
} else if (!playerData.account.role && !isLineBind) {
return true;
}
return false;
}
export function scrollToBottom(dynamicListHeight: number, duration: number = 200) {
Scroll.animateScroll.scrollTo(dynamicListHeight, {
duration: duration,
smooth: "easeInQuad",
containerId: "scrollableDiv",
offset: 50
});
}
export function Copy(serialNum: string) {
try {
navigator.clipboard.writeText(serialNum);
}
catch (error) {
console.error(error);
}
}

235
src/utils/setRPCData.ts Normal file
View File

@ -0,0 +1,235 @@
import MainControl from "@/Common/MainControl/MainControl";
import { CoroutineV2 } from "@/Engine/CatanEngine/CoroutineV2/CoroutineV2";
import { TTeamBattleData } from "@/UI/RouterPage/GameContent/GameContentUtils";
import { Cocos } from "@/assets/VueScript/Cocos";
import { CommonEventCallBack } from "@/assets/VueScript/CocosVueScript";
import GameData_Cocos from "@/assets/VueScript/share/GameData_Cocos";
import { GiftCallBack, GiftEventEnum, PanelType, UpdateOneListInfo } from "@/components/ModalContent/TxnModal/txnUtils";
import { gameObj } from "@/context/GameItemsContext";
import { LocalStorage } from "@/define";
import { EActivitySyncType, RpcActivityComSyncResponse, TActivityComSyncData, TActivitySyncData } from "@/define/Request/ActivityRequest";
import { ResponseBackpackInfo } from "@/define/Request/BackpackRequest";
import { FriendRequest } from "@/define/Request/FriendRequest";
import { ProfileRequest } from "@/define/Request/ProfileRequest";
import { TxnRequest } from "@/define/Request/TxnRequest";
import { VipRequest } from "@/define/Request/VIPRequest";
import Player from "@/modules/player";
import { State } from "@/modules/player/define/data";
import { BackpackItemData } from "@/modules/player/define/data/backpack";
import { CommonEventType } from ".";
let SN: number = 0;
export function profileInfo(data: ProfileRequest.InfoResponse): void {
const playerData: State = Player.data.getState();
if (data.aId !== playerData.account.aId) {
return;
}
playerData.account.name = data.name;
playerData.account.message = data.msg;
playerData.account.phone = data.phone;
playerData.account.money = data.money;
playerData.vip.level = data.vip;
Player.data.setState(playerData);
}
export function vipInfo(data: VipRequest.InfoResponse): void {
const playerData: State = Player.data.getState();
playerData.vip.level = data.level;
playerData.vip.totalBet = data.bet;
playerData.vip.totalCharge = data.sv;
playerData.vip.rich = data.rich;
playerData.vip.et = data.et;
Player.data.setState(playerData);
}
export function friendAllowList(data: FriendRequest.ListFriendData): void {
const playerData: State = Player.data.getState();
playerData.account.allowList = data;
Player.data.setState(playerData);
}
export function friendDenyList(data: FriendRequest.ListFriendData): void {
const playerData: State = Player.data.getState();
playerData.account.denyList = data;
Player.data.setState(playerData);
}
export function activityComSync(data: RpcActivityComSyncResponse): void {
const { teamBattleData, setTeamBattleData } = gameObj;
for (let i = 0; i < data.length; i++) {
const activityComSyncData: TActivityComSyncData = data[i];
const [id, activitySyncDatas] = activityComSyncData;
// for (let j = 0; j < teamBattleData.length; j++) {
// const teamBattle: TTeamBattleData = teamBattleData[j];
// const [teamBattleId, , ,] = teamBattle;
// if (id === teamBattleId) {
// for (let k = 0; k < activitySyncDatas.length; k++) {
// const activitySyncData: TActivitySyncData = activitySyncDatas[k];
// const [type, value] = activitySyncData;
// switch (type) {
// case EActivitySyncType.IsOpen: {
// teamBattleData[j][1] = -1;
// setTeamBattleData(teamBattleData);
// break;
// }
// case EActivitySyncType.Sync: {
// teamBattleData[j][1] = value;
// setTeamBattleData(teamBattleData);
// break;
// }
// case EActivitySyncType.Task: {
// break;
// }
// default:
// break;
// }
// }
// break;
// }
// }
for (let j = 0; j < activitySyncDatas.length; j++) {
const activitySyncData: TActivitySyncData = activitySyncDatas[j];
const [type, value] = activitySyncData;
switch (type) {
case EActivitySyncType.IsOpen: {
// for (let k = 0; k < teamBattleData.length; k++) {
// const teamBattle: TTeamBattleData = teamBattleData[k];
// const [teamBattleId, , ,] = teamBattle;
// if (id === teamBattleId) {
// teamBattleData[j][1] = value;
// setTeamBattleData(teamBattleData);
// break;
// }
// }
break;
}
case EActivitySyncType.Sync: {
for (let k = 0; k < teamBattleData.length; k++) {
const teamBattle: TTeamBattleData = teamBattleData[k];
const [teamBattleId, , ,] = teamBattle;
if (id === teamBattleId) {
teamBattleData[j][1] = value;
setTeamBattleData(teamBattleData);
break;
}
}
break;
}
case EActivitySyncType.Task: {
break;
}
default:
break;
}
}
}
CommonEventCallBack.DispatchCallback(CommonEventType.ActivityReRender, null);
}
export function backpackInfo(data: ResponseBackpackInfo[], isAdd: boolean = false): void {
const playerData: State = Player.data.getState();
const backpackList: BackpackItemData[] = isAdd ? playerData.backpack.Copy() : [];
for (let i = 0; i < data.length; i++) {
const backpackServerData = data[i];
const id: number = backpackServerData[1][0];
const count: number = backpackServerData[1][1];
for (let j = 0; j < count; j++) {
const backpackClientData: BackpackItemData = {
SN: SN,
ResourceType: backpackServerData[0],
ID: id,
Viewed: false,
};
backpackList.push(backpackClientData);
SN++;
}
}
if (!isAdd) {
const oldBackpackListStr: string = localStorage.getItem(LocalStorage.Key.Backpack);
if (oldBackpackListStr) {
const oldBackpackList: BackpackItemData[] = JSON.parse(oldBackpackListStr);
for (let i = 0; i < backpackList.length; i++) {
const backpack: BackpackItemData = backpackList[i];
for (let j = 0; j < oldBackpackList.length; j++) {
const oldBackpack: BackpackItemData = oldBackpackList[j];
if (backpack.ID === oldBackpack.ID && backpack.ResourceType === oldBackpack.ResourceType) {
backpackList[i].Viewed = oldBackpack.Viewed;
oldBackpackList.splice(j, 1);
break;
}
}
}
}
}
playerData.backpack = backpackList;
localStorage.setItem(LocalStorage.Key.Backpack, JSON.stringify(playerData.backpack));
Player.data.setState(playerData);
const totalUnreadCount: number = playerData.backpack.filter((item) => !item.Viewed).length;
if (!MainControl.Instance.IsInGame) {
Cocos.CocosEventListener.DispatchCallback(GameData_Cocos.CELT.SetBackpackUnreadCount, totalUnreadCount);
}
}
export function txnNew(data: TxnRequest.TxnInfo): void {
const playerData = Player.data.getState();
const centerList = playerData.txn.centerList.slice();
centerList.push(data);
playerData.txn.centerList = centerList;
GiftCallBack.DispatchCallback(GiftEventEnum.ReFlash, null);
Cocos.CocosEventListener.DispatchCallback(GameData_Cocos.CELT.SetTxnUnreadCount, playerData.txn.centerList.length);
Player.data.setState(playerData);
}
export function txnCenter(data: TxnRequest.TxnInfo[]): void {
const playerData = Player.data.getState();
playerData.txn.centerList = data;
GiftCallBack.DispatchCallback(GiftEventEnum.ReFlash, null);
Cocos.CocosEventListener.DispatchCallback(GameData_Cocos.CELT.SetTxnUnreadCount, playerData.txn.centerList.length);
Player.data.setState(playerData);
}
export function txnTrade(data: TxnRequest.TradeResponse): void {
const playerData: State = Player.data.getState();
const centerList: TxnRequest.TxnInfo[] = playerData.txn.centerList;
let type: number = PanelType.RecordPanel;
let status: number = +data.s;
if (status < 20) {
type = PanelType.CenterPanel;
switch (status) {
case 11:
case 12:
case 13: {
for (let i: number = 0; i < centerList.length; i++) {
const centerData: TxnRequest.TxnInfo = centerList[i];
if (centerData[0] == +data.id && centerData[3][0] == playerData.account.aId) {
type = PanelType.RecordPanel;
break;
}
}
break;
}
default:
break;
}
}
CoroutineV2.Single(UpdateOneListInfo(data.id, type)).Start();
}
export function txnUserAdd(data: TxnRequest.UserAddResponse): void {
const playerData: State = Player.data.getState();
playerData.txn.receiverList.push(data.u);
Player.data.setState(playerData);
}

1
src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

38
src/zSbmoduleUpdate.bat Normal file
View File

@ -0,0 +1,38 @@
@echo off
title Git Working
goto selectAll
:selectAll
echo ---------------------------------------------------
set n=0
echo 1.第一次取專案設定所需子儲存庫
echo 2.所有子儲存庫呼叫小烏龜PULL視窗
echo *注意此儲存庫也會跟著呼叫.沒有錯誤會自動關閉
set/p n= 請選擇:
if "%n%"=="1" ( goto change )
if "%n%"=="2" ( goto childupdate )
goto exit
:change
echo 第一次取專案設定所需子儲存庫
echo.
git.exe clone --progress -v "git@git.catan.com.tw:Line_Project_1/Plan-Form_Table.git" FormTable
git.exe clone --progress -v "git@git.catan.com.tw:Frontend/FormTableExt.git" FormTableExt
echo 結束
goto selectAll
:childupdate
echo 所有子儲存庫呼叫小烏龜PULL視窗
cd FormTable
TortoiseGitProc -command:pull /closeonend:2
echo path:FormTable done.
cd ..
cd FormTableExt
TortoiseGitProc -command:pull /closeonend:2
echo path:FormTableExt done.
cd ..
echo 結束
goto selectAll

41
tsconfig.json Normal file
View File

@ -0,0 +1,41 @@
{
"compilerOptions": {
"target": "es2015",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"sourceMap": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": false,
"strictNullChecks": false,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": false,
"noEmit": true,
"jsx": "react-jsx",
"types": [
"node"
],
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"src",
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}

11
tsconfig.node.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": [
"vite.config.ts"
]
}

2
vite.config.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
declare const _default: import("vite").UserConfig;
export default _default;

28
vite.config.ts Normal file
View File

@ -0,0 +1,28 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
define: {
"AppVersion": JSON.stringify(process.env.npm_package_version),
},
resolve: {
alias: [
{ find: "@", replacement: path.resolve(__dirname, "src") },
{ find: "@views", replacement: path.resolve(__dirname, "src/views") },
{ find: "@assets", replacement: path.resolve(__dirname, "src/assets") },
{ find: "@css", replacement: path.resolve(__dirname, "src/assets/css") },
{ find: "@less", replacement: path.resolve(__dirname, "src/assets/less") },
{ find: "@images", replacement: path.resolve(__dirname, "src/assets/images") },
{ find: "@components", replacement: path.resolve(__dirname, "src/components") },
],
},
publicDir: "build-templates",
build: {
outDir: "./build"
},
base: "./",
envDir: "./viteEnv",
});