一个用 C# / WPF (.NET 8) 写的 Dota 2 桌面辅助 Overlay。基于 Valve 官方提供的 Game State Integration (GSI) 读取本机游戏数据,在屏幕上渲染半透明信息面板并提供语音播报。
合规说明:仅消费 Valve 官方 GSI 在本机回调的 JSON 数据。不做任何注入、内存读取、抓包、按键模拟。这与 Overwolf 等同类做法的原理一致。
半透明置顶浮窗,可拖动 / 缩放 / 鼠标穿透,实时显示英雄状态、神符 / 肉山 / 大招倒计时。
GSI 连接状态、实时游戏数据、智能建议;支持一键安装 GSI 配置文件、重启 GSI 服务。
DataGrid 直接查看与编辑全局热键,支持「捕获组合键」一键绑定。
按阶段 / 关键词检索游戏经验,可直接编辑 JSON 后热加载。
| 模块 | 说明 |
|---|---|
| 敌我血量监控 | 我方英雄实时血条 + 数值;敌方信息基于 GSI hero.team3 节点(观战 / 演示模式可见) |
| 蓝量监控 | 实时蓝量条 + 数值 + 百分比 |
| 神符刷新提示 | 每 2 分钟周期倒计时;30s / 10s 自动语音播报;区分水神符 / 强化神符 / 赏金能量神符 |
| Roshan 提示 | 死亡 / 重生窗口(min~max)显示;状态切换自动语音播报 |
| 我方 / 敌方大招 | 大招冷却列表;敌方大招就绪自动语音播报,去抖避免重复 |
| 团战关键技能 | 与大招同列表展示,可在英雄白名单中扩展任意关键技能 |
| 经验知识库 | 内置 JSON 知识库,按阶段 / 英雄 / 关键词检索;按局势(HP/MP/时间)自动推荐建议 |
| 全局快捷键 | 任意时刻呼出主面板 / 显隐 Overlay / 缩放 / 静音 |
| Overlay 缩放 | 0.5x ~ 2.5x 平滑缩放、不透明度调节、鼠标穿透模式 |
| DPI 自适应 | manifest 启用 PerMonitorV2,4K 高分屏文字不糊 |
| 设置持久化 | 所有偏好保存到 %AppData%/Dota2Copilot/settings.json,含一次性迁移逻辑 |
- Windows 10 / 11
- .NET 8 SDK(下载)
- Steam 上正版 Dota 2
# 克隆/进入项目目录后
dotnet build .\Dota2Copilot.sln -c Release
.\src\Dota2Copilot\bin\Release\net8.0-windows\Dota2Copilot.exe启动后会同时出现:
- Overlay 浮窗(默认右上角,可拖动)
- 主面板(默认显示,关闭按钮等于隐藏到后台)
打开主面板 → 概览 → 安装 GSI 配置文件,选择 Dota 2 的 game/dota 目录,常见位置:
<Steam>/steamapps/common/dota 2 beta/game/dota
程序会自动写入:
<dota>/cfg/gamestate_integration/gamestate_integration_dota2copilot.cfg
在 Steam 库中右键 Dota 2 → 属性 → 启动选项 中追加:
-gamestateintegration
重启游戏。开打后 GSI 会向 http://127.0.0.1:39999/ 自动 POST JSON。
都可以在主面板「快捷键」页捕获修改。失败时会单条提示哪个键被占用,其它键继续生效。
| 功能 | 默认 |
|---|---|
| 显示/隐藏 主面板 | Ctrl + Shift + D |
| 显示/隐藏 Overlay | Ctrl + Alt + O |
| 放大 Overlay | Ctrl + Alt + = |
| 缩小 Overlay | Ctrl + Alt + - |
| 静音/恢复 语音 | Ctrl + Alt + M |
避开 Ctrl+Shift+O / F12 / Ctrl+Alt+A 等被 Steam、NVIDIA、微信/QQ 截图、输入法占用的键。
- GSI 端口配置 + 重启 GSI 服务 + 安装 GSI 配置文件
- 实时游戏数据:游戏状态 / 英雄 / 神符 / 肉山
- 智能建议:根据当前 HP/MP/游戏时间自动从知识库挑一条建议
- 隐藏到后台:等同关闭按钮,应用仍在运行
- 退出 Copilot:完全结束应用
- UI 缩放:滑块 0.5x ~ 2.5x(也可用
Ctrl+Alt+=/Ctrl+Alt+-) - 不透明度:0.3 ~ 1.0
- 鼠标穿透:勾选后 Overlay 不抢点击,但也无法拖动;要调位置先取消勾选
- 显示 / 隐藏 Overlay 按钮
- 显示项开关(血量/蓝量/神符/肉山/我方大招/敌方大招/关键技能)
- 启用开关、音量、语速
- "测试语音"按钮验证 TTS
- DataGrid 显示所有热键
- 选中行 → "捕获组合键" → 按下任意组合 → "应用并保存"
- 单键冲突会弹窗指出哪个键被占用,其它键照样生效
- 关键词搜索 + 阶段筛选(early / mid / late)
- "刷新"按钮热重载磁盘上的
knowledge_base.json - "打开知识库 JSON" 直接在记事本里编辑
- 拖动:左键按住任意空白处即可(鼠标穿透模式下需关闭穿透)
- 缩放:快捷键或主面板滑块
- 位置/大小/不透明度:自动持久化到设置文件,下次启动恢复
┌─────────────────────────────────────────────────────────────┐
│ WPF Process │
│ │
│ ┌────────────────┐ POST / ┌─────────────────────┐ │
│ │ Dota 2 Game │ ───────────▶ │ GsiHttpServer │ │
│ │ (-gameint…) │ JSON │ (HttpListener) │ │
│ └────────────────┘ └──────────┬──────────┘ │
│ │ PayloadReceived│
│ ▼ │
│ ┌─────────────────────┐ │
│ │ GameStateTracker │ │
│ │ - GSI 解析归一化 │ │
│ │ - 神符/Roshan 计时 │ │
│ │ - 大招就绪事件 │ │
│ │ - INotifyProperty │ │
│ └──────┬──────────────┘ │
│ │ Property/Alert │
│ ┌─────────────────────────────────┼─────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Overlay │ │ MainWindow │ │ VoiceService │ │
│ │ Window │ │ (Tabs) │ │ (SAPI 队列) │ │
│ └──────────┘ └──────┬───────┘ └──────────────────┘ │
│ │ │
│ ┌────────┴────────┐ │
│ ▼ ▼ │
│ ┌───────────────┐ ┌──────────────┐ │
│ │ HotkeyManager │ │ KnowledgeBase│ │
│ │ (Win32 hotkey)│ │ (JSON 检索) │ │
│ └───────────────┘ └──────────────┘ │
│ │
│ ┌────────────┐ │
│ │AppSettings │ %AppData%/Dota2Copilot/ │
│ │ │ settings.json │
│ └────────────┘ │
└─────────────────────────────────────────────────────────────┘
- 基于
System.Net.HttpListener的轻量服务,只接受127.0.0.1的 POST - 默认端口
39999,可在主面板修改并热重启 - 反序列化为
GsiPayload后通过PayloadReceived事件传出 - 解析失败 / 端口异常通过
ServerError事件上报,不会让进程崩溃
- 实现
INotifyPropertyChanged,WPF 控件可直接绑定 - 同时兼容单人模式(GSI 平铺字段)和观战/Demo 模式(
team2/team3 → playerN嵌套结构) - 神符规则:
clock_time mod 120计算下次刷新;30s/10s 触发AlertRaised;类型识别水/强化/赏金能量 - Roshan:优先取顶层
roshan节点,回退到map.roshan_state;状态变化触发语音 - 大招就绪去抖:用
Dictionary<owner|name, bool>记录上一次状态,避免重复播报
- 封装 Win32
RegisterHotKey/UnregisterHotKey - 通过
HwndSource拦截WM_HOTKEY = 0x0312消息 - 默认带
MOD_NOREPEAT防长按重复触发 - 注册失败抛带
WinErr=N的异常,上层做友好提示
- 基于
System.Speech.Synthesis.SpeechSynthesizer - 单线程
BlockingCollection<string>队列,避免 UI 线程被Speak阻塞 - 启动时自动选择系统中第一个
zh-*语音
- 简单 JSON 文件 +
LINQ多字段过滤 Recommend(gameTime, hero, hpPct, mpPct)根据局势返回最匹配条目- 支持热重载("刷新"按钮)
WindowStyle=None + AllowsTransparency=True + Topmost=True- 整体放在一个
<Grid>上挂ScaleTransform,缩放即改ScaleX/Y WS_EX_TOOLWINDOW防止出现在 Alt+Tab;WS_EX_TRANSPARENT切换鼠标穿透- 通过
App.State.PropertyChanged自动刷新
- 5 个 Tab + 全局热键 Attach 点
- 关闭按钮重写为隐藏:保证应用驻留后台、热键继续生效
- "退出 Copilot" 按钮才真正
Application.Shutdown()
Dota 2 (GSI cfg)
│ POST JSON (~10 Hz, "throttle 0.1" 控制)
▼
GsiHttpServer ── PayloadReceived ──▶ Dispatcher.Invoke
│
▼
GameStateTracker.Apply
├─▶ INotifyPropertyChanged ──▶ OverlayWindow / MainWindow
└─▶ AlertRaised ──▶ VoiceService.Speak
UI 线程性能要点:
- HTTP 接收在线程池,反序列化也在线程池
- 仅
State.Apply与 UI 更新走Dispatcher - 大招列表用
BindingList<UltimateInfo> + ResetBindings()批量刷新
dota2-copilot/
├─ Dota2Copilot.sln
├─ README.md
├─ .gitignore
├─ demo/ README 中引用的截图
│ ├─ overlay.png
│ ├─ main-overview.png
│ ├─ main-hotkeys.png
│ └─ main-knowledge.png
└─ src/Dota2Copilot/
├─ Dota2Copilot.csproj .NET 8 + WPF + System.Speech
├─ app.manifest PerMonitorV2 DPI 感知
├─ App.xaml / App.xaml.cs 全局服务装配 / 启动迁移
├─ Gsi/
│ ├─ GsiPayload.cs GSI JSON 模型
│ └─ GsiHttpServer.cs 127.0.0.1:39999 HttpListener
├─ Services/
│ ├─ GameStateTracker.cs 状态聚合 / 神符 / Roshan / 大招事件
│ ├─ HotkeyManager.cs Win32 RegisterHotKey 封装
│ ├─ VoiceService.cs SAPI 中文 TTS 队列
│ ├─ KnowledgeBase.cs 经验库 + 局势自动推荐
│ └─ AppSettings.cs 配置持久化
├─ Views/
│ ├─ OverlayWindow.xaml(.cs) 无边框透明置顶悬浮,可拖动/缩放/穿透
│ └─ MainWindow.xaml(.cs) 概览 / Overlay / 语音 / 热键 / 知识库
└─ Resources/
├─ knowledge_base.json 可热编辑的经验条目
└─ gamestate_integration_dota2copilot.cfg
| 键 | 默认 | 说明 |
|---|---|---|
GsiPort |
39999 |
GSI 监听端口 |
VoiceEnabled |
true |
语音总开关 |
VoiceVolume / VoiceRate |
90 / 1 |
音量 0-100、语速 -10 ~ 10 |
OverlayScale |
1.4 |
启动缩放(< 1.2 会自动迁移到 1.4) |
OverlayOpacity |
0.85 |
Overlay 不透明度 |
OverlayLeft / OverlayTop |
100 / 100 |
Overlay 屏幕位置 |
Show* |
true |
各显示项开关 |
Hotkeys |
dict | 功能 ID → 组合键字符串 |
自动迁移(App.xaml.cs.Application_Startup):
OverlayScale < 1.2→ 升到1.4toggle_overlay == "Ctrl+Shift+O"→ 改为"Ctrl+Alt+O"(避开 Steam)
"uri" "http://127.0.0.1:39999/"
"timeout" "5.0"
"buffer" "0.1"
"throttle" "0.1"
"heartbeat" "10.0"
"data" {
"provider 1, map 1, player 1, hero 1, abilities 1,
items 1, events 1, buildings 1, league 1, draft 1,
wearables 1, minimap 1, roshan 1, couriers 1,
neutralitems 1"
}
[
{
"phase": "early", // early / mid / late,可空
"hero": "invoker", // 英雄 internal name 子串匹配,可空
"tags": ["卡尔", "连招"], // 关键词检索时也会匹配
"title": "祈求者团战",
"advice": "ZEEE 阵地战;EMP 在团前留蓝量..."
}
]AppSettings.Hotkeys默认表里加键,例如["screenshot"] = "Ctrl+Alt+P"MainWindow.InitHotkeyRows()添加 DataGrid 行MainWindow.HandleHotkey(string id)加case "screenshot":分支
GameStateTracker加属性 +INotifyPropertyChanged通知OverlayWindow.xaml拖一个<TextBlock>绑定即可- 必要时在
Apply()/ApplyAbilities()里补解析
- 在
GameStateTracker找到合适的状态变化点 AlertRaised?.Invoke(this, new GameAlert(GameAlertType.Custom, "提示词"))App.xaml.cs已统一桥接到VoiceService.Speak
直接编辑 Resources/knowledge_base.json,主面板「经验知识库 → 刷新」热加载。
- 已修:XAML 里
IsSelected="True"触发的SelectionChanged在控件初始化前被回调。RefreshKnowledgeGrid加了IsLoaded守卫 - 如还遇到,请贴 stack trace
旧实例还在后台。PowerShell 运行:
Get-Process Dota2Copilot -ErrorAction SilentlyContinue | Stop-Process -Force然后再 dotnet build。
ERROR_HOTKEY_ALREADY_REGISTERED,组合键已被其他程序占用。常见占用方:
- Steam Overlay(截图键,常占
Ctrl+Shift+O / F12) - NVIDIA GeForce Experience / ShadowPlay
- 微信、QQ 截图(
Ctrl+Alt+A) - 各类输入法
进入主面板「快捷键」→ 选中行 → 捕获组合键 → 换一个不冲突的组合 → 应用并保存。
- Dota 2 用 独占全屏 时 Overlay 会被遮蔽,请改"全屏窗口化"
- 多显示器:先把 Overlay 拖到 Dota 2 所在显示器(关闭"鼠标穿透"才能拖)
- 检查
OverlayLeft/Top是否被拖到屏幕外,删除settings.json重置
- 系统设置 → 时间和语言 → 语音 → 添加"中文(简体,中国)"语音包
- 重启应用(语音引擎在启动时选择 voice)
- 主面板「语音提示 → 测试语音」验证
- 启动项是否加了
-gamestateintegration? - cfg 文件名必须以
gamestate_integration_开头 - 端口被占?主面板"重启 GSI 服务"换一个端口,并同步修改 cfg 中的
uri - Windows 防火墙首次会弹窗,记得放行(仅 127.0.0.1 实际不需要)
- 项目已开启 PerMonitorV2 应该不会糊;若仍糊,确认 exe 属性 → 兼容性 → DPI 缩放设置 没被强制覆盖
关闭应用后删除:
%AppData%\Dota2Copilot\settings.json
下次启动会用全新默认值。