├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .luarc.json
├── LICENSE
├── README.md
├── decode_pulse_qrcode.py
├── docs
├── api.md
└── images
│ └── screenshot-widget.png
├── frontend
├── .gitignore
├── .vscode
│ └── extensions.json
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ ├── lib
│ │ └── zxing_full.wasm
│ └── vite.svg
├── src
│ ├── App.vue
│ ├── ViewerApp.vue
│ ├── apis
│ │ ├── dgLabSocketApi.ts
│ │ ├── socketApi.ts
│ │ └── webApi.ts
│ ├── assets
│ │ ├── battery.svg
│ │ ├── bluetooth.svg
│ │ ├── fire-white.svg
│ │ └── vue.svg
│ ├── charts
│ │ ├── Bar1.vue
│ │ ├── Battery1.vue
│ │ ├── Circle1.vue
│ │ ├── HealthBar1.vue
│ │ ├── bar1
│ │ │ └── pills-mask.svg
│ │ ├── chartRoutes.ts
│ │ ├── healthBar1
│ │ │ ├── Heart.vue
│ │ │ ├── heart-mask.svg
│ │ │ └── joystix.monospace-regular.otf
│ │ ├── types.d.ts
│ │ ├── types
│ │ │ └── ChartParamDef.ts
│ │ └── utils
│ │ │ └── transitionRef.ts
│ ├── components
│ │ ├── card
│ │ │ └── PulseCard.vue
│ │ ├── dialogs
│ │ │ ├── ClientInfoDialog.vue
│ │ │ ├── ConfigSavePrompt.vue
│ │ │ ├── ConnectToClientDialog.vue
│ │ │ ├── ConnectToSavedClientsDialog.vue
│ │ │ ├── GetLiveCompDialog.vue
│ │ │ ├── ImportPulseDialog.vue
│ │ │ ├── PromptDialog.vue
│ │ │ └── SortPulseDialog.vue
│ │ ├── partials
│ │ │ ├── ConnectToSavedClientsList.vue
│ │ │ ├── CoyoteBluetoothPanel.vue
│ │ │ ├── CoyoteBluetoothService.vue
│ │ │ └── CustomToastContent.vue
│ │ └── transitions
│ │ │ └── FadeAndSlideTransitionGroup.vue
│ ├── lib
│ │ └── dg-pulse-helper
│ │ │ ├── DGLabPulseHelper.ts
│ │ │ ├── DGLabPulseQRHelper.ts
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ ├── main.ts
│ ├── pages
│ │ ├── Controller.vue
│ │ └── controller
│ │ │ ├── GameConnection.vue
│ │ │ ├── PulseSettings.vue
│ │ │ └── StrengthSettings.vue
│ ├── stores
│ │ ├── ChartValueStore.ts
│ │ ├── ClientsStore.ts
│ │ ├── CoyoteBTStore.ts
│ │ └── RemoteNotificationStore.ts
│ ├── style.scss
│ ├── type
│ │ ├── common.ts
│ │ ├── dg.ts
│ │ └── pulse.ts
│ ├── utils
│ │ ├── CoyoteBluetoothController.ts
│ │ ├── coyotePulse.ts
│ │ ├── event.ts
│ │ ├── request.ts
│ │ ├── response.ts
│ │ └── utils.ts
│ ├── viewer.ts
│ ├── vite-env.d.ts
│ └── workers
│ │ ├── AbstractBluetoothConnector.ts
│ │ ├── SocketToCoyote2.ts
│ │ └── SocketToCoyote3.ts
├── tsconfig.json
├── tsconfig.node.json
├── viewer.html
└── vite.config.ts
├── package-dist.json
├── package-lock.json
├── package.json
├── sdk
├── auto_hot_key.ahk
└── cheat_engine_sdk.lua
├── server
├── cli
│ └── build-schema.js
├── config.example-server.yaml
├── config.example.yaml
├── data
│ └── pulse.json5
├── package-lock.json
├── package.json
├── public
│ └── .gitkeep
├── src
│ ├── config.ts
│ ├── controllers
│ │ ├── game
│ │ │ ├── CoyoteGameController.ts
│ │ │ └── actions
│ │ │ │ ├── AbstractGameAction.ts
│ │ │ │ └── GameFireAction.ts
│ │ ├── http
│ │ │ ├── GameApi.ts
│ │ │ └── Web.ts
│ │ └── ws
│ │ │ ├── DGLabWS.ts
│ │ │ └── WebWS.ts
│ ├── index.ts
│ ├── managers
│ │ ├── CoyoteGameManager.ts
│ │ ├── DGLabWSManager.ts
│ │ └── WebWSManager.ts
│ ├── model
│ │ └── config
│ │ │ ├── CustomPulseConfigUpdater.ts
│ │ │ ├── GamePlayConfigUpdater.ts
│ │ │ ├── GamePlayUserConfigUpdater.ts
│ │ │ └── MainGameConfigUpdater.ts
│ ├── modules.d.ts
│ ├── router.ts
│ ├── schemas
│ │ ├── CustomSkinManifest.json
│ │ ├── GameCustomPulseConfig.json
│ │ ├── GameStrengthConfig.json
│ │ ├── MainConfigType.json
│ │ ├── MainGameConfig.json
│ │ └── schemas.json
│ ├── services
│ │ ├── CoyoteGameConfigService.ts
│ │ ├── CustomSkinService.ts
│ │ ├── DGLabPulse.ts
│ │ └── SiteNotificationService.ts
│ ├── types.d.ts
│ ├── types
│ │ ├── config.ts
│ │ ├── customSkin.ts
│ │ ├── dg.ts
│ │ ├── game.ts
│ │ ├── gamePlay.ts
│ │ └── server.ts
│ └── utils
│ │ ├── EventStore.ts
│ │ ├── ExEventEmitter.ts
│ │ ├── MultipleLinkedMap.ts
│ │ ├── ObjectUpdater.ts
│ │ ├── PulsePlayList.ts
│ │ ├── WebSocketAsync.ts
│ │ ├── WebSocketRouter.ts
│ │ ├── checkUpdate.ts
│ │ ├── latencyLogger.ts
│ │ ├── onExit.ts
│ │ ├── task.ts
│ │ ├── utils.ts
│ │ ├── validator.ts
│ │ └── websocket.ts
└── tsconfig.json
└── version.json
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ "main" ]
9 | paths-ignore:
10 | - 'docs/**'
11 | - 'sdk/**'
12 | - 'README.md'
13 | pull_request:
14 | branches: [ "main" ]
15 |
16 | jobs:
17 | build:
18 |
19 | runs-on: ubuntu-latest
20 |
21 | strategy:
22 | matrix:
23 | node-version: [20.x]
24 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
25 |
26 | steps:
27 | - uses: actions/checkout@v4
28 | - name: Use Node.js ${{ matrix.node-version }}
29 | uses: actions/setup-node@v4
30 | with:
31 | node-version: ${{ matrix.node-version }}
32 | cache: 'npm'
33 | - name: Install frontend packages
34 | run: npm ci
35 | working-directory: ./frontend
36 | - name: Build frontend
37 | run: npm run build --if-present
38 | working-directory: ./frontend
39 | - name: Install server packages
40 | run: npm ci
41 | working-directory: ./server
42 | - name: Build server
43 | run: npm run build --if-present
44 | working-directory: ./server
45 | - name: Install root packages
46 | run: npm ci
47 | - name: Migrate files
48 | run: npm run build:migrate
49 | - name: Build nodejs release
50 | run: npm run build:pkg:clean && npm run build:pkg:assets && npm run build:pkg:nodejs
51 | - name: Move Files to artifact dir
52 | run: mkdir -p artifacts && mv build artifacts/coyote-game-hub
53 | - name: Archive nodejs artifact
54 | uses: actions/upload-artifact@v4
55 | with:
56 | name: coyote-game-hub-nodejs-server
57 | path: |
58 | artifacts
59 | - name: Clean up
60 | run: rm -rf artifacts
61 | - name: Build Windows executable
62 | run: npm run build:pkg:clean && npm run build:pkg:assets && npm run build:pkg:win
63 | - name: Move Files to artifact dir
64 | run: mkdir -p artifacts && mv build artifacts/coyote-game-hub
65 | - name: Archive Windows artifact
66 | uses: actions/upload-artifact@v4
67 | with:
68 | name: coyote-game-hub-windows-amd64-dist
69 | path: |
70 | artifacts
71 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.VSCodeCounter
2 | /.idea
3 | /.vs
4 | .DS_Store
5 |
6 | node_modules/
7 |
8 | /server/config.yaml
9 | /server/dist
10 | /server/public
11 | !/server/public/.gitkeep
12 |
13 | /frontend/dist
14 | /frontend/src/auto-imports.d.ts
15 | /frontend/src/components.d.ts
16 |
17 | /build
18 |
19 | game-config/
--------------------------------------------------------------------------------
/.luarc.json:
--------------------------------------------------------------------------------
1 | {
2 | "workspace.library": [],
3 | "diagnostics.disable": []
4 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
战败惩罚——郊狼游戏控制器
2 |
7 |
8 |
15 |
16 |
17 |

18 |
19 |
20 | ## 注意事项
21 |
22 | 请遵守直播平台的相关规定,不要违规使用本组件,如果使用本组件造成直播间封禁等后果与本组件作者无关。
23 |
24 | ## 使用方法(二进制发行版)
25 |
26 | 1. 从[Github Actions](https://github.com/hyperzlib/DG-Lab-Coyote-Game-Hub/actions)下载编译后的文件:[点击跳转](https://github.com/hyperzlib/DG-Lab-Coyote-Game-Hub/actions)
27 | 2. 解压后运行```coyote-game-hub-server.exe```启动服务器
28 |
29 | ## 使用方法(命令行)
30 |
31 | (以下样例中使用了```pnpm```安装依赖,你也可以使用```npm```或者```yarn```)
32 |
33 | 1. 进入```server```目录,运行```pnpm install```安装依赖
34 |
35 | 2. 进入```frontend```目录,运行```pnpm install```安装依赖
36 |
37 | 3. 在项目根目录运行```pnpm install```安装依赖,运行```npm run build```编译项目
38 |
39 | 4. 在项目根目录运行```npm start```启动服务器
40 |
41 | 5. 浏览器打开```http://localhost:8920```,即可看到控制面板
42 |
43 | ## 项目结构
44 |
45 | - ```server```:服务器端代码
46 | - ```frontend```:前端代码
47 |
48 | ## 构建
49 |
50 | ### 环境准备
51 | 在全局环境安装下面的包:
52 | ```
53 | npm install -g nexe
54 | npm install -g vite
55 | npm install -g pkg
56 | ```
57 |
58 | ### 构建工程
59 | 按顺序运行下面的指令
60 | ```
61 | npm run build
62 | npm run build:pkg
63 | npm run build:pkg:assets
64 | npm run build:pkg:nodejs
65 | npm run build:pkg:linux
66 | npm run build:pkg
67 | ```
68 |
69 | ### 产物位置
70 | 之后可以在 `build/` 目录下发现构建的产物
--------------------------------------------------------------------------------
/decode_pulse_qrcode.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | import base64
4 | import deflate
5 | import zxing
6 |
7 | def decode_pulse_str(pulse_str: str) -> str:
8 | binary = bytes.fromhex(pulse_str)
9 | # Inflate
10 | data = deflate.gzip_decompress(binary)
11 | # Decode base64
12 | data = base64.b64decode(data)
13 | return data
14 |
15 | def read_pulse_str_from_qrcode(file_path: str) -> str:
16 | reader = zxing.BarCodeReader()
17 | barcode = reader.decode(file_path)
18 | url: str = barcode.parsed
19 | pulse_str = url.split("#DGLAB-PULSE#")[1]
20 |
21 | return pulse_str
22 |
23 | if __name__ == "__main__":
24 | if len(sys.argv) != 2:
25 | print("Usage: python decode_pulse_qrcode.py ")
26 | sys.exit(1)
27 |
28 | file_path = sys.argv[1]
29 | pulse_str = read_pulse_str_from_qrcode(file_path)
30 | data = decode_pulse_str(pulse_str)
31 | print(data)
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | # 第三方插件接口
2 |
3 | ## 获取游戏信息
4 |
5 | ```sh
6 | GET /api/v2/game/{clientId}
7 | ```
8 |
9 | ### 请求参数
10 |
11 | 无
12 |
13 | ### 响应
14 |
15 | ```json5
16 | {
17 | "status": 1,
18 | "code": "OK",
19 | "strengthConfig": {
20 | "strength": 5, // 基础强度
21 | "randomStrength": 5 // 随机强度,(强度范围:[strength, strength+randomStrength])
22 | },
23 | "gameConfig": {
24 | "strengthChangeInterval": [15, 30], // 随机强度变化间隔,单位:秒
25 | "enableBChannel": false, // 是否启用B通道
26 | "bChannelStrengthMultiplier": 1, // B通道强度倍数
27 | "pulseId": "d6f83af0", // 当前波形列表,可能是string或者string[]
28 | "pulseMode": "single", // 波形播放模式,single: 单个波形, sequence: 列表顺序播放, random: 随机播放
29 | "pulseChangeInterval": 60
30 | },
31 | "clientStrength": {
32 | "strength": 0, // 客户端当前强度
33 | "limit": 20 // 客户端强度上限
34 | },
35 | "currentPulseId": "d6f83af0" // 当前正在播放的波形ID
36 | }
37 | ```
38 |
39 | ## 获取波形列表
40 |
41 | ```sh
42 | GET /api/v2/pulse_list # 获取服务器配置的波形列表
43 | GET /api/v2/game/{clientId}/pulse_list # 获取完整的波形列表(包括客户端自定义波形)
44 | ```
45 |
46 | ### 请求参数
47 |
48 | 无
49 |
50 | ### 响应
51 |
52 | ```json5
53 | {
54 | "status": 1,
55 | "code": "OK",
56 | "pulseList": [
57 | {
58 | "id": "d6f83af0", // 波形ID
59 | "name": "呼吸" // 波形名称
60 | },
61 | // ...
62 | ]
63 | }
64 | ```
65 |
66 | ## 获取游戏强度信息
67 |
68 | ```sh
69 | GET /api/v2/game/{clientId}/strength
70 | ```
71 |
72 | ### 请求参数
73 |
74 | 无
75 |
76 | ### 响应
77 |
78 | ```json5
79 | {
80 | "status": 1,
81 | "code": "OK",
82 | "strengthConfig": {
83 | "strength": 5, // 基础强度
84 | "randomStrength": 5 // 随机强度,(强度范围:[strength, strength+randomStrength])
85 | }
86 | }
87 | ```
88 |
89 | ## 设置游戏强度配置
90 |
91 | ```sh
92 | POST /api/v2/game/{clientId}/strength
93 | ```
94 |
95 | ### 请求参数
96 |
97 | 如果服务器配置```allowBroadcastToClients: true```,可以将请求地址中的```{clientId}```设置为```all```,将设置到所有客户端。
98 |
99 |
100 | 以下是请求参数的类型定义:
101 |
102 | ```typescript
103 | type SetStrengthConfigRequest = {
104 | strength?: {
105 | add?: number; // 增加基础强度
106 | sub?: number; // 减少强度
107 | set?: number; // 设置强度
108 | },
109 | randomStrength?: {
110 | add?: number; // 增加随机强度
111 | sub?: number; // 减少强度
112 | set?: number; // 设置强度
113 | }
114 | }
115 | ```
116 |
117 | 使用JSON POST格式发送请求的Post Body:
118 |
119 | ```json5
120 | {
121 | "strength": {
122 | "add": 1
123 | }
124 | }
125 | ```
126 |
127 | 使用x-www-form-urlencoded格式发送请求的Post Body:
128 |
129 | ```html
130 | strength.add=1
131 | ```
132 |
133 | 强度配置在服务端已做限制,不会超出范围。插件可以随意发送请求,不需要担心超出范围。
134 |
135 | ### 响应
136 |
137 | ```json5
138 | {
139 | "status": 1,
140 | "code": "OK",
141 | "message": "成功设置了 1 个游戏的强度配置",
142 | "successClientIds": [
143 | "3ab0773d-69d0-41af-b74b-9c6ce6507f65"
144 | ]
145 | }
146 | ```
147 |
148 | ## 获取游戏当前波形ID
149 |
150 | ```sh
151 | GET /api/v2/game/{clientId}/pulse
152 | ```
153 |
154 | ### 请求参数
155 |
156 | 无
157 |
158 | ### 响应
159 |
160 | ```json5
161 | {
162 | "status": 1,
163 | "code": "OK",
164 | "pulseId": "d6f83af0"
165 | }
166 | ```
167 |
168 | 或
169 |
170 | ```json5
171 | {
172 | "status": 1,
173 | "code": "OK",
174 | "pulseId": [
175 | "d6f83af0",
176 | "7eae1e5f",
177 | "eea0e4ce",
178 | "2cbd592e"
179 | ]
180 | }
181 | ```
182 |
183 | ## 设置游戏当前波形ID
184 |
185 | ```sh
186 | POST /api/v2/game/{clientId}/pulse
187 | ```
188 |
189 | ### 请求参数
190 |
191 | 如果服务器配置```allowBroadcastToClients: true```,可以将请求地址中的```{clientId}```设置为```all```,将设置到所有客户端。
192 |
193 | 使用JSON POST格式发送请求的Post Body:
194 |
195 | ```json5
196 | {
197 | "pulseId": "d6f83af0" // 波形ID
198 | }
199 | ```
200 |
201 | 或
202 |
203 | ```json5
204 | {
205 | "pulseId": [
206 | "d6f83af0",
207 | "7eae1e5f",
208 | "eea0e4ce",
209 | "2cbd592e"
210 | ] // 波形ID列表
211 | }
212 | ```
213 |
214 | 使用x-www-form-urlencoded格式发送请求的Post Body:
215 |
216 | ```html
217 | pulseId=d6f83af0
218 | ```
219 |
220 | 或
221 |
222 | ```html
223 | pulseId[]=d6f83af0&pulseId[]=7eae1e5f&pulseId[]=eea0e4ce&pulseId[]=2cbd592e
224 | ```
225 |
226 | ### 响应
227 |
228 | ```json5
229 | {
230 | "status": 1,
231 | "code": "OK",
232 | "message": "成功设置了 1 个游戏的波形ID",
233 | "successClientIds": [
234 | "3ab0773d-69d0-41af-b74b-9c6ce6507f65"
235 | ]
236 | }
237 | ```
238 |
239 | ## 请求错误响应
240 |
241 | ```json5
242 | {
243 | "status": 0,
244 | "code": "ERR::INVALID_REQUEST",
245 | "message": "请求参数不正确"
246 | }
247 | ```
248 |
249 |
250 | ## 一键开火
251 |
252 | ```sh
253 | POST /api/v2/game/{clientId}/action/fire
254 | ```
255 |
256 | ### 请求参数
257 |
258 | 如果服务器配置```allowBroadcastToClients: true```,可以将请求地址中的```{clientId}```设置为```all```,将设置到所有客户端。
259 |
260 |
261 | 以下是请求参数的类型定义:
262 |
263 | ```json5
264 | {
265 | "strength": 20, // 一键开火强度,最高40
266 | "time": 5000, // (可选)一键开火时间,单位:毫秒,默认为5000,最高30000(30秒)
267 | "override": false, // (可选)多次一键开火时,是否重置时间,true为重置时间,false为叠加时间,默认为false
268 | "pulseId": "d6f83af0" // (可选)一键开火的波形ID
269 | }
270 | ```
271 |
272 | 使用JSON POST格式发送请求的Post Body:
273 |
274 | ```json5
275 | {
276 | "strength": 20,
277 | "time": 5000
278 | }
279 | ```
280 |
281 | 使用x-www-form-urlencoded格式发送请求的Post Body:
282 |
283 | ```html
284 | strength=20&time=5000
285 | ```
286 |
287 | 强度配置在服务端已做限制,不会超出范围。
288 |
289 | ### 响应
290 |
291 | ```json5
292 | {
293 | "status": 1,
294 | "code": "OK",
295 | "message": "成功向 1 个游戏发送了一键开火指令",
296 | "successClientIds": [
297 | "3ab0773d-69d0-41af-b74b-9c6ce6507f65"
298 | ]
299 | }
300 | ```
301 |
--------------------------------------------------------------------------------
/docs/images/screenshot-widget.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hyperzlib/DG-Lab-Coyote-Game-Hub/79e1cb0650fd32cc1f1bf5bbe71e3a1635d63e55/docs/images/screenshot-widget.png
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/frontend/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["Vue.volar"]
3 | }
4 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # Vue 3 + TypeScript + Vite
2 |
3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `
13 |