From 8dddfaf7b1de2b9ea6c3f43fc75d641c2c7a925a Mon Sep 17 00:00:00 2001 From: Lex Lim Date: Sun, 17 Nov 2024 01:25:37 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E6=8E=A7=E5=88=B6=E5=99=A8?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/apis/webApi.ts | 13 ++ .../components/dialogs/GetLiveCompDialog.vue | 2 + .../partials/CoyoteBluetoothPanel.vue | 203 +----------------- .../partials/CoyoteBluetoothService.vue | 135 ++++++++++++ .../components/partials/GameConnection.vue | 16 ++ .../src/components/partials/PulseSettings.vue | 24 +-- .../components/partials/StrengthSettings.vue | 71 ++++++ .../FadeAndSlideTransitionGroup.vue | 6 +- frontend/src/pages/Controller.vue | 182 +++++++--------- frontend/src/stores/CoyoteBTStore.ts | 69 ++++++ server/cli/build-schema.js | 4 + server/src/controllers/http/Web.ts | 17 ++ server/src/controllers/ws/WebWS.ts | 86 +++++--- server/src/index.ts | 2 + server/src/router.ts | 6 + server/src/schemas/CustomSkinManifest.json | 69 ++++++ server/src/schemas/MainConfigType.json | 2 +- server/src/schemas/schemas.json | 5 +- server/src/services/CustomSkinService.ts | 68 ++++++ server/src/services/DGLabPulse.ts | 1 - server/src/types/config.ts | 2 +- server/src/types/customSkin.ts | 14 ++ server/src/types/game.ts | 5 + server/src/utils/validator.ts | 5 + 24 files changed, 665 insertions(+), 342 deletions(-) create mode 100644 frontend/src/components/partials/CoyoteBluetoothService.vue create mode 100644 frontend/src/components/partials/GameConnection.vue create mode 100644 frontend/src/components/partials/StrengthSettings.vue create mode 100644 frontend/src/stores/CoyoteBTStore.ts create mode 100644 server/src/schemas/CustomSkinManifest.json create mode 100644 server/src/services/CustomSkinService.ts create mode 100644 server/src/types/customSkin.ts diff --git a/frontend/src/apis/webApi.ts b/frontend/src/apis/webApi.ts index 31172fe..5efc676 100644 --- a/frontend/src/apis/webApi.ts +++ b/frontend/src/apis/webApi.ts @@ -1,3 +1,5 @@ +import { ChartParamDef } from "../charts/types/ChartParamDef"; + export type ServerInfoResData = { server: { wsUrl: string, @@ -15,6 +17,17 @@ export type ClientConnectInfoResData = { clientId: string, }; +export type CustomSkinInfo = { + name: string; + url: string; + help?: string; + params?: ChartParamDef[]; +}; + +export type CustomSkinsResData = { + customSkins: CustomSkinInfo[], +}; + export type ApiResponse = { status: number, message?: string, diff --git a/frontend/src/components/dialogs/GetLiveCompDialog.vue b/frontend/src/components/dialogs/GetLiveCompDialog.vue index 5c8ee7a..29256e3 100644 --- a/frontend/src/components/dialogs/GetLiveCompDialog.vue +++ b/frontend/src/components/dialogs/GetLiveCompDialog.vue @@ -22,6 +22,7 @@ const visible = defineModel('visible'); const state = reactive({ theme: 'default', chartParams: {} as Record, + themeLoading: true, }); const getThemeNameFromPath = (path: string) => { @@ -100,6 +101,7 @@ const copyUrl = () => { :options="themeOptions" optionLabel="label" optionValue="value" + :loading="state.themeLoading" placeholder="请选择主题" class="w-full" > diff --git a/frontend/src/components/partials/CoyoteBluetoothPanel.vue b/frontend/src/components/partials/CoyoteBluetoothPanel.vue index 0bbc2d9..4fe6a5b 100644 --- a/frontend/src/components/partials/CoyoteBluetoothPanel.vue +++ b/frontend/src/components/partials/CoyoteBluetoothPanel.vue @@ -5,39 +5,15 @@ import Chip from 'primevue/chip'; import BatteryIcon from '../../assets/battery.svg'; import { ToastServiceMethods } from 'primevue/toastservice'; -import { CoyoteBluetoothController } from '../../utils/CoyoteBluetoothController'; import { ConfirmationOptions } from 'primevue/confirmationoptions'; import { ConnectorType, CoyoteDeviceVersion } from '../../type/common'; -import { Reactive } from 'vue'; -import { ControllerPageState } from '../../pages/Controller.vue'; +import { useCoyoteBTStore } from '../../stores/CoyoteBTStore'; defineOptions({ name: 'CoyoteBluetoothPanel', }); -const BT_CONFIG_STORAGE_KEY = 'CGH_BTClientConfig'; - -const props = defineProps<{ - state: any; -}>(); - -// 从父组件获取state -let parentState: Reactive; -watch(() => props.state, (value) => { - parentState = value; -}, { immediate: true }); - -const state = reactive({ - connected: false, - - deviceBattery: 100, - deviceStrengthA: 0, - deviceStrengthB: 0, - - freqBalance: 150, - inputLimitA: 20, - inputLimitB: 20, -}); +const state = useCoyoteBTStore(); const toast = inject('parentToast'); const confirm = inject<{ @@ -45,114 +21,6 @@ const confirm = inject<{ close: () => void; }>('parentConfirm'); -/** 蓝牙控制器 */ -let bluetoothController: CoyoteBluetoothController | null = null; - -// 蓝牙连接相关 -const startBluetoothConnect = async (deviceVersion: CoyoteDeviceVersion) => { - parentState.showConnectionDialog = false; - - if (!bluetoothController) { - bluetoothController = new CoyoteBluetoothController(deviceVersion, parentState.clientId); - } - - try { - confirm?.require({ - header: '蓝牙连接', - message: '扫描中,请在弹出窗口中选择扫描到的设备,然后点击“配对”按钮。', - acceptClass: 'd-none', - rejectClass: 'd-none', - }); - - await bluetoothController.scan(); - - confirm?.require({ - header: '蓝牙连接', - message: '正在连接到蓝牙设备,这可能需要十几秒的时间', - acceptClass: 'd-none', - rejectClass: 'd-none', - }); - - await bluetoothController.connect(); - bindBTControllerEvents(); - - confirm?.close(); - toast?.add({ severity: 'success', summary: '连接成功', detail: '已连接到蓝牙控制器', life: 3000 }); - - if (deviceVersion === CoyoteDeviceVersion.V2) { - parentState.connectorType = ConnectorType.COYOTE_BLE_V2; - } else { - parentState.connectorType = ConnectorType.COYOTE_BLE_V3; - } - - state.connected = true; - window.onbeforeunload = (event) => { - event.preventDefault(); - return '确定要断开蓝牙连接吗?'; - }; - } catch (error: any) { - confirm?.close(); - console.error('Cannot connect via bluetooth:', error); - toast?.add({ severity: 'error', summary: '连接失败', detail: error.message }); - } -}; - -const bindBTControllerEvents = () => { - if (!bluetoothController) { - return; - } - - bluetoothController.on('workerInit', () => { - bluetoothController?.setStrengthLimit(state.inputLimitA, state.inputLimitB); - bluetoothController?.setFreqBalance(state.freqBalance); - bluetoothController?.startWs(); - }); - - bluetoothController.on('batteryLevelChange', (battery) => { - state.deviceBattery = battery; - }); - - bluetoothController.on('strengthChange', (strengthA, strengthB) => { - state.deviceStrengthA = strengthA; - state.deviceStrengthB = strengthB; - }); - - bluetoothController.on('reconnecting', () => { - confirm?.require({ - header: '蓝牙连接', - message: '连接已断开,正在重新连接到蓝牙设备', - acceptClass: 'd-none', - rejectLabel: '取消', - rejectProps: { - severity: 'secondary', - }, - reject: () => { - stopBluetoothConnect(); - confirm?.close(); - }, - }); - }); - - bluetoothController.on('connect', () => { - // 重连成功 - confirm?.close(); - }); - - bluetoothController.on('disconnect', () => { - state.connected = false; - window.onbeforeunload = null; - confirm?.close(); - }); -}; - -const stopBluetoothConnect = async () => { - bluetoothController?.cleanup(); - bluetoothController = null; - state.connected = false; - - window.onbeforeunload = null; -}; - const handleStopBluetoothConnect = () => { confirm?.require({ header: '断开蓝牙连接', @@ -164,74 +32,11 @@ const handleStopBluetoothConnect = () => { severity: 'secondary', }, accept: async () => { - await stopBluetoothConnect(); + state.disconnect(); toast?.add({ severity: 'success', summary: '断开成功', detail: '已断开蓝牙连接', life: 3000 }); }, }); }; - -const saveLocalConfig = () => { - localStorage.setItem(BT_CONFIG_STORAGE_KEY, JSON.stringify({ - freqBalance: state.freqBalance, - inputLimitA: state.inputLimitA, - inputLimitB: state.inputLimitB, - })); -}; - -const loadLocalConfig = () => { - const config = localStorage.getItem(BT_CONFIG_STORAGE_KEY); - if (!config) { - return; - } - - const savedConfig = JSON.parse(config); - if (!savedConfig) { - return; - } - - if (typeof savedConfig.freqBalance === 'number') { - state.freqBalance = Math.min(200, Math.max(0, savedConfig.freqBalance)); - } - - if (typeof savedConfig.inputLimitA === 'number') { - state.inputLimitA = Math.min(200, Math.max(1, savedConfig.inputLimitA)); - } - - if (typeof savedConfig.inputLimitB === 'number') { - state.inputLimitB = Math.min(200, Math.max(1, savedConfig.inputLimitB)); - } -}; - -watch(() => parentState.clientId, async (newVal) => { - if (newVal) { - // 刷新客户端ID时断开蓝牙连接 - await stopBluetoothConnect(); - } -}); - -watch(() => [state.inputLimitA, state.inputLimitB], (newVal) => { - if (bluetoothController) { - bluetoothController.setStrengthLimit(newVal[0], newVal[1]); - saveLocalConfig(); - toast?.add({ severity: 'success', summary: '设置成功', detail: '已更新强度上限', life: 3000 }); - } -}, { immediate: true }); - -watch(() => state.freqBalance, (newVal) => { - if (bluetoothController && parentState.connectorType === ConnectorType.COYOTE_BLE_V2) { - bluetoothController.setFreqBalance(newVal); - saveLocalConfig(); - toast?.add({ severity: 'success', summary: '设置成功', detail: '已更新频率平衡参数', life: 3000 }); - } -}, { immediate: true }); - -onMounted(() => { - loadLocalConfig(); -}); - -defineExpose({ - startBluetoothConnect, -}); - - - diff --git a/frontend/src/stores/CoyoteBTStore.ts b/frontend/src/stores/CoyoteBTStore.ts new file mode 100644 index 0000000..a28efad --- /dev/null +++ b/frontend/src/stores/CoyoteBTStore.ts @@ -0,0 +1,69 @@ +import { defineStore } from 'pinia' +import { CoyoteBluetoothController } from '../utils/CoyoteBluetoothController'; +import { CoyoteDeviceVersion } from '../type/common'; + +export const useCoyoteBTStore = defineStore('coyoteBTStore', { + state: () => ({ + connected: false, + + deviceVersion: CoyoteDeviceVersion.V3, + + deviceBattery: 100, + deviceStrengthA: 0, + deviceStrengthB: 0, + + freqBalance: 150, + inputLimitA: 20, + inputLimitB: 20, + + controller: null as null | CoyoteBluetoothController, + }), + actions: { + initConnection() { + if (!this.controller) { + return; + } + + this.deviceVersion = this.controller.deviceVersion; + + this.connected = true; + window.onbeforeunload = (event) => { + event.preventDefault(); + return '确定要断开蓝牙连接吗?'; + }; + + this.controller.on('workerInit', () => { + // 恢复设置 + this.controller?.setStrengthLimit(this.inputLimitA, this.inputLimitB); + this.controller?.setFreqBalance(this.freqBalance); + // 启动蓝牙 + this.controller?.startWs(); + }); + + this.controller.on('batteryLevelChange', (battery) => { + this.deviceBattery = battery; + }); + + this.controller.on('strengthChange', (strengthA, strengthB) => { + this.deviceStrengthA = strengthA; + this.deviceStrengthB = strengthB; + }); + + this.controller.on('disconnect', () => { + this.connected = false; + window.onbeforeunload = null; + }); + }, + disconnect() { + this.controller?.cleanup(); + this.controller = null; + this.connected = false; + + window.onbeforeunload = null; + }, + }, + persist: { + key: 'CoyoteBTStore', + pick: ['freqBalance', 'inputLimitA', 'inputLimitB'], + } +}); \ No newline at end of file diff --git a/server/cli/build-schema.js b/server/cli/build-schema.js index 7980e70..5bf3b53 100644 --- a/server/cli/build-schema.js +++ b/server/cli/build-schema.js @@ -16,6 +16,10 @@ const typesList = [ { file: './src/types/config.ts', types: ['MainConfigType'] + }, + { + file: './src/types/customSkin.ts', + types: ['CustomSkinManifest'] } ]; diff --git a/server/src/controllers/http/Web.ts b/server/src/controllers/http/Web.ts index 1fd6379..e4d5245 100644 --- a/server/src/controllers/http/Web.ts +++ b/server/src/controllers/http/Web.ts @@ -3,6 +3,7 @@ import { Context } from 'koa'; import { DGLabWSManager } from '../../managers/DGLabWSManager'; import { MainConfig } from '../../config'; import { LocalIPAddress } from '../../utils/utils'; +import { CustomSkinService } from '../../services/CustomSkinService'; const DGLAB_WS_PREFIX = 'https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#'; @@ -58,6 +59,14 @@ export class WebController { }; }; + public static async getCustomSkinList(ctx: Context): Promise { + ctx.body = { + status: 1, + code: 'OK', + customSkins: CustomSkinService.instance.skins, + }; + } + public static async getClientConnectInfo(ctx: Context): Promise { let clientId: string = ''; for (let i = 0; i < 10; i++) { @@ -84,4 +93,12 @@ export class WebController { clientId, }; } + + public static async notImplemented(ctx: Context): Promise { + ctx.body = { + status: 0, + code: 'ERR::NOT_IMPLEMENTED', + message: '此功能尚未实现', + }; + } } \ No newline at end of file diff --git a/server/src/controllers/ws/WebWS.ts b/server/src/controllers/ws/WebWS.ts index dc7054a..9516435 100644 --- a/server/src/controllers/ws/WebWS.ts +++ b/server/src/controllers/ws/WebWS.ts @@ -124,31 +124,43 @@ export class WebWSClient { return; } - switch (message.action) { - case 'bindClient': - await this.handleBindClient(message); - break; - case 'updateStrengthConfig': - await this.handleUpdateStrengthConfig(message); - break; - case 'updateConfig': - await this.handleUpdateGameConfig(message); - break; - case 'startGame': - await this.handleStartGame(message); - break; - case 'stopGame': - await this.handleStopGame(message); - break; - case 'heartbeat': - this.prevHeartbeatTime = Date.now(); - break; - default: - await this.sendResponse(message.requestId, { - status: 0, - message: '未知的 action: ' + message.action, - }); - break; + try { + switch (message.action) { + case 'bindClient': + await this.handleBindClient(message); + break; + case 'kickClient': + await this.handleKickClient(message); + break; + case 'updateStrengthConfig': + await this.handleUpdateStrengthConfig(message); + break; + case 'updateConfig': + await this.handleUpdateGameConfig(message); + break; + case 'startGame': + await this.handleStartGame(message); + break; + case 'stopGame': + await this.handleStopGame(message); + break; + case 'heartbeat': + this.prevHeartbeatTime = Date.now(); + break; + default: + await this.sendResponse(message.requestId, { + status: 0, + message: '未知的 action: ' + message.action, + }); + break; + } + } catch (error: any) { + console.error("Failed to handle message:", error); + await this.sendResponse(message.requestId, { + status: 0, + message: '错误: ' + error.message, + detail: error, + }); } } @@ -191,6 +203,30 @@ export class WebWSClient { }); } + private async handleKickClient(message: any) { + if (!this.gameInstance) { + await this.sendResponse(message.requestId, { + status: 0, + message: '游戏未连接', + }); + return; + } + + if (!this.gameInstance.client) { + await this.sendResponse(message.requestId, { + status: 0, + message: '客户端未连接', + }); + return; + } + + await this.gameInstance.client.close(); + + await this.sendResponse(message.requestId, { + status: 1, + }); + } + private async handleUpdateStrengthConfig(message: any) { if (!this.gameInstance) { await this.sendResponse(message.requestId, { diff --git a/server/src/index.ts b/server/src/index.ts index 7e85064..ee2f7c7 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -19,6 +19,7 @@ import { validator } from './utils/validator'; import { CoyoteGameConfigService } from './services/CoyoteGameConfigService'; import { SiteNotificationService } from './services/SiteNotificationService'; import { checkUpdate } from './utils/checkUpdate'; +import { CustomSkinService } from './services/CustomSkinService'; async function main() { // blocked((time, stack) => { @@ -31,6 +32,7 @@ async function main() { await DGLabPulseService.instance.initialize(); await CoyoteGameConfigService.instance.initialize(); await SiteNotificationService.instance.initialize(); + await CustomSkinService.instance.initialize(); const app = new Koa(); const httpServer = http.createServer(app.callback()); diff --git a/server/src/router.ts b/server/src/router.ts index ee26bb9..082ccb0 100644 --- a/server/src/router.ts +++ b/server/src/router.ts @@ -9,6 +9,7 @@ export const setupRouter = (router: KoaRouter, wsRouter: WebSocketRouter) => { router.get('/', WebController.index); router.get('/api/server_info', WebController.getServerInfo); router.get('/api/client/connect', WebController.getClientConnectInfo); + router.get('/api/custom_skins', WebController.getCustomSkinList); router.get('/api/game', GameApiController.gameApiInfo); @@ -24,6 +25,8 @@ export const setupRouter = (router: KoaRouter, wsRouter: WebSocketRouter) => { // v2 router.get('/api/v2/pulse_list', GameApiController.getPulseList); + router.post('/api/v2/pair_game', WebController.notImplemented); + router.get('/api/v2/game/:id', GameApiController.gameInfo); router.get('/api/v2/game/:id/strength', GameApiController.getGameStrength); router.post('/api/v2/game/:id/strength', GameApiController.setGameStrength); @@ -33,6 +36,9 @@ export const setupRouter = (router: KoaRouter, wsRouter: WebSocketRouter) => { router.post('/api/v2/game/:id/action/fire', GameApiController.startActionFire); + router.post('/api/v2/game/:id/gameplay/init', WebController.notImplemented); + router.post('/api/v2/game/:id/gameplay/:gameplayid/event/emit', WebController.notImplemented); + wsRouter.get('/ws', async (ws, req) => { WebWSManager.instance.handleWebSocket(ws, req); }); diff --git a/server/src/schemas/CustomSkinManifest.json b/server/src/schemas/CustomSkinManifest.json new file mode 100644 index 0000000..76edbc1 --- /dev/null +++ b/server/src/schemas/CustomSkinManifest.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/CustomSkinManifest", + "definitions": { + "CustomSkinManifest": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "main": { + "type": "string" + }, + "help": { + "type": "string" + }, + "params": { + "type": "array", + "items": { + "$ref": "#/definitions/CustomSkinParamDef" + } + } + }, + "required": [ + "name", + "main" + ], + "additionalProperties": false + }, + "CustomSkinParamDef": { + "type": "object", + "properties": { + "prop": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "string", + "int", + "boolean", + "select" + ] + }, + "options": { + "type": "object", + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "label", + "value" + ], + "additionalProperties": false + } + }, + "required": [ + "prop", + "type" + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/server/src/schemas/MainConfigType.json b/server/src/schemas/MainConfigType.json index 3dfe3ac..df991d7 100644 --- a/server/src/schemas/MainConfigType.json +++ b/server/src/schemas/MainConfigType.json @@ -35,7 +35,7 @@ }, "apiBaseHttpUrl": { "type": "string", - "description": "API的基础URL,不需要“/api”,某些游戏插件可能不支持HTTPS,需要配置HTTP接口" + "description": "API的基础URL,不需要“api”,末尾需要“/”,某些游戏插件可能不支持HTTPS,需要配置HTTP接口" }, "pulseConfigPath": { "type": "string", diff --git a/server/src/schemas/schemas.json b/server/src/schemas/schemas.json index e2e2f56..77cf346 100644 --- a/server/src/schemas/schemas.json +++ b/server/src/schemas/schemas.json @@ -9,6 +9,9 @@ "fileMd5": "767957c4f5c0bedfadc8b1389d22ba33" }, "./src/schemas/MainConfigType.json": { - "fileMd5": "b35dc3c910e178d711532cbf2a31d5a6" + "fileMd5": "0af6fae41e565e20eebfee8c0cd0dec7" + }, + "./src/schemas/CustomSkinManifest.json": { + "fileMd5": "d470a5cbc371ad4458f1fb9b4829a923" } } \ No newline at end of file diff --git a/server/src/services/CustomSkinService.ts b/server/src/services/CustomSkinService.ts new file mode 100644 index 0000000..d771ace --- /dev/null +++ b/server/src/services/CustomSkinService.ts @@ -0,0 +1,68 @@ +import { CustomSkinManifest } from "../types/customSkin"; +import * as fs from 'fs'; +import { validator } from "../utils/validator"; + +export type CustomSkinInfo = CustomSkinManifest & { + url: string; +} + +export class CustomSkinService { + private static _instance: CustomSkinService; + + private skinsPath = 'public/skins'; + private skinsBaseUrl = '/skins'; + + public skins: CustomSkinInfo[] = []; + + public static createInstance() { + if (!this._instance) { + this._instance = new CustomSkinService(); + } + } + + public static get instance() { + this.createInstance(); + return this._instance; + } + + public async initialize() { + await this.scanSkins(); + } + + public async scanSkins() { + if (!fs.existsSync(this.skinsPath)) { + console.log('CustomSkins path not found, ignore.'); + return; + } + + const files = await fs.promises.readdir(this.skinsPath); + const skins: CustomSkinInfo[] = []; + for (const skinDir of files) { + if (!fs.statSync(`${this.skinsPath}/${skinDir}`).isDirectory()) { + continue; + } + + const manifestPath = `${this.skinsPath}/${skinDir}/skin.json`; + if (!fs.existsSync(manifestPath)) { + continue; + } + + const manifestContent = await fs.promises.readFile(manifestPath, { encoding: 'utf-8' }); + const manifest: CustomSkinManifest = JSON.parse(manifestContent); + + if (!validator.validateCustomSkinManifest(manifest)) { + console.error(`Invalid manifest for skin ${skinDir}`); + console.error(validator.validateCustomSkinManifest.errors); + continue; + } + + let skinIndexUrl = `${this.skinsBaseUrl}/${skinDir}/${manifest.main}`; + skins.push({ + ...manifest, + url: skinIndexUrl, + }); + } + + this.skins = skins; + } +} \ No newline at end of file diff --git a/server/src/services/DGLabPulse.ts b/server/src/services/DGLabPulse.ts index 030f086..302adb1 100644 --- a/server/src/services/DGLabPulse.ts +++ b/server/src/services/DGLabPulse.ts @@ -29,7 +29,6 @@ export class DGLabPulseService { public pulseList: DGLabPulseInfo[] = []; private pulseConfig: ReactiveConfig; - private pulseQRDir = 'data/pulse-qrcode'; private pulseConfigPath = 'data/pulse.json5'; private events = new EventEmitter(); diff --git a/server/src/types/config.ts b/server/src/types/config.ts index 3686397..e0d13b1 100644 --- a/server/src/types/config.ts +++ b/server/src/types/config.ts @@ -11,7 +11,7 @@ export type MainConfigType = { webWsBaseUrl?: string | null; /** DG-Lab客户端连接时的WebSocket URL */ clientWsBaseUrl?: string | null; - /** API的基础URL,不需要“/api”,某些游戏插件可能不支持HTTPS,需要配置HTTP接口 */ + /** API的基础URL,不需要“api”,末尾需要“/”,某些游戏插件可能不支持HTTPS,需要配置HTTP接口 */ apiBaseHttpUrl?: string; /** 波形配置文件路径 */ pulseConfigPath: string; diff --git a/server/src/types/customSkin.ts b/server/src/types/customSkin.ts new file mode 100644 index 0000000..8e45827 --- /dev/null +++ b/server/src/types/customSkin.ts @@ -0,0 +1,14 @@ +export type CustomSkinParamDef = { + prop: string; + type: 'boolean' | 'int' | 'float' | 'string' | 'select'; + name: string; + help?: string; + options?: { value: string, label: string }[]; +}; + +export type CustomSkinManifest = { + name: string; + main: string; + help?: string; + params?: CustomSkinParamDef[]; +}; \ No newline at end of file diff --git a/server/src/types/game.ts b/server/src/types/game.ts index a3107a4..cf8ee21 100644 --- a/server/src/types/game.ts +++ b/server/src/types/game.ts @@ -19,6 +19,11 @@ export interface MainGameConfig { pulseChangeInterval: number; } +export interface GameConnectionConfig { + /** 游戏链接码 */ + connectCodeList: string[]; +} + export interface GameCustomPulseConfig { customPulseList: any[]; } \ No newline at end of file diff --git a/server/src/utils/validator.ts b/server/src/utils/validator.ts index 319ad27..b883957 100644 --- a/server/src/utils/validator.ts +++ b/server/src/utils/validator.ts @@ -12,6 +12,7 @@ class TypeValidator { this.validators.set('GameStrengthConfig', this.ajv.compile(await import('../schemas/GameStrengthConfig.json'))); this.validators.set('GameCustomPulseConfig', this.ajv.compile(await import('../schemas/GameCustomPulseConfig.json'))); this.validators.set('MainConfigType', this.ajv.compile(await import('../schemas/MainConfigType.json'))); + this.validators.set('CustomSkinManifest', this.ajv.compile(await import('../schemas/CustomSkinManifest.json'))); } public validate(type: string, data: any): boolean { @@ -38,6 +39,10 @@ class TypeValidator { public get validateMainConfigType() { return this.validators.get('MainConfigType')!; } + + public get validateCustomSkinManifest() { + return this.validators.get('CustomSkinManifest')!; + } } export const validator = new TypeValidator(); \ No newline at end of file