From d6212169afa2c55e2f8942f7f353e5681d76efbd Mon Sep 17 00:00:00 2001 From: voson Date: Thu, 30 Jul 2026 00:48:07 +0800 Subject: [PATCH] v0.5.109: hero role toolbar, skill side-by-side layout, taller detail panel. Move role filters between grid and detail, fix skill video/text layout and detail height jump, unify item dividers, drop OpenDota matchup cross, sync docs and assets. Co-authored-by: Cursor --- .gitignore | 1 - AGENTS.md | 15 +- ARCHITECTURE.md | 269 + CHANGELOG.md | 145 + DESIGN.md | 530 +- README.md | 4 +- pc/fetch_cdn_templates.py | 13 +- shared/audit_relations.py | 177 +- shared/data/heroes.json | 11296 ++++++++-------- shared/fetch_matchups.py | 86 - shared/matchup_cross.py | 281 - shared/paths.py | 2 + ...tchup_cross.py => test_stratz_matchups.py} | 84 +- web/_oss_static_assets.py | 1 + web/assets/role_icons/Carry.png | Bin 0 -> 4889 bytes web/assets/role_icons/Disabler.png | Bin 0 -> 6579 bytes web/assets/role_icons/Durable.png | Bin 0 -> 5283 bytes web/assets/role_icons/Escape.png | Bin 0 -> 4352 bytes web/assets/role_icons/Initiator.png | Bin 0 -> 5804 bytes web/assets/role_icons/Nuker.png | Bin 0 -> 5534 bytes web/assets/role_icons/Pusher.png | Bin 0 -> 4773 bytes web/assets/role_icons/Support.png | Bin 0 -> 4372 bytes web/assets/ui_icons/icon_armor.png | Bin 0 -> 1635 bytes web/assets/ui_icons/icon_attack_range.png | Bin 0 -> 1710 bytes web/assets/ui_icons/icon_attack_time.png | Bin 0 -> 3018 bytes web/assets/ui_icons/icon_damage.png | Bin 0 -> 2199 bytes web/assets/ui_icons/icon_magic_resist.png | Bin 0 -> 2864 bytes web/assets/ui_icons/icon_movement_speed.png | Bin 0 -> 2332 bytes web/assets/ui_icons/icon_projectile_speed.png | Bin 0 -> 1576 bytes web/assets/ui_icons/icon_turn_rate.png | Bin 0 -> 2834 bytes web/assets/ui_icons/icon_vision.png | Bin 0 -> 2450 bytes web/export_relations_site.py | 5 +- web/fetch_hero_portraits.py | 44 +- web/frontend/app.js | 223 +- web/frontend/config.js | 3 +- web/frontend/index.html | 103 +- web/frontend/style.css | 767 +- web/serve_relations.py | 100 +- 38 files changed, 7225 insertions(+), 6924 deletions(-) create mode 100644 ARCHITECTURE.md delete mode 100644 shared/fetch_matchups.py delete mode 100644 shared/matchup_cross.py rename shared/tests/{test_matchup_cross.py => test_stratz_matchups.py} (62%) create mode 100644 web/assets/role_icons/Carry.png create mode 100644 web/assets/role_icons/Disabler.png create mode 100644 web/assets/role_icons/Durable.png create mode 100644 web/assets/role_icons/Escape.png create mode 100644 web/assets/role_icons/Initiator.png create mode 100644 web/assets/role_icons/Nuker.png create mode 100644 web/assets/role_icons/Pusher.png create mode 100644 web/assets/role_icons/Support.png create mode 100644 web/assets/ui_icons/icon_armor.png create mode 100644 web/assets/ui_icons/icon_attack_range.png create mode 100644 web/assets/ui_icons/icon_attack_time.png create mode 100644 web/assets/ui_icons/icon_damage.png create mode 100644 web/assets/ui_icons/icon_magic_resist.png create mode 100644 web/assets/ui_icons/icon_movement_speed.png create mode 100644 web/assets/ui_icons/icon_projectile_speed.png create mode 100644 web/assets/ui_icons/icon_turn_rate.png create mode 100644 web/assets/ui_icons/icon_vision.png diff --git a/.gitignore b/.gitignore index 267e454..cad2dce 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ plans/ *_log.txt # Regenerable data caches / reports (re-fetch or re-run scripts) -shared/data/matchups.json shared/data/synergies.json shared/data/stratz_matchups.json shared/data/relations_audit.json diff --git a/AGENTS.md b/AGENTS.md index 43ba7bd..dd36233 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,9 +59,7 @@ climperor/ | `shared/http_utils.py` | 所有 fetch 脚本共用的 HTTP 请求、图标下载、Valve datafeed 加载(统一 UA / 超时;429/5xx 指数退避重试) | | `shared/import_relations_xlsx.py` | 从选将笔记 xlsx 种子导入 `shared/data/relations.json` | | `shared/fetch_stratz.py` | 拉取 STRATZ 对位优势 / 搭档协同 → `shared/data/stratz_matchups.json` / `shared/data/synergies.json`(需 token;`--mode matchups\|synergies`;供 `audit_relations.py`) | -| `shared/fetch_matchups.py` | 拉取 OpenDota 对位优势 → `shared/data/matchups.json`(供 `audit_relations.py` 交叉审计) | -| `shared/audit_relations.py` | OpenDota+STRATZ 交叉审计 `relations.json`,列出缺失英雄;另含 Web 对位 Top × OpenDota 的 `web_matchup_tops_cross` 质量段(agree/conflict/weak;不改写 relations) | -| `shared/matchup_cross.py` | Web 对位 Top 与 OpenDota 的基线校正交叉分类(供 audit / serve 富化;观测证据,不作机制结论) | +| `shared/audit_relations.py` | 用 STRATZ 对位/搭档缓存审计 `relations.json`,列出缺失英雄与候选边(不改写 relations;Web 对位页只读 `stratz_matchup_tops.json`) | | `shared/data/heroes.json` | 英雄表(id/key/attr/roles/aliases/abbr/tags + 基础属性/初始血蓝等;由 `pc/fetch_cdn_templates.py` 生成) | | `shared/data/relations.json` | 定性克制边 + 搭档边(可提交;手改 JSON,Web 站点只读) | @@ -126,11 +124,12 @@ climperor/ | `web/frontend/router.js` | Hash 路由:`parseHash` / `serializeHash` / `installRouter` / `syncStateToUrl`;状态↔URL 双向同步(顶层标签 `heroes\|rankings\|streamers\|matches\|trends\|mechanics\|items\|patches` / 英雄 + 子标签 `skills\|core\|fears\|trends\|matchups\|matches\|streamers\|patches` / Immortal 地区 `#/rankings[/region]` / 明星比赛 `#/matches[/account_id][?origin=pro\|china][&page=N]`(默认全部类型、每页 20 场) / 主播目录 `#/streamers` / 近 8 周走势榜 `#/trends[/bracket][?sort=pr]`(默认按胜率) / 机制查询 `#/mechanics[/{effect}]`(默认 `basic_dispel`) / 物品 / 版本 / 标签筛选 / 搜索;旧 `stats` / `#/rankings/meta` 别名兼容) | | `web/assets/hero_portraits/` | 官网横版头像(上分帝 Web;默认 wide 面部构图,非匹配模板) | | `web/assets/attr_icons/` | 官网主属性图标(力量/敏捷/智力/全才,上分帝 Web 用) | +| `web/assets/role_icons/` | Valve 选人定位筛选图标(透明 PNG;英雄页定位栏;本地 `/role-icon/`,线上 OSS `role-icon/`) | | `web/assets/rank_icons/` | 天梯勋章图标(OpenDota `rank_icon_1..8` 先锋→冠绝;走势段位选择器) | | `web/assets/item_icons/` | Steam CDN 装备图标(上分帝 Web常用装备) | | `web/assets/item_cat_icons/` | 官网商店分类图标(`itemcat_*.png`,物品页列头) | | `web/assets/ability_icons/` | Steam CDN 技能图标(上分帝 Web;按需缓存);`innate.png` 先天共用图标;`talent_tree.png` 天赋树触发图标 | -| `web/assets/ui_icons/` | dota2.com.cn 通用 UI 图标(`cooldown.png` 等,技能详情底栏冷却图标)+ 平台 logo(`platform_douyin.png`,主播页) | +| `web/assets/ui_icons/` | dota2.com.cn 通用 UI 图标(`cooldown.png` 等;`icon_damage.png` 等战斗属性图标;平台 logo) | | `web/assets/streamer_avatars/` | 主播头像缓存(由 `fetch_streamers.py` 写入;上分帝 Web「主播」) | | `web/assets/streamer_videos/` | 主播精选高光 mp4 + 同名 JPG 封面(手工放入;上分帝 Web「主播」卡片;勿提交,线上走 OSS `streamer-video/`) | | `web/assets/ability_videos/` | 官网技能演示视频(webm/mp4;勿提交,由脚本拉取;线上由阿里云 OSS `climperor` 桶 `ability-video/` 前缀托管) | @@ -142,7 +141,7 @@ climperor/ - `web/dist/` — 静态站点导出(`export_relations_site.py`) - `pc/results/` — 每局 JSON - `pc/failures/` — `--truth` 调试用错识裁切 -- `shared/data/matchups.json` / `synergies.json` / `stratz_matchups.json` / `relations_audit.json` 等 — 可再生成缓存与报告 +- `shared/data/synergies.json` / `stratz_matchups.json` / `relations_audit.json` 等 — 可再生成缓存与报告 - `web/data/hero_stats.json` / `hero_matches.json` / `stratz_*.json` 等 — 可再生成缓存 - `web/assets/ability_videos/` — 官网技能演示(体积大,可再拉取) - `web/assets/streamer_videos/` — 主播精选高光(体积大,手工放入后 OSS 同步) @@ -219,7 +218,8 @@ python pc/evaluate.py - 只改任务所需逻辑,避免无关重构与大范围格式化。 - 代码标识符、日志、错误信息、注释用**英文**;用户可见摘要可用中文。 -- 改匹配阈值或裁切时,用 `pc/evaluate.py` / 标注帧验证,并更新 `CHANGELOG.md` 与必要时的 `DESIGN.md`。 +- 改匹配阈值或裁切时,用 `pc/evaluate.py` / 标注帧验证,并更新 `CHANGELOG.md` 与必要时的 `ARCHITECTURE.md`。 +- 改上分帝 Web 视觉(色板、字号、间距、圆角、组件态)时先对齐 `DESIGN.md` 令牌,再改 `web/frontend/style.css`;勿引入未入规范的硬编码尺度。 - 改 GSI cfg 时同步核对 `pc/gsi_setup.py` 与 Dota `gamestate_integration` 目录。 - 改 Web 路由形态(URL 段 / query 参数 / 默认值)时同步 `web/frontend/router.js`(`parseHash` / `serializeHash`)与 `app.js` 的 `applyPatch` 校验;新增可路由状态维度时在两处都加,并在 `syncStateToUrl` 调用点(含搜索 debounce)接好。Hash 路由不命中后端,`web/serve_relations.py` 无需改;`web/export_relations_site.py` 的导出文件列表须含 `router.js`。 - 改 `shared/data/heroes.json` 结构时同步 `shared/grid.py`(依赖 `attr` / `name_loc`);`roles` 供 `pc/overlay.py` 使用;`aliases` 为中文口语/俗称(勿与 `name_loc` 重复),重跑 `pc/fetch_cdn_templates.py` 会按 `key` 合并保留;`tags` 为中文定位(核心/辅助/…/幻象,由 `roles`+幻想系推导),上分帝 Web 筛选 + 局内 `draft_archetypes` 缺口/推进画像共用;基础属性/血蓝等数值字段供上分帝 Web 详情条,勿塞机制文案。 @@ -231,7 +231,8 @@ python pc/evaluate.py | 文件 | 内容 | |------|------| | `README.md` | 安装与操作手册 | -| `DESIGN.md` | 背景、选型、关键实测与决策记录 | +| `ARCHITECTURE.md` | 背景、选型、关键实测与决策记录 | +| `DESIGN.md` | 上分帝 Web 视觉设计规范(令牌 + 使用规则;[design.md](https://github.com/google-labs-code/design.md) 格式) | | `AGENTS.md` | 本文件:给协作者 / AI 的约束 | | `CHANGELOG.md` | 面向用户的版本变更 | diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..d17530e --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,269 @@ +# 上分帝(Climperor)—— 方案与实施细节 + +`README.md` 是操作手册;本文记录背景、选型与关键决策。协作约束见 `AGENTS.md`。上分帝 Web 视觉设计规范见 `DESIGN.md`。 + +--- + +## 1. 背景与目标 + +### 要解决的问题 + +在 Dota 2 选将阶段(以及进入游戏后)**几秒内自动获取双方 10 个英雄**,服务天梯选将辅助或赛后分析。 + +### 为什么 GSI 做不到 + +| 场景 | GSI 可获得的阵容数据 | +|------|---------------------| +| 排位 / 普通 All Pick | 仅自己的 `hero.id`;`draft` 通常为空 | +| Captains Mode | 历史上有部分 pick/ban,不稳定 | +| 观战 / 裁判视角 | 阵容字段较全 | +| 赛后 | 需依赖 OpenDota 等外部 API | + +Valve 已明确关闭普通玩家视角的实时 draft +([#9562](https://github.com/ValveSoftware/Dota2-Gameplay/issues/9562)、 +[#7193](https://github.com/ValveSoftware/Dota2-Gameplay/issues/7193),not planned)。 + +### 候选方案 + +| 方案 | 结论 | +|------|------| +| 官方 GSI | 拿不到双方 pick | +| Overwolf GEP | 备选,偏重 | +| 读游戏内存 | **排除**(合规) | +| 截屏 + 模板匹配 | **选定** | +| 整图问多模态大模型 | 排除为主路径(实测整图几乎全错;裁顶栏后仍不如白名单匹配) | + +社区同类工具共同点:**裁固定 ROI + 模板匹配 / 小模型**,输出约束在英雄白名单内。 + +--- + +## 2. 技术方案 + +### 处理流程 + +``` +截图 → 相对坐标裁 10 格 →(天梯则遮罩段位条)→ CDN NCC 匹配 + → score + margin 双门控 → hero_key | null +``` + +### 关键决策 + +**① 相对坐标** + +横向 `(cx - W/2) / H`,纵向与宽高 `/ H`。同宽高比下分辨率无关;21:9 等需锚点或重标定。 + +**② 单一 CDN 模板库** + +`templates/cdn/{hero}.png` 来自 Steam 官方头像,按顶栏实际窗口裁切后写入。 + +曾尝试「CDN 兜底 + real 实拍」双层库。实测修正 CDN 裁切后 **CDN-only 即可全对**, +继续攒 real 库性价比低,已放弃(见 §3 发现二)。皮肤靠会话策略,不堆变体模板。 + +**③ 宁可不认,不可乱认** + +- `score >= min_score`(默认 0.45) +- `margin = Top1 − Top2 >= min_margin`(默认 0.04) + +**④ 会话层处理皮肤与淡入** + +- 选人阶段:多帧确认、允许更高分改判(`revise_gain`) +- 决策阶段:只补空槽、禁止改判(避免皮肤顶栏覆盖默认脸结论) +- best 帧优先选人阶段默认脸(`recognized` 相同时) +- 本人英雄以 GSI `hero` 覆盖视觉结果(只覆盖自己的槽) + +**⑤ 截图按对局归档** + +GSI 会话写入 `samples/raw/{matchid}/`;手动 `capture.py` 仍写 `samples/raw/` 根目录。 +结果 JSON 仍为 `results/draft_<时间戳>.json`(内含 `match_id`)。 + +**⑥ GSI 全量落盘** + +`gsi.dump_payloads`(默认开)把每包 POST body 追加到 `samples/raw/{matchid}/gsi.jsonl`, +行格式 `{"t": , "payload": <原文>}`。内容上限仍是 cfg 订阅字段 + 普通玩家视角; +不能靠落盘补出双方 pick。CLI:`--dump-gsi` / `--no-dump-gsi`。 + +--- + +## 3. 关键实测(2026-07-25) + +环境:2560×1440 无边框;人机 + 天梯。 + +### 发现一:CDN 裁切窗口决定上限 + +初版按正方形取中心,比例与顶栏 111×88 不符,均分约 0.65。 +网格搜索得到正确窗口 **x0=38, w=182, 全高**(源 256×144),均分约 0.94。 +写入 `fetch_cdn_templates.py` 的 `CROP_X0/CROP_X1` 后,**CDN-only 30/30 / 40/40**。 + +> 兜底层差时先查素材处理,再考虑堆数据。 + +### 发现二:不必维护 real 库 + +CDN-only 开箱即用后,「打几十局攒默认脸」不再是主路径。项目改为只维护 CDN。 + +后续审计(迁出前原型库)进一步确认: + +- 标注集上大量 `score=1.000` 往往是「从同帧裁进 real 再评同帧」的自证,夸大了 real 贡献。 +- 剩余 real 与 CDN(天梯遮罩)多数 ≥0.85,近乎 CDN 副本;明显偏离的极少(如军团指挥官一类边缘格)。 +- **近期问题局**(宙斯缺槽、斯拉达 CDN 分低、敌法末位、皮肤帧风行者 0.415)主要靠 + **多帧会话 + 决策禁改判 + GSI 补自己** 凑齐,不是靠 real。 +- 皮肤变体极多,入库皮肤模板不合理;段位条/徽章每人不同,匹配时已用遮罩忽略, + 也不该为不同段位重复存图。 + +### 发现三:自动标定靠玩家颜色条 + +10 条固定玩家色定位槽位;中心用中位数槽距拟合(剔除被身后头像污染的宽条); +头像下沿用「格内列 vs 格间空隙」亮度差,勿用逐行差分(易误判到名字行)。 + +### 发现四:天梯段位条用遮罩 + +GSI 不含可靠 lobby 类型时,用右下金色勋章检测(`has_ranked_overlay`), +匹配时屏蔽底部 32% + 右侧 22%。人机无勋章走全图匹配。 + +### 发现五:禁用名单不靠模板 + +网格按主属性分块 + 客户端本地化名行优先排布;禁用/已选卡片对比度塌陷 +(std 约 8–21 vs 正常 ≥33)。`data/heroes.json` 需含 `attr` / `name_loc`; +另含手工维护的中文口语 `aliases`(重拉 CDN 表时合并保留,暂未接入推荐展示)。 +格数对不上时 `ok=False`,不返回残缺名单。 + +### 发现六:位置与「我」 + +定位局位置字用二值 IoU(非 OCR)。「我」优先 GSI `team_slot`; +否则姓名亮度相对差(最亮比次亮 ≥ 25)兜底。 + +### 发现七:客户端顶栏时机(会话策略前提) + +这些是实战纠正后的事实,改 `DraftSession` 时不要违背: + +| 现象 | 含义 | +|------|------| +| 顶栏默认脸 → 皮肤 | **全员选完后**才换皮肤立绘,不是一进决策就换 | +| 本机已进 `STRATEGY_TIME` | 别人可能还在选;**不能**一进决策就永久停视觉 | +| 空槽红旗倒计时 | 只出现在**未选**槽;揭晓后消失,不是挡脸主因 | +| GSI | **没有**「皮肤已加载」字段;无法精确卡「人选完、皮肤前」单帧 | + +因此正确节奏是:选人 + 决策前期持续读,直到确认 10/10 或 `strategy_tail_polls` 用尽; +凑齐后停视觉,再短等 GSI 补本人英雄。单帧皮肤弱识别(如决策 3D 模型挡顶栏)属预期, +靠会话回填,不靠降阈值或堆皮肤模板。 + +### 发现八:揭晓淡入需要可改判 + +首帧揭晓常半透明 + 段位条,易误认(例:剑圣揭晓瞬间被认成幽鬼)。 +`confirm_polls` 后若仍禁止改判会锁死错误。选人阶段允许 +`score ≥ was_score + revise_gain` 的稳定新结果覆盖;决策阶段则关闭改判。 + +--- + +## 4. 代码结构 + +``` +climperor/ +├── pc/ # 局内选将识别(GSI + 截屏 + OpenCV) +│ ├── common.py / recognize.py / draft_session.py / gsi_watch.py +│ ├── recommend.py / draft_archetypes.py / roles.py / modes.py / overlay.py +│ ├── config.json / templates/ / assets/role_icons/ / samples/ +│ └── requirements.txt +├── web/ # 上分帝 Web(前端、数据流水线、部署) +│ ├── frontend/ # 原 web/relations/ +│ ├── data/ / assets/ / dist/ +│ ├── fetch_*.py / item_fears.py / mechanic_tags.py / loc_format.py +│ ├── serve_relations.py / export_relations_site.py / deploy_relations.py +│ ├── refresh_web.py / notify_site_traffic.py / _cf_* / _oss_* / _gitea_* +│ └── requirements.txt +├── shared/ # 只被 pc/web 依赖,绝不反向依赖 +│ ├── paths.py / grid.py / relations.py / hero_tags.py / http_utils.py +│ ├── import_relations_xlsx.py / audit_relations.py / fetch_stratz.py +│ └── data/heroes.json / data/relations.json +├── .gitea/workflows/ # site-traffic-notify + web-daily/weekly/patch +└── README.md / AGENTS.md / ARCHITECTURE.md / DESIGN.md / CHANGELOG.md +``` + +运行时目录 `pc/preview/`、`pc/results/`、`pc/failures/`、`pc/samples/raw/`(含按对局子目录)不入库。上分帝 Web 易变数据(STRATZ meta、hero_stats 等)可由 Gitea Actions 定时拉取后直部 Pages,不必入库。 + +### 主要配置 + +| 字段 | 含义 | 默认 | +|------|------|------| +| `match.min_score` / `min_margin` | 识别门控 | 0.45 / 0.04 | +| `match.ranked_mask` | 天梯遮罩比例 | bottom 0.32, right 0.22 | +| `gsi.port` | 监听端口 | 3223 | +| `gsi.confirm_polls` | 连续同结果帧数 | 2 | +| `gsi.revise_gain` | 选人阶段改判所需分数增益 | 0.15 | +| `gsi.strategy_tail_polls` | 决策阶段继续视觉轮询 | 8 | +| `gsi.strategy_gsi_wait` | 视觉结束后等待本人 GSI 英雄 | 3.0 | +| `gsi.require_foreground` | 仅当前台为 `dota2.exe` 时截屏识别 | true | +| `gsi.dump_payloads` | 全量 GSI JSONL 落盘 | true | +| `recommend.enabled` | 选将全网格克/搭/补推荐 | true | +| `recommend.top_n` | 标记数量上限;`≤0` 不截断 | 0 | +| `recommend.min_enemies` | 开始推荐所需敌方锁人数 | 1 | +| `recommend.min_heroes_for_gaps` | 缺口/补位注入所需锁人数 | 2 | +| `recommend.archetypes` | 推进/全球流/缺口规则画像 | true | +| `recommend.relations_path` | 定性关系文件 | `shared/data/relations.json` | +| `recommend.role_tags` | 分路→角色标签过滤(1–5 号位;非定位局不过滤) | 见 config.json | +| `overlay.enabled` | 选将顶栏角色标签悬浮层 | true | +| `overlay.y_gap_rel` / `icon_h_rel` / `icon_gap_rel` | 标签相对屏幕高的间距与尺寸 | 0.008 / 0.016 / 0.002 | +| `overlay.mark_size_rel` / `mark_pad_rel` / `mark_gap_rel` | 网格克/搭/补方标尺寸、内边距、间距 | 0.018 / 0.004 / 0.002 | +| `overlay.counter_color` / `synergy_color` / `fill_color` / `mark_text_color` | 克 / 搭 / 补 / 文字色 | `#2ec4b6` / `#e9a825` / `#9b7ebd` / `#0b1220` | +| `overlay.analysis_*` | 阵容分析横条位置/高度/字号/底色字色 | 见 config.json | + +--- + +## 5. GSI 自动化 + +``` +Dota 2 (-gamestateintegration) + → POST → pc/gsi_watch.py :3223 + → pc/samples/raw/{matchid}/gsi.jsonl(可选) + → DraftSession 轮询 recognize + roles/grid/modes + → pc/results/draft_*.json +``` + +要点:按 `matchid` 去重;截图进 `pc/samples/raw/{matchid}/`;识别在工作线程; +未标定时降级为只截图。 + +天梯 AP 选人按 2/2/1 成批揭晓,本轮结束前互不可见——因此必须在 +**HERO_SELECTION** 跟踪,不能只在决策时间截一张终局图;决策前期仍读几帧 +以吃到最后一人揭晓(见发现七)。 + +--- + +## 6. 选将全网格「克 / 搭 / 补」推荐 + +- **触发**:敌方至少锁定 1 人(`min_enemies`)后开始;本人锁人后清空标记。 +- **分路**:定位匹配画面字(`roles.py`)+ GSI `team_slot` 定位本人;不做截屏认自己。有分路则按 `role_tags` 过滤候选;非定位局(读不到分路字)则全英雄表。 +- **关系模型**:机制克制/搭档(`shared/data/relations.json`),不用胜率/场次——版本会变,机制边相对稳。Web 英雄详情「走势」Tab 优先 STRATZ(`web/data/stratz_hero_meta.json`):勋章条切换 8 档 → **最近 1 周**三卡(胜率/上场率/场次)→ 1~5 号位分路胜率 → **近 8 周**逐周列表(日期、胜率、周环比、区间缩放横条、场次;新→旧);OpenDota(`web/data/hero_stats.json`,近约 7 天)作兜底。「对位」Tab 展示 STRATZ 数值 Top(`web/data/stratz_matchup_tops.json`),与网格定性「克/怕/搭」分开展示;均不进局内推荐。 +- **阵容画像(规则,不接 AI)**:[`pc/draft_archetypes.py`](pc/draft_archetypes.py) 识别敌方推进/全球流、敌我 tag 缺口(缺控制/爆发/输出/先手);输出一句 `analysis`(如 `敌:缺控制·偏推进 | 我:缺爆发`)与每英雄短 `reasons`。 +- **纳入**: + - **克**:关系克制边;推进/全球流应对表(如美杜莎对推进);惩戒敌方缺口 + - **搭**:与已锁己有搭档边 + - **补**:填我方缺口(`min_heroes_for_gaps` 默认 2 才注入缺口类标记) +- **打分**:关系边为主;阵容应对 / 缺口小幅加分;敌方 tags 软加分仅排序。 +- **展示**:网格左上角全量相关格标「克」(青)/「搭」(琥珀)/「补」(紫灰);叠加层横条显示阵容分析;原因进日志与 `results` JSON,不写在每个格子旁。 +- **上分帝 Web**:`web/serve_relations.py` 本地开发服务;仿选将网格查看克制/被克制/搭档;数据手改 `shared/data/relations.json` 或从 xlsx 导入。 + 另有顶级 **排行**、**走势**(`#/trends[/bracket]`,近 8 周高胜率/上场率榜)、**物品**、**版本** 页;目录见 `web/data/item_shop.json` / `web/data/patches.json` / `web/data/leaderboards.json`。 + 英雄详情子标签含 **走势**(见上;`#/heroes//trends`)、**对位**(克制/被克/搭档三列 STRATZ Top;`#/heroes//matchups`)、**近期比赛**(`#/heroes//matches`)。排行页为 Immortal 四区选手榜。 +- **Web Hash 路由**:状态(顶层标签 / 选中英雄 + 详情子标签 / 选中物品 / 选中版本 / + 标签筛选 / 搜索)双向同步进 URL(`#/heroes/axe/core?tags=核心&q=axe`、`#/heroes/axe/trends` 等)。选 Hash + 而非 Path 路由:静态导出(GitHub Pages)无需 rewrite、`web/serve_relations.py` 零改动、 + 相对路径子站亦可用。写 URL 用 `history.pushState`/`replaceState`(静默,不触发 + `hashchange`,无回环);前进/后退靠 `hashchange` 监听重 parse + 校验 + render。 + 搜索 debounce 300ms + `replaceState` 防刷历史栈;坏链接丢弃该项不崩。路由逻辑集中在 + `web/frontend/router.js`,`app.js` 只在 `main()` 装 `installRouter` + 各 state 变更点 + 调 `syncStateToUrl`。 +- **不做**:避用标、Steam 登录页、爬 Dotabuff;关系**不**写入 `shared/data/heroes.json`;不接实时 AI。 + +--- + +## 7. 与 dota2-hex + +独立验证项目。可选:Rust 重写并入,或本地旁路 HTTP 回传。 +合规同 `dota2-hex`:**屏幕可见信息 only**。 + +--- + +## 8. 参考 + +- [Valve #9562](https://github.com/ValveSoftware/Dota2-Gameplay/issues/9562) +- [Valve #14915](https://github.com/ValveSoftware/Dota2-Gameplay/issues/14915)(开发者 OCR ≈85%) +- [Overwolf Dota 2 GEP](https://dev.overwolf.com/ow-native/live-game-data-gep/supported-games/dota-2/) +- Steam CDN:`https://cdn.cloudflare.steamstatic.com/apps/dota2/images/dota_react/heroes/{key}.png` diff --git a/CHANGELOG.md b/CHANGELOG.md index 8080ba1..31e3647 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,151 @@ ## [Unreleased] +## [0.5.109] - 2026-07-30 + +### Changed + +- 英雄页:定位筛选迁至网格与详情之间(Valve 图标斜角按钮);去掉详情 Chip;未选英雄时隐藏详情占位。 +- 提高英雄详情固定高度(约至 640px 上限),压缩网格与详情之间的多余空白;Tab 切换高度不跳动。 +- 技能 Tab:演示视频与文案强制左右排布;去掉图标与详情之间的中间分割线。 +- 核心装备 / 被克装备:详情区上下分割线统一为全宽。 + +## [0.5.108] - 2026-07-30 + +### Fixed + +- 技能 Tab:演示视频与文案强制左右排布(不再因中间栏偏窄折行上下叠);详情固定高度提至约 560px,常规技能默认可视区内完整展示。 + +## [0.5.107] - 2026-07-30 + +### Changed + +- 技能 Tab:去掉技能图标与详情之间的中间分割线(装备 Tab 保留)。 + +## [0.5.106] - 2026-07-30 + +### Fixed + +- 核心装备 / 被克装备:详情区上下分割线统一为全宽(合成线不再缩在 720px 文案栏内)。 + +## [0.5.105] - 2026-07-30 + +### Fixed + +- 技能 / 核心装备 / 被克装备等 Tab 切换时详情区高度不再跳动(恢复固定高 + 禁止压缩)。 + +## [0.5.104] - 2026-07-30 + +### Changed + +- 缩小英雄详情固定高度,给网格与定位栏留更多空间;走势 / 对位等长内容仍在详情区内滚动。 + +## [0.5.103] - 2026-07-30 + +### Changed + +- 未选中英雄时隐藏详情占位条,不再显示提示文案。 + +## [0.5.102] - 2026-07-30 + +### Changed + +- 战斗属性补全转身速率:OpenDota 缺省时按引擎默认 `0.6` 显示(与官网一致); + `heroes.json` 回填 82 名英雄。 + +## [0.5.101] - 2026-07-30 + +### Changed + +- 选中英雄时定位栏不再点亮「全部」,只高亮该英雄定位。 + +## [0.5.100] - 2026-07-30 + +### Changed + +- 去掉英雄定位栏上下分割线。 + +## [0.5.99] - 2026-07-30 + +### Changed + +- 英雄详情技能区去掉定位 Chip;选中英雄时在上方定位栏高亮对应选项(不影响网格筛选)。 + +## [0.5.98] - 2026-07-29 + +### Changed + +- 英雄详情战斗属性(攻击 / 防御 / 机动性)改为官网同款图标 + 数值横排;图标取自 + dota2.com.cn `herostatic/stats`,视野显示为「白天 / 夜晚」。 + +## [0.5.97] - 2026-07-29 + +### Changed + +- 英雄定位筛选移至英雄网格与详情面板之间,改用 Valve 风格图标与斜角按钮;保留多选, + 暂不展示「幻象」筛选。 + +## [0.5.96] - 2026-07-29 + +### Changed + +- 加长主导航选中下划线至 44px;hover 改为仅文字提亮,移除与斜切分隔冲突的矩形冷光底。 + +## [0.5.95] - 2026-07-29 + +### Changed + +- 移除顶栏品牌 Logo 右侧斜线,仅保留暗红内光,减少品牌区与导航之间的装饰噪声。 + +## [0.5.94] - 2026-07-29 + +### Changed + +- 放大宽屏顶栏比例:主导航改为 16px / 76px 最小宽 / 44px 点击高,品牌 Logo 提升至 + 32px;≤1200px 自动恢复 15px / 40px 紧凑规格,筛选 Chip 保持次级尺寸。 + +## [0.5.93] - 2026-07-29 + +### Changed + +- 顶栏轻度借鉴 Dota 2 国服官网:加入近黑金属横梁、暗红品牌内光与斜切细分隔; + 保留青色短下划线和定位 Chip,不复刻门户式梯形按钮、厚重贴图与高顶栏。 + +## [0.5.92] - 2026-07-29 + +### Changed + +- 主导航放弃连续按钮外框,改为轻量文字 Tab:透明背景、低噪 hover 与青色短下划线选中态;定位筛选继续使用独立 Chip,强化两级导航层次。 + +## [0.5.91] - 2026-07-29 + +### Changed + +- 顶部导航改为紧凑双层:首行品牌 | 居中主导航 | 搜索+邮件;次行仅英雄定位筛选。 + 去掉品牌/邮件绝对悬浮与搜索贴边,减少横向空洞;窄桌面收紧 Tab/搜索并隐藏品牌标题。 + +## [0.5.90] - 2026-07-29 + +### Changed + +- 对位页底部文案简化为「数据来自 STRATZ(相对优势,全局聚合)。」 + +## [0.5.89] - 2026-07-29 + +### Changed + +- 英雄「对位」统一只使用 STRATZ(`stratz_matchup_tops.json`);移除 OpenDota + 对位交叉与页面「来源一致 / 分歧」提示;`audit_relations` 改为 STRATZ 单源审计。 + +## [0.5.88] - 2026-07-29 + +### Changed + +- 根目录 `DESIGN.md` 改为上分帝 Web 视觉设计规范([design.md](https://github.com/google-labs-code/design.md) 格式); + 原技术方案迁至 `ARCHITECTURE.md`。 +- Web 统一设计令牌:色板、字号层级、4/8 间距、圆角、内容宽度档位与焦点态; + 排行 / 比赛 / 主播 / 物品补齐页标题;版本号展示降至 32px;断点收敛为 640 / 900 / 1200。 + ## [0.5.87] - 2026-07-29 ### Fixed diff --git a/DESIGN.md b/DESIGN.md index dd99503..ab40f38 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,269 +1,341 @@ -# 上分帝(Climperor)—— 方案与实施细节 - -`README.md` 是操作手册;本文记录背景、选型与关键决策。协作约束见 `AGENTS.md`。 - +--- +version: alpha +name: Climperor Web +description: Dark Dota-inspired information panels for the Climperor Web desktop site. +colors: + primary: "#5EC8FF" + secondary: "#8FA3BC" + tertiary: "#3DCE7A" + neutral: "#0B1018" + surface: "#0E1622" + surface-raised: "#101824" + on-surface: "#E8EEF7" + on-surface-muted: "#8FA3BC" + border: "rgba(140, 170, 210, 0.18)" + accent: "#5EC8FF" + good: "#3DCE7A" + bad: "#E07A2F" + syn: "#7EC8FF" + danger: "#E86A6A" + gold: "#E8C878" + live: "#FE2C55" + panel: "rgba(14, 22, 34, 0.96)" + panel-soft: "rgba(8, 12, 20, 0.55)" + on-accent: "#0B0D10" +typography: + display: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 32px + fontWeight: 800 + lineHeight: 1.1 + letterSpacing: 0.02em + page-title: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 22px + fontWeight: 800 + lineHeight: 1.2 + letterSpacing: 0.02em + title-lg: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 18px + fontWeight: 700 + lineHeight: 1.3 + letterSpacing: 0.04em + title-md: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 16px + fontWeight: 700 + lineHeight: 1.35 + letterSpacing: 0.02em + body-lg: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 15px + fontWeight: 400 + lineHeight: 1.5 + body-md: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 14px + fontWeight: 400 + lineHeight: 1.5 + body-sm: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 13px + fontWeight: 500 + lineHeight: 1.45 + label-md: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 13px + fontWeight: 600 + lineHeight: 1.3 + letterSpacing: 0.06em + label-sm: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 12px + fontWeight: 600 + lineHeight: 1.3 + letterSpacing: 0.04em + numeric-lg: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 28px + fontWeight: 700 + lineHeight: 1.1 + letterSpacing: 0.02em + numeric-md: + fontFamily: "Segoe UI, Microsoft YaHei UI, PingFang SC, sans-serif" + fontSize: 14px + fontWeight: 700 + lineHeight: 1.2 +rounded: + sm: 4px + md: 8px + lg: 12px + xl: 16px + full: 9999px +spacing: + xs: 4px + sm: 8px + md: 12px + lg: 16px + xl: 24px + 2xl: 32px + 3xl: 48px + gutter: 16px + page-y: 28px + content-read: 820px + content-standard: 880px + content-wide: 1080px + content-data: 1680px +components: + button-primary: + backgroundColor: "{colors.primary}" + textColor: "{colors.on-accent}" + rounded: "{rounded.md}" + padding: 12px + height: 40px + button-primary-hover: + backgroundColor: "#7AD4FF" + button-secondary: + backgroundColor: "{colors.surface}" + textColor: "{colors.on-surface}" + rounded: "{rounded.md}" + padding: 12px + height: 40px + button-secondary-active: + backgroundColor: "{colors.primary}" + textColor: "{colors.on-accent}" + tab-main: + backgroundColor: "{colors.surface}" + textColor: "{colors.secondary}" + typography: "{typography.body-lg}" + height: 42px + padding: 10px + tab-main-active: + backgroundColor: "{colors.surface-raised}" + textColor: "{colors.on-surface}" + input-search: + backgroundColor: "{colors.neutral}" + textColor: "{colors.on-surface}" + rounded: "{rounded.md}" + height: 40px + padding: 12px + panel-card: + backgroundColor: "{colors.surface}" + textColor: "{colors.on-surface}" + rounded: "{rounded.lg}" + padding: 16px + chip-filter: + backgroundColor: "{colors.surface}" + textColor: "{colors.secondary}" + rounded: "{rounded.full}" + padding: 8px + chip-filter-active: + backgroundColor: "{colors.surface-raised}" + textColor: "{colors.on-surface}" + mark-good: + backgroundColor: "{colors.neutral}" + textColor: "{colors.good}" + mark-bad: + backgroundColor: "{colors.neutral}" + textColor: "{colors.bad}" + mark-syn: + backgroundColor: "{colors.neutral}" + textColor: "{colors.syn}" + badge-gold: + backgroundColor: "{colors.neutral}" + textColor: "{colors.gold}" + badge-live: + backgroundColor: "{colors.live}" + textColor: "#FFFFFF" + badge-danger: + backgroundColor: "{colors.neutral}" + textColor: "{colors.danger}" + panel-raised: + backgroundColor: "{colors.panel}" + textColor: "{colors.on-surface}" + panel-soft: + backgroundColor: "{colors.panel-soft}" + textColor: "{colors.on-surface-muted}" + divider: + backgroundColor: "{colors.surface}" + textColor: "{colors.border}" --- -## 1. 背景与目标 +# Climperor Web Design System -### 要解决的问题 +## Overview -在 Dota 2 选将阶段(以及进入游戏后)**几秒内自动获取双方 10 个英雄**,服务天梯选将辅助或赛后分析。 +上分帝 Web 是面向桌面浏览器的 Dota 2 信息站:深色蓝黑底、高信息密度、冷静可读。气质接近官方客户端与选人界面——偏工具面板,不走社交 feed 或明亮营销站。 -### 为什么 GSI 做不到 +目标感受: -| 场景 | GSI 可获得的阵容数据 | -|------|---------------------| -| 排位 / 普通 All Pick | 仅自己的 `hero.id`;`draft` 通常为空 | -| Captains Mode | 历史上有部分 pick/ban,不稳定 | -| 观战 / 裁判视角 | 阵容字段较全 | -| 赛后 | 需依赖 OpenDota 等外部 API | +- **Dense but calm**:列表、表格、网格可挤满视口,但字号、行高、间距仍保持清晰节奏。 +- **Semantic color first**:青色强调当前选择与导航;绿 / 橙 / 青蓝分别表示克制 / 被克 / 搭档;粉红仅用于直播态。 +- **Desktop shell**:`100dvh` 外框 + 页内滚动;手机 / 平板由 `mobile-gate` 拦截,不为真移动端另做一套 IA。 -Valve 已明确关闭普通玩家视角的实时 draft -([#9562](https://github.com/ValveSoftware/Dota2-Gameplay/issues/9562)、 -[#7193](https://github.com/ValveSoftware/Dota2-Gameplay/issues/7193),not planned)。 +实现源:`web/frontend/style.css` 的 CSS 变量必须与本文件令牌一一对应。工程决策见 `ARCHITECTURE.md`。 -### 候选方案 +## Colors -| 方案 | 结论 | -|------|------| -| 官方 GSI | 拿不到双方 pick | -| Overwolf GEP | 备选,偏重 | -| 读游戏内存 | **排除**(合规) | -| 截屏 + 模板匹配 | **选定** | -| 整图问多模态大模型 | 排除为主路径(实测整图几乎全错;裁顶栏后仍不如白名单匹配) | +调色盘以近黑海军为底,浅冷灰为正文,单一青色作交互主色。 -社区同类工具共同点:**裁固定 ROI + 模板匹配 / 小模型**,输出约束在英雄白名单内。 +- **Primary (`#5EC8FF`)**:主交互与选中态(主 Tab 底线、侧栏选中填充、焦点环、选中英雄描边)。 +- **Secondary (`#8FA3BC`)**:次要文案、标签、时间戳、未选中控件。 +- **Tertiary (`#3DCE7A`)**:正向语义(克制、胜率上升、胜场)。 +- **Neutral (`#0B1018`)**:页面底色;表面层用更深的半透明面板叠在渐变背景上。 +- **Bad / Danger / Gold / Live / Syn**:业务辨识色,不得拿来当通用强调色。 ---- +对比度要求:正文与底至少 WCAG AA;muted 文案仅用于辅助信息,不用在关键操作标签上。 -## 2. 技术方案 +## Typography -### 处理流程 +系统栈:`Segoe UI` + `Microsoft YaHei UI` + `PingFang SC`。中文优先清晰,不引入展示性装饰字体。 -``` -截图 → 相对坐标裁 10 格 →(天梯则遮罩段位条)→ CDN NCC 匹配 - → score + margin 双门控 → hero_key | null -``` +| Token | 用途 | +|-------|------| +| `display` | 版本号等少数展示数字(约 32px,勿再放大到 40px) | +| `page-title` | 各顶层页标题(排行 / 走势 / 机制 / 主播 / 物品等) | +| `title-lg` / `title-md` | 区段标题、英雄名、物品名 | +| `body-lg` / `body-md` / `body-sm` | 导航、正文、说明 | +| `label-md` / `label-sm` | 表头、分区标签、徽章 | +| `numeric-*` | 表格数字与指标卡,配合 `tabular-nums` | -### 关键决策 +字重只用 400 / 500 / 600 / 700 / 800。禁止 `650`、`13.5px` 等孤立值。默认行高正文 1.5,标题 1.1–1.3;勿留 `line-height: normal` 在可读正文上。 -**① 相对坐标** +## Layout -横向 `(cx - W/2) / H`,纵向与宽高 `/ H`。同宽高比下分辨率无关;21:9 等需锚点或重标定。 +桌面壳:顶栏固定高度区 + 可滚动 `main.board`;英雄页的英雄网格与固定高 `#detail` +之间放置一行定位筛选。选中英雄后 `#detail` 使用共享固定高度(约 `min(640px, …)`), +Tab 切换不跳动;技能演示与文案左右排布,长 Tab(走势/对位等)在详情区内滚动。 -**② 单一 CDN 模板库** +### 顶部壳(紧凑单层) -`templates/cdn/{hero}.png` 来自 Steam 官方头像,按顶栏实际窗口裁切后写入。 +顶栏只保留主行,禁止品牌 / 主导航 / 搜索三者各自「漂浮居中」造成大块空洞: -曾尝试「CDN 兜底 + real 实拍」双层库。实测修正 CDN 裁切后 **CDN-only 即可全对**, -继续攒 real 库性价比低,已放弃(见 §3 发现二)。皮肤靠会话策略,不堆变体模板。 +1. **首行 `PrimaryRow`**:`display: grid; grid-template-columns: 1fr auto 1fr`(或等价 flex)。 + - 左:品牌(logo + 标题) + - 中:主导航分段控件(真正几何居中) + - 右:工具区(英雄/物品搜索 + 邮件入口),`justify-content: end` +视觉语言轻度借鉴 Dota 2 国服官网导航,但不复刻门户式大顶栏: -**③ 宁可不认,不可乱认** +- 顶栏使用近黑金属渐变与上下 1px 冷色高光,形成连续横梁;总高控制在约 60px。 +- 品牌区仅用克制的暗红内光形成视觉锚点,不在 Logo 右侧增加分隔线;不使用视频 Logo、金色奖项条或厚重贴图。 +- 主导航保持现代无边框文字 Tab;相邻 Tab 仅用低对比斜切细线分隔,不做梯形按钮。 +- 主交互色仍为 `primary` 青色;Dota 红只用于品牌区,不替代全站选中态。 +- 宽屏主行采用 44px 控件高:主导航 16px / 最小 76px 宽,品牌 Logo 32px。`lg` 断点恢复 40px、15px 的紧凑规格。 -- `score >= min_score`(默认 0.45) -- `margin = Top1 − Top2 >= min_margin`(默认 0.04) +断点降级: -**④ 会话层处理皮肤与淡入** +- `lg` ≤1200px:Tab 水平内边距收紧;搜索宽约 160–180px +- `md` ≤900px:隐藏品牌标题,仅留 logo;搜索再缩短 +- `sm` ≤640px:极窄桌面可隐藏整块品牌 -- 选人阶段:多帧确认、允许更高分改判(`revise_gain`) -- 决策阶段:只补空槽、禁止改判(避免皮肤顶栏覆盖默认脸结论) -- best 帧优先选人阶段默认脸(`recognized` 相同时) -- 本人英雄以 GSI `hero` 覆盖视觉结果(只覆盖自己的槽) +内容宽度档位(命名,勿再散落魔法数): -**⑤ 截图按对局归档** - -GSI 会话写入 `samples/raw/{matchid}/`;手动 `capture.py` 仍写 `samples/raw/` 根目录。 -结果 JSON 仍为 `results/draft_<时间戳>.json`(内含 `match_id`)。 - -**⑥ GSI 全量落盘** - -`gsi.dump_payloads`(默认开)把每包 POST body 追加到 `samples/raw/{matchid}/gsi.jsonl`, -行格式 `{"t": , "payload": <原文>}`。内容上限仍是 cfg 订阅字段 + 普通玩家视角; -不能靠落盘补出双方 pick。CLI:`--dump-gsi` / `--no-dump-gsi`。 - ---- - -## 3. 关键实测(2026-07-25) - -环境:2560×1440 无边框;人机 + 天梯。 - -### 发现一:CDN 裁切窗口决定上限 - -初版按正方形取中心,比例与顶栏 111×88 不符,均分约 0.65。 -网格搜索得到正确窗口 **x0=38, w=182, 全高**(源 256×144),均分约 0.94。 -写入 `fetch_cdn_templates.py` 的 `CROP_X0/CROP_X1` 后,**CDN-only 30/30 / 40/40**。 - -> 兜底层差时先查素材处理,再考虑堆数据。 - -### 发现二:不必维护 real 库 - -CDN-only 开箱即用后,「打几十局攒默认脸」不再是主路径。项目改为只维护 CDN。 - -后续审计(迁出前原型库)进一步确认: - -- 标注集上大量 `score=1.000` 往往是「从同帧裁进 real 再评同帧」的自证,夸大了 real 贡献。 -- 剩余 real 与 CDN(天梯遮罩)多数 ≥0.85,近乎 CDN 副本;明显偏离的极少(如军团指挥官一类边缘格)。 -- **近期问题局**(宙斯缺槽、斯拉达 CDN 分低、敌法末位、皮肤帧风行者 0.415)主要靠 - **多帧会话 + 决策禁改判 + GSI 补自己** 凑齐,不是靠 real。 -- 皮肤变体极多,入库皮肤模板不合理;段位条/徽章每人不同,匹配时已用遮罩忽略, - 也不该为不同段位重复存图。 - -### 发现三:自动标定靠玩家颜色条 - -10 条固定玩家色定位槽位;中心用中位数槽距拟合(剔除被身后头像污染的宽条); -头像下沿用「格内列 vs 格间空隙」亮度差,勿用逐行差分(易误判到名字行)。 - -### 发现四:天梯段位条用遮罩 - -GSI 不含可靠 lobby 类型时,用右下金色勋章检测(`has_ranked_overlay`), -匹配时屏蔽底部 32% + 右侧 22%。人机无勋章走全图匹配。 - -### 发现五:禁用名单不靠模板 - -网格按主属性分块 + 客户端本地化名行优先排布;禁用/已选卡片对比度塌陷 -(std 约 8–21 vs 正常 ≥33)。`data/heroes.json` 需含 `attr` / `name_loc`; -另含手工维护的中文口语 `aliases`(重拉 CDN 表时合并保留,暂未接入推荐展示)。 -格数对不上时 `ok=False`,不返回残缺名单。 - -### 发现六:位置与「我」 - -定位局位置字用二值 IoU(非 OCR)。「我」优先 GSI `team_slot`; -否则姓名亮度相对差(最亮比次亮 ≥ 25)兜底。 - -### 发现七:客户端顶栏时机(会话策略前提) - -这些是实战纠正后的事实,改 `DraftSession` 时不要违背: - -| 现象 | 含义 | -|------|------| -| 顶栏默认脸 → 皮肤 | **全员选完后**才换皮肤立绘,不是一进决策就换 | -| 本机已进 `STRATEGY_TIME` | 别人可能还在选;**不能**一进决策就永久停视觉 | -| 空槽红旗倒计时 | 只出现在**未选**槽;揭晓后消失,不是挡脸主因 | -| GSI | **没有**「皮肤已加载」字段;无法精确卡「人选完、皮肤前」单帧 | - -因此正确节奏是:选人 + 决策前期持续读,直到确认 10/10 或 `strategy_tail_polls` 用尽; -凑齐后停视觉,再短等 GSI 补本人英雄。单帧皮肤弱识别(如决策 3D 模型挡顶栏)属预期, -靠会话回填,不靠降阈值或堆皮肤模板。 - -### 发现八:揭晓淡入需要可改判 - -首帧揭晓常半透明 + 段位条,易误认(例:剑圣揭晓瞬间被认成幽鬼)。 -`confirm_polls` 后若仍禁止改判会锁死错误。选人阶段允许 -`score ≥ was_score + revise_gain` 的稳定新结果覆盖;决策阶段则关闭改判。 - ---- - -## 4. 代码结构 - -``` -climperor/ -├── pc/ # 局内选将识别(GSI + 截屏 + OpenCV) -│ ├── common.py / recognize.py / draft_session.py / gsi_watch.py -│ ├── recommend.py / draft_archetypes.py / roles.py / modes.py / overlay.py -│ ├── config.json / templates/ / assets/role_icons/ / samples/ -│ └── requirements.txt -├── web/ # 上分帝 Web(前端、数据流水线、部署) -│ ├── frontend/ # 原 web/relations/ -│ ├── data/ / assets/ / dist/ -│ ├── fetch_*.py / item_fears.py / mechanic_tags.py / loc_format.py -│ ├── serve_relations.py / export_relations_site.py / deploy_relations.py -│ ├── refresh_web.py / notify_site_traffic.py / _cf_* / _oss_* / _gitea_* -│ └── requirements.txt -├── shared/ # 只被 pc/web 依赖,绝不反向依赖 -│ ├── paths.py / grid.py / relations.py / hero_tags.py / http_utils.py -│ ├── import_relations_xlsx.py / audit_relations.py / fetch_matchups.py / fetch_stratz.py -│ └── data/heroes.json / data/relations.json -├── .gitea/workflows/ # site-traffic-notify + web-daily/weekly/patch -└── README.md / AGENTS.md / DESIGN.md / CHANGELOG.md -``` - -运行时目录 `pc/preview/`、`pc/results/`、`pc/failures/`、`pc/samples/raw/`(含按对局子目录)不入库。上分帝 Web 易变数据(STRATZ meta、hero_stats 等)可由 Gitea Actions 定时拉取后直部 Pages,不必入库。 - -### 主要配置 - -| 字段 | 含义 | 默认 | +| 档位 | 宽度 | 页面 | |------|------|------| -| `match.min_score` / `min_margin` | 识别门控 | 0.45 / 0.04 | -| `match.ranked_mask` | 天梯遮罩比例 | bottom 0.32, right 0.22 | -| `gsi.port` | 监听端口 | 3223 | -| `gsi.confirm_polls` | 连续同结果帧数 | 2 | -| `gsi.revise_gain` | 选人阶段改判所需分数增益 | 0.15 | -| `gsi.strategy_tail_polls` | 决策阶段继续视觉轮询 | 8 | -| `gsi.strategy_gsi_wait` | 视觉结束后等待本人 GSI 英雄 | 3.0 | -| `gsi.require_foreground` | 仅当前台为 `dota2.exe` 时截屏识别 | true | -| `gsi.dump_payloads` | 全量 GSI JSONL 落盘 | true | -| `recommend.enabled` | 选将全网格克/搭/补推荐 | true | -| `recommend.top_n` | 标记数量上限;`≤0` 不截断 | 0 | -| `recommend.min_enemies` | 开始推荐所需敌方锁人数 | 1 | -| `recommend.min_heroes_for_gaps` | 缺口/补位注入所需锁人数 | 2 | -| `recommend.archetypes` | 推进/全球流/缺口规则画像 | true | -| `recommend.relations_path` | 定性关系文件 | `shared/data/relations.json` | -| `recommend.role_tags` | 分路→角色标签过滤(1–5 号位;非定位局不过滤) | 见 config.json | -| `overlay.enabled` | 选将顶栏角色标签悬浮层 | true | -| `overlay.y_gap_rel` / `icon_h_rel` / `icon_gap_rel` | 标签相对屏幕高的间距与尺寸 | 0.008 / 0.016 / 0.002 | -| `overlay.mark_size_rel` / `mark_pad_rel` / `mark_gap_rel` | 网格克/搭/补方标尺寸、内边距、间距 | 0.018 / 0.004 / 0.002 | -| `overlay.counter_color` / `synergy_color` / `fill_color` / `mark_text_color` | 克 / 搭 / 补 / 文字色 | `#2ec4b6` / `#e9a825` / `#9b7ebd` / `#0b1220` | -| `overlay.analysis_*` | 阵容分析横条位置/高度/字号/底色字色 | 见 config.json | +| full | 100% | 英雄网格 | +| read | `min(820px, 92%)` | 版本 | +| standard | `min(880px, calc(100% - 48px))` | 排行、主播、机制 | +| wide | `min(1080px, …)` | 走势 | +| data | `min(1680px, …)` | 比赛 | ---- +间距尺度:4 / 8 / 12 / 16 / 24 / 32 / 48px。页内 gutter 默认 16px;列表页上下 `page-y` 28px。侧栏与主列间距 20px。 -## 5. GSI 自动化 +断点收敛为三档: -``` -Dota 2 (-gamestateintegration) - → POST → pc/gsi_watch.py :3223 - → pc/samples/raw/{matchid}/gsi.jsonl(可选) - → DraftSession 轮询 recognize + roles/grid/modes - → pc/results/draft_*.json -``` +- `sm` ≤640px:极窄桌面(列堆叠、双列网格) +- `md` ≤900px:aside 改上下堆叠、英雄两列 +- `lg` ≤1200px:物品详情上移、列间距收紧 -要点:按 `matchid` 去重;截图进 `pc/samples/raw/{matchid}/`;识别在工作线程; -未标定时降级为只截图。 +手机不走上述断点,由 UA 门禁接管。 -天梯 AP 选人按 2/2/1 成批揭晓,本轮结束前互不可见——因此必须在 -**HERO_SELECTION** 跟踪,不能只在决策时间截一张终局图;决策前期仍读几帧 -以吃到最后一人揭晓(见发现七)。 +## Elevation & Depth ---- +深度靠色调分层与细边框,不用厚重投影: -## 6. 选将全网格「克 / 搭 / 补」推荐 +1. 页面渐变底 +2. 半透明顶栏 / 面板(`panel-soft`) +3. 实心一点的浮层(`panel`、物品 sticky 详情、天赋 popover) -- **触发**:敌方至少锁定 1 人(`min_enemies`)后开始;本人锁人后清空标记。 -- **分路**:定位匹配画面字(`roles.py`)+ GSI `team_slot` 定位本人;不做截屏认自己。有分路则按 `role_tags` 过滤候选;非定位局(读不到分路字)则全英雄表。 -- **关系模型**:机制克制/搭档(`shared/data/relations.json`),不用胜率/场次——版本会变,机制边相对稳。Web 英雄详情「走势」Tab 优先 STRATZ(`web/data/stratz_hero_meta.json`):勋章条切换 8 档 → **最近 1 周**三卡(胜率/上场率/场次)→ 1~5 号位分路胜率 → **近 8 周**逐周列表(日期、胜率、周环比、区间缩放横条、场次;新→旧);OpenDota(`web/data/hero_stats.json`,近约 7 天)作兜底。「对位」Tab 展示 STRATZ 数值 Top(`web/data/stratz_matchup_tops.json`),与网格定性「克/怕/搭」分开展示;均不进局内推荐。 -- **阵容画像(规则,不接 AI)**:[`pc/draft_archetypes.py`](pc/draft_archetypes.py) 识别敌方推进/全球流、敌我 tag 缺口(缺控制/爆发/输出/先手);输出一句 `analysis`(如 `敌:缺控制·偏推进 | 我:缺爆发`)与每英雄短 `reasons`。 -- **纳入**: - - **克**:关系克制边;推进/全球流应对表(如美杜莎对推进);惩戒敌方缺口 - - **搭**:与已锁己有搭档边 - - **补**:填我方缺口(`min_heroes_for_gaps` 默认 2 才注入缺口类标记) -- **打分**:关系边为主;阵容应对 / 缺口小幅加分;敌方 tags 软加分仅排序。 -- **展示**:网格左上角全量相关格标「克」(青)/「搭」(琥珀)/「补」(紫灰);叠加层横条显示阵容分析;原因进日志与 `results` JSON,不写在每个格子旁。 -- **上分帝 Web**:`web/serve_relations.py` 本地开发服务;仿选将网格查看克制/被克制/搭档;数据手改 `shared/data/relations.json` 或从 xlsx 导入。 - 另有顶级 **排行**、**走势**(`#/trends[/bracket]`,近 8 周高胜率/上场率榜)、**物品**、**版本** 页;目录见 `web/data/item_shop.json` / `web/data/patches.json` / `web/data/leaderboards.json`。 - 英雄详情子标签含 **走势**(见上;`#/heroes//trends`)、**对位**(克制/被克/搭档三列 STRATZ Top;`#/heroes//matchups`)、**近期比赛**(`#/heroes//matches`)。排行页为 Immortal 四区选手榜。 -- **Web Hash 路由**:状态(顶层标签 / 选中英雄 + 详情子标签 / 选中物品 / 选中版本 / - 标签筛选 / 搜索)双向同步进 URL(`#/heroes/axe/core?tags=核心&q=axe`、`#/heroes/axe/trends` 等)。选 Hash - 而非 Path 路由:静态导出(GitHub Pages)无需 rewrite、`web/serve_relations.py` 零改动、 - 相对路径子站亦可用。写 URL 用 `history.pushState`/`replaceState`(静默,不触发 - `hashchange`,无回环);前进/后退靠 `hashchange` 监听重 parse + 校验 + render。 - 搜索 debounce 300ms + `replaceState` 防刷历史栈;坏链接丢弃该项不崩。路由逻辑集中在 - `web/frontend/router.js`,`app.js` 只在 `main()` 装 `installRouter` + 各 state 变更点 - 调 `syncStateToUrl`。 -- **不做**:避用标、Steam 登录页、爬 Dotabuff;关系**不**写入 `shared/data/heroes.json`;不接实时 AI。 +选中态用 inset 底线、填充主色或 1–2px `accent` 描边;阴影仅用于浮层(popover / 气泡),模糊约 24–32px、黑色 45% 透明度。 ---- +## Shapes -## 7. 与 dota2-hex +圆角语言偏克制: -独立验证项目。可选:Rust 重写并入,或本地旁路 HTTP 回传。 -合规同 `dota2-hex`:**屏幕可见信息 only**。 +- `sm` 4px:表格内图标、小徽章 +- `md` 8px:输入框、多数按钮、指标卡 +- `lg` 12px:详情面板、机制卡、主内容卡 +- `xl` 16px:仅大卡片(主播卡)需要时使用,勿扩散到所有面板 +- `full`:筛选 pill、关注按钮 ---- +禁止同一视图混用锐角与大圆角而无语义理由。 -## 8. 参考 +## Components -- [Valve #9562](https://github.com/ValveSoftware/Dota2-Gameplay/issues/9562) -- [Valve #14915](https://github.com/ValveSoftware/Dota2-Gameplay/issues/14915)(开发者 OCR ≈85%) -- [Overwolf Dota 2 GEP](https://dev.overwolf.com/ow-native/live-game-data-gep/supported-games/dota-2/) -- Steam CDN:`https://cdn.cloudflare.steamstatic.com/apps/dota2/images/dota_react/heroes/{key}.png` +### 导航 + +- **Main tabs**:无边框文字 Tab,透明背景;相邻项以低对比斜切细线分隔。未选 muted,hover 仅提亮文字,禁止施加矩形 / 渐变底以免与斜切线冲突;选中使用高对比文字 + 居中 44px、2px 高的 `primary` 下划线。宽屏点击区高 44px、字号 16px、最小宽 76px,与搜索 / 邮件对齐;`lg` 断点收敛至 40px / 15px。禁止重新包回连续按钮外框或梯形按钮底。 +- **Detail tabs**:下划线式,字号 `body-md`。 +- **Aside filters**(地区 / 机制效果):竖向 filled;选中 `primary` 底 + `on-accent` 字。 +- **顶栏邮件**:40×40 图标按钮,muted;hover 提亮。放在右侧工具区,不绝对悬浮到视口角。 + +### 英雄定位筛选 + +位于英雄网格和详情面板之间,使用 Valve 选人筛选符号与中文标签的紧凑斜角按钮。 +「全部」用中性网格符号;核心 / 辅助 / 推进 / 逃生 / 爆发 / 先手 / 控制 / 耐久使用对应 +Valve 图标。支持多选筛选(`active`);选中英雄时,其定位以同色选中态(`matched`) +高亮,不单独在详情区再放 Chip。当前不展示「幻象」筛选,勿与主导航混排。 + +### 搜索 + +高 40px,圆角 `md`,左侧放大镜;focus 时边框与光晕用 `primary`。默认宽约 200px,落在首行右侧工具区(非 absolute 贴边);物品页换物品搜索,其他页隐藏搜索。 + +### 面板 / 卡片 + +共享 `.ui-panel`:边框 `border`、背景 `panel-soft`、圆角 `lg`、内边距 16px。空态统一 `.ui-empty`:居中 muted、`body-md`、上下 48px。 + +### 表格 + +表头 `label-sm` muted;单元格 `body-md`;数字 `tabular-nums`。行 hover 轻微提亮,不加斑马纹。 + +### 焦点 + +所有可交互控件:`outline: 2px solid primary; outline-offset: 2px`(`.ui-focus`)。最小点击高度 36–40px。 + +### 业务标记(不可通用化) + +- 克 / 怕 / 搭 → `good` / `bad` / `syn` +- 直播环与关注按钮 → `live` +- 装备金价 → `gold` + +## Do's and Don'ts + +- Do 改视觉前先更新本文件令牌,再改 `style.css` 变量与引用。 +- Do 用命名宽度档位与三档断点;Don't 再加孤立 `max-width: Npx`。 +- Do 每个顶层页提供清晰的页标题语义(可见 `h1`/`h2` 或等价 `.page-title`)。 +- Don't 用 `font-weight: 650` 或非尺度字号(如 `13.5px`)。 +- Don't 把直播粉、胜负红、金价黄当作通用 CTA。 +- Don't 为真移动端在现有碎片 `@media` 上硬撑;门禁解除前保持桌面壳。 +- Do 保持英雄底栏固定高与列表页整板滚动两种壳模型,勿发明第三种。 +- Don't 在 data-only 刷新里 bump `SITE_VERSION`;视觉发版才同步 config / export / `?v=` 缓存戳。 diff --git a/README.md b/README.md index 2237ba7..45c35bd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Dota 2 **天梯选将识别**:从选人 / 决策时间截图中识别双方 10 本地、离线、秒级;模板来自 Steam CDN 官方头像。GSI 只负责触发时机,阵容靠视觉读顶栏。 -设计背景见 [DESIGN.md](DESIGN.md);协作约定见 [AGENTS.md](AGENTS.md);变更记录见 [CHANGELOG.md](CHANGELOG.md)。 +架构与方案背景见 [ARCHITECTURE.md](ARCHITECTURE.md);上分帝 Web 视觉设计规范见 [DESIGN.md](DESIGN.md);协作约定见 [AGENTS.md](AGENTS.md);变更记录见 [CHANGELOG.md](CHANGELOG.md)。 ## 原理 @@ -122,7 +122,7 @@ python web/refresh_web.py --tier patch --skip-deploy --skip-oss 顶部另有 **走势** 页:近 8 周各段位高胜率 / 上场率榜(`#/trends[/bracket]`;STRATZ 周胜率数据)。 顶部另有 **物品** 页:对齐 [官网商店物品](https://www.dota2.com.cn/items/index.htm) 的 **11 列竖排**(基础分类 5 列 + 合成分类 6 列),点选后详情显示在右侧(含合成组件)。目录来自 `web/data/item_shop.json`(`python web/fetch_item_shop.py`)。 顶部另有 **版本** 页:默认展示最新版本完整改动(综合 / 物品 / 中立物品 / 英雄),下拉切换近一年其它版本;数据来自 `web/data/patches.json`(`python web/fetch_patches.py`,近一年窗口可用 `--days` / `--since` 调)。 -数据在 `shared/data/relations.json`(英雄关系,不用胜率);选将全网格「克/搭/补」推荐读同一份文件,并用 `pc/draft_archetypes.py` 规则识别推进/全球流与敌我缺口(不接 AI)。热门装备来自 OpenDota 统计。**走势**优先 STRATZ(`web/data/stratz_hero_meta.json`,按勋章近 8 周 + 最近 1 周分路;`python web/fetch_stratz_meta.py`,需 token),OpenDota 各段位胜率/上场率/场次在 `web/data/hero_stats.json`(近约 7 天兜底;不进推荐)。**对位**数值 Top 在 `web/data/stratz_matchup_tops.json`(STRATZ 全局聚合相对优势,非走势页段位/周口径;与网格定性克/搭分开展示;有 OpenDota matchups 时页面可提示来源一致/分歧)。被克装备以描述标签 + 规则为主;`web/fetch_item_counter_stats.py` 另算敌方终局装备相对同装备全局基线的购买率提升/胜率差,对全部英雄的已有候选小幅调序;装备卡有数据时显示对阵该英雄的敌方队伍终局装备出现率。统计显著的新组合仍须确认机制成立后写入 `web/data/hero_fear_overrides.json`,避免“高相关但不克制”的误报。可改 `web/data/item_tag_overrides.json` 后重跑 `web/fetch_items_meta.py` 与 `web/item_fears.py`。**尚无对局内出装推荐**。 +数据在 `shared/data/relations.json`(英雄关系,不用胜率);选将全网格「克/搭/补」推荐读同一份文件,并用 `pc/draft_archetypes.py` 规则识别推进/全球流与敌我缺口(不接 AI)。热门装备来自 OpenDota 统计。**走势**优先 STRATZ(`web/data/stratz_hero_meta.json`,按勋章近 8 周 + 最近 1 周分路;`python web/fetch_stratz_meta.py`,需 token),OpenDota 各段位胜率/上场率/场次在 `web/data/hero_stats.json`(近约 7 天兜底;不进推荐)。**对位**数值 Top 仅用 `web/data/stratz_matchup_tops.json`(STRATZ 全局聚合相对优势,非走势页段位/周口径;与网格定性克/搭分开展示)。被克装备以描述标签 + 规则为主;`web/fetch_item_counter_stats.py` 另算敌方终局装备相对同装备全局基线的购买率提升/胜率差,对全部英雄的已有候选小幅调序;装备卡有数据时显示对阵该英雄的敌方队伍终局装备出现率。统计显著的新组合仍须确认机制成立后写入 `web/data/hero_fear_overrides.json`,避免“高相关但不克制”的误报。可改 `web/data/item_tag_overrides.json` 后重跑 `web/fetch_items_meta.py` 与 `web/item_fears.py`。**尚无对局内出装推荐**。 ## 快速开始(GSI 自动跟踪) diff --git a/pc/fetch_cdn_templates.py b/pc/fetch_cdn_templates.py index 94a5426..afa1dd4 100644 --- a/pc/fetch_cdn_templates.py +++ b/pc/fetch_cdn_templates.py @@ -102,14 +102,13 @@ def stats_from_opendota(raw: dict) -> dict: "vision_day": int(_num(raw, "day_vision", 1800)), "vision_night": int(_num(raw, "night_vision", 800)), } - # turn_rate is null in OpenDota for heroes using the game default — omit - # rather than inventing 0.6 so the UI only shows an explicit value. + # OpenDota leaves turn_rate null for the engine default (0.6); match + # dota2.com.cn and always surface a value for the combat panel. turn = raw.get("turn_rate") - if turn is not None: - try: - result["turn_rate"] = _round1(float(turn)) - except (TypeError, ValueError): - pass + try: + result["turn_rate"] = _round1(float(turn)) if turn is not None else 0.6 + except (TypeError, ValueError): + result["turn_rate"] = 0.6 return result diff --git a/shared/audit_relations.py b/shared/audit_relations.py index d17d29b..b5e19ba 100644 --- a/shared/audit_relations.py +++ b/shared/audit_relations.py @@ -1,10 +1,12 @@ -"""Audit qualitative relations.json against OpenDota + STRATZ. +"""Audit qualitative relations.json against STRATZ matchup / synergy caches. Sources: - - shared/data/matchups.json OpenDota vs winrates - shared/data/stratz_matchups.json STRATZ vs advantage (fetch_stratz.py --mode matchups) - shared/data/synergies.json STRATZ teammate synergies - - web/data/stratz_matchup_tops.json Web 对位 Top (optional cross-check section) + +Hero matchup numbers for the Web site come only from +web/data/stratz_matchup_tops.json (fetch_stratz_meta.py); this audit does not +cross-check against OpenDota. Usage: python shared/audit_relations.py @@ -23,14 +25,11 @@ import json from datetime import datetime, timezone from shared.grid import hero_table -from shared.matchup_cross import audit_matchup_tops -from shared.paths import DATA, ROOT, SHARED_DATA +from shared.paths import ROOT, SHARED_DATA from shared.relations import DEFAULT_RELATIONS, load_relations -MATCHUPS = SHARED_DATA / "matchups.json" STRATZ_MATCHUPS = SHARED_DATA / "stratz_matchups.json" SYNERGIES = SHARED_DATA / "synergies.json" -STRATZ_MATCHUP_TOPS = DATA / "stratz_matchup_tops.json" # Legacy monorepo-move leftovers (repo-root data/). _LEGACY_DATA = ROOT / "data" @@ -52,10 +51,6 @@ def _load_json_safe(path: Path) -> dict: return {} -def _pair_key(a: str, b: str) -> tuple[str, str]: - return (a, b) if a <= b else (b, a) - - def load_id_maps() -> tuple[dict[int, str], dict[str, int], dict[str, str]]: table = hero_table() id_to_key = {int(h["id"]): h["key"] for h in table} @@ -64,41 +59,6 @@ def load_id_maps() -> tuple[dict[int, str], dict[str, int], dict[str, str]]: return id_to_key, key_to_id, names -def matchup_wr(by_hero: dict, aid: int, bid: int) -> tuple[float | None, int]: - cell = (by_hero.get(str(aid)) or {}).get(str(bid)) - if not cell: - return None, 0 - games = int(cell.get("games") or 0) - wins = int(cell.get("wins") or 0) - if games <= 0: - return None, 0 - return wins / games, games - - -def build_baseline(by_hero: dict) -> dict[int, float]: - out: dict[int, float] = {} - for hid_s, opps in by_hero.items(): - tw = tg = 0 - for cell in opps.values(): - g = int(cell.get("games") or 0) - w = int(cell.get("wins") or 0) - tw += w - tg += g - if tg > 0: - out[int(hid_s)] = tw / tg - return out - - -def odota_adv(by_hero: dict, baseline: dict[int, float], aid: int, bid: int) -> tuple[float | None, int]: - wr, games = matchup_wr(by_hero, aid, bid) - if wr is None: - return None, 0 - base = baseline.get(aid) - if base is None: - return None, games - return wr - base, games - - def stratz_adv(by_hero: dict, aid: int, bid: int) -> tuple[float | None, int]: cell = (by_hero.get(str(aid)) or {}).get(str(bid)) if not cell: @@ -112,31 +72,23 @@ def stratz_adv(by_hero: dict, aid: int, bid: int) -> tuple[float | None, int]: def main() -> None: ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("--min-games", type=int, default=80) - ap.add_argument("--odota-agree", type=float, default=0.015) - ap.add_argument("--odota-disagree", type=float, default=-0.015) ap.add_argument("--stratz-agree", type=float, default=1.0) ap.add_argument("--stratz-disagree", type=float, default=-1.0) ap.add_argument("--syn-agree", type=float, default=1.0) - ap.add_argument("--top", type=int, default=10) + ap.add_argument("--suggest-min-adv", type=float, default=2.0) ap.add_argument("--out", type=Path, default=SHARED_DATA / "relations_audit.json") args = ap.parse_args() - matchups_path = _resolve_cache(MATCHUPS, _LEGACY_DATA / "matchups.json") stratz_path = _resolve_cache(STRATZ_MATCHUPS, _LEGACY_DATA / "stratz_matchups.json") syn_path = _resolve_cache(SYNERGIES, _LEGACY_DATA / "synergies.json") - tops_path = _resolve_cache(STRATZ_MATCHUP_TOPS, _LEGACY_DATA / "stratz_matchup_tops.json") - id_to_key, key_to_id, names = load_id_maps() + _id_to_key, key_to_id, names = load_id_maps() all_keys = sorted(key_to_id.keys(), key=lambda k: key_to_id[k]) rel = load_relations() - odota = _load_json_safe(matchups_path) stratz = _load_json_safe(stratz_path) syn_file = _load_json_safe(syn_path) - tops = _load_json_safe(tops_path) - by_o = odota.get("by_hero") or {} by_s = stratz.get("by_hero") or {} by_syn = syn_file.get("by_hero") or {} - baseline = build_baseline(by_o) counters = [(e["a"], e["b"], e.get("reason") or "") for e in rel.get("counters") or []] syns = [(e["a"], e["b"], e.get("reason") or "") for e in rel.get("synergies") or []] @@ -148,9 +100,7 @@ def main() -> None: if aid is None or bid is None: counter_no_data.append({"a": a, "b": b, "why": "unknown_hero"}) continue - o_adv, o_games = odota_adv(by_o, baseline, aid, bid) s_adv, s_games = stratz_adv(by_s, aid, bid) - r_o, r_og = odota_adv(by_o, baseline, bid, aid) r_s, r_sg = stratz_adv(by_s, bid, aid) row = { "a": a, @@ -158,34 +108,23 @@ def main() -> None: "b": b, "b_loc": names.get(b, b), "reason": reason, - "opendota_adv": None if o_adv is None else round(o_adv, 4), - "opendota_games": o_games, "stratz_adv": None if s_adv is None else round(s_adv, 3), "stratz_games": s_games, - "reverse_opendota_adv": None if r_o is None else round(r_o, 4), "reverse_stratz_adv": None if r_s is None else round(r_s, 3), } - o_ok = o_adv is not None and o_games >= args.min_games and o_adv >= args.odota_agree - o_bad = o_adv is not None and o_games >= args.min_games and o_adv <= args.odota_disagree s_ok = s_adv is not None and s_games >= args.min_games and s_adv >= args.stratz_agree - s_bad = s_adv is not None and s_games >= args.min_games and s_adv <= args.stratz_disagree - if o_adv is None and s_adv is None: - counter_no_data.append({**row, "why": "missing_both"}) - elif o_games < args.min_games and (s_games < args.min_games or s_adv is None): + s_bad = ( + s_adv is not None and s_games >= args.min_games and s_adv <= args.stratz_disagree + ) + if s_adv is None: + counter_no_data.append({**row, "why": "missing_stratz"}) + elif s_games < args.min_games: counter_no_data.append({**row, "why": "low_games"}) - elif o_ok or s_ok: - sources = [] - if o_ok: - sources.append("opendota") - if s_ok: - sources.append("stratz") - counter_ok.append({**row, "agree_sources": sources}) - elif o_bad or s_bad: + elif s_ok: + counter_ok.append({**row, "agree_sources": ["stratz"]}) + elif s_bad: tip = "conflict" - if ( - (r_o is not None and r_og >= args.min_games and r_o >= args.odota_agree) - or (r_s is not None and r_sg >= args.min_games and r_s >= args.stratz_agree) - ): + if r_s is not None and r_sg >= args.min_games and r_s >= args.stratz_agree: tip = "maybe_reverse" counter_conflict.append({**row, "tip": tip}) else: @@ -197,7 +136,9 @@ def main() -> None: if aid is None or bid is None: syn_missing.append({"a": a, "b": b, "why": "unknown_hero"}) continue - cell = (by_syn.get(str(aid)) or {}).get(str(bid)) or (by_syn.get(str(bid)) or {}).get(str(aid)) + cell = (by_syn.get(str(aid)) or {}).get(str(bid)) or ( + by_syn.get(str(bid)) or {} + ).get(str(aid)) if not cell: syn_missing.append( { @@ -236,15 +177,11 @@ def main() -> None: if akey == bkey or (akey, bkey) in known_counters: continue bid = key_to_id[bkey] - o_adv, o_games = odota_adv(by_o, baseline, aid, bid) s_adv, s_games = stratz_adv(by_s, aid, bid) if ( - o_adv is not None - and o_games >= args.min_games - and o_adv >= 0.03 - and s_adv is not None + s_adv is not None and s_games >= args.min_games - and s_adv >= 2.0 + and s_adv >= args.suggest_min_adv ): suggestions.append( { @@ -252,13 +189,11 @@ def main() -> None: "a_loc": names[akey], "b": bkey, "b_loc": names[bkey], - "opendota_adv": round(o_adv, 4), - "opendota_games": o_games, "stratz_adv": round(s_adv, 3), "stratz_games": s_games, } ) - suggestions.sort(key=lambda r: (-r["stratz_adv"], -r["opendota_adv"])) + suggestions.sort(key=lambda r: (-r["stratz_adv"], -r["stratz_games"])) suggestions = suggestions[:100] counters_from: dict[str, int] = {k: 0 for k in all_keys} @@ -276,9 +211,6 @@ def main() -> None: syn_count[b] += 1 missing_cache = { - "opendota_matchups": [ - f"{names[k]}({k})" for k in all_keys if str(key_to_id[k]) not in by_o - ], "stratz_matchups": [ f"{names[k]}({k})" for k in all_keys if str(key_to_id[k]) not in by_s ], @@ -310,28 +242,17 @@ def main() -> None: "counters_out": counters_from[k], "counters_in": counters_to[k], "synergies": syn_count[k], - "cross_source_suggestions": suggest_by_hero.get(k, 0), + "stratz_suggestions": suggest_by_hero.get(k, 0), } for k in empty + [x for x in thin if x not in empty] ], key=lambda r: ( r["counters_out"] + r["counters_in"] + r["synergies"], - -r["cross_source_suggestions"], + -r["stratz_suggestions"], r["key"], ), ) - web_tops_audit = None - if tops.get("by_hero"): - web_tops_audit = audit_matchup_tops( - tops, - odota, - id_to_key=id_to_key, - key_to_id=key_to_id, - names=names, - min_games=args.min_games, - ) - def _rel(path: Path) -> str | None: if not path.is_file(): return None @@ -344,12 +265,6 @@ def main() -> None: "generated_at": datetime.now(timezone.utc).isoformat(), "sources": { "relations": str(DEFAULT_RELATIONS.relative_to(ROOT)).replace("\\", "/"), - "opendota_matchups": { - "path": _rel(matchups_path), - "source": odota.get("source"), - "fetched_at": odota.get("fetched_at"), - "heroes": len(by_o), - }, "stratz_matchups": { "path": _rel(stratz_path), "source": stratz.get("source"), @@ -362,20 +277,13 @@ def main() -> None: "fetched_at": syn_file.get("fetched_at"), "heroes": len(by_syn), }, - "stratz_matchup_tops": { - "path": _rel(tops_path), - "source": tops.get("source"), - "fetched_at": tops.get("fetched_at"), - "heroes": len(tops.get("by_hero") or {}), - }, }, "thresholds": { "min_games": args.min_games, - "odota_agree": args.odota_agree, - "odota_disagree": args.odota_disagree, "stratz_agree": args.stratz_agree, "stratz_disagree": args.stratz_disagree, "syn_agree": args.syn_agree, + "suggest_min_adv": args.suggest_min_adv, }, "summary": { "relations_counters": len(counters), @@ -401,16 +309,11 @@ def main() -> None: }, "counter_conflicts": sorted( counter_conflict, - key=lambda r: ((r.get("stratz_adv") or 0), (r.get("opendota_adv") or 0)), + key=lambda r: (r.get("stratz_adv") or 0), ), - "counter_agree_both": [ - r - for r in counter_ok - if set(r.get("agree_sources") or []) == {"opendota", "stratz"} - ], - "suggest_add_counters_cross_source": suggestions, + "counter_agree": counter_ok, + "suggest_add_counters": suggestions, "synergy_conflicts": syn_conflict, - "web_matchup_tops_cross": web_tops_audit, } args.out.parent.mkdir(parents=True, exist_ok=True) @@ -421,26 +324,18 @@ def main() -> None: s = report["summary"] print("=== relations audit ===") print( - f"counters: agree={s['counter_agree']} (both={len(report['counter_agree_both'])}) " - f"conflict={s['counter_conflict']} weak={s['counter_weak']} no_data={s['counter_no_data']} " - f"/ total={s['relations_counters']}" + f"counters: agree={s['counter_agree']} " + f"conflict={s['counter_conflict']} weak={s['counter_weak']} " + f"no_data={s['counter_no_data']} / total={s['relations_counters']}" ) print( f"synergies: agree={s['synergy_agree']} conflict={s['synergy_conflict']} " f"weak/missing={s['synergy_weak_or_missing']} / total={s['relations_synergies']}" ) print( - f"qualitative gaps: empty={s['heroes_empty_relations']} thin(<={1} edge)={s['heroes_thin_relations']} " - f"/ heroes={s['heroes_total']}" + f"qualitative gaps: empty={s['heroes_empty_relations']} " + f"thin(<={1} edge)={s['heroes_thin_relations']} / heroes={s['heroes_total']}" ) - if web_tops_audit: - ws = web_tops_audit["summary"] - print( - "web matchup tops × OpenDota: " - f"agree={ws['agree']} conflict={ws['conflict']} weak={ws['weak']} " - f"/ rows={ws['counter_rows']} heroes={ws['heroes']}" - ) - print(f" note: {web_tops_audit['manual_review_note']}") print("cache gaps:") for k, v in missing_cache.items(): print(f" {k}: {len(v)}") @@ -449,7 +344,7 @@ def main() -> None: if row["counters_out"] + row["counters_in"] + row["synergies"] == 0: print( f" {row['name_loc']}({row['key']}) " - f"suggest={row['cross_source_suggestions']}" + f"suggest={row['stratz_suggestions']}" ) print(f"\nwrote {args.out}") diff --git a/shared/data/heroes.json b/shared/data/heroes.json index bfc6396..9d2c6e6 100644 --- a/shared/data/heroes.json +++ b/shared/data/heroes.json @@ -1,5608 +1,5690 @@ [ - { - "id": 1, - "key": "antimage", - "name": "Anti-Mage", - "attr": "agi", - "name_loc": "敌法师", - "roles": [ - "Carry", - "Escape", - "Nuker" - ], - "tags": [ - "核心", - "逃生", - "爆发" - ], - "aliases": [ - "敌法" - ], - "abbr": [ - "am" - ], - "base_str": 21, - "str_gain": 1.6, - "base_agi": 24, - "agi_gain": 2.8, - "base_int": 12, - "int_gain": 1.8, - "health": 582, - "mana": 219, - "health_regen": 3.1, - "mana_regen": 0.6, - "armor": 6.0, - "damage_min": 53, - "damage_max": 57, - "move_speed": 310, - "attack_range": 150, - "attack_rate": 1.4, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 2, - "key": "axe", - "name": "Axe", - "attr": "str", - "name_loc": "斧王", - "roles": [ - "Initiator", - "Durable", - "Disabler", - "Carry" - ], - "tags": [ - "核心", - "先手", - "控制", - "耐久" - ], - "aliases": [], - "abbr": [], - "base_str": 25, - "str_gain": 2.7, - "base_agi": 20, - "agi_gain": 1.7, - "base_int": 18, - "int_gain": 1.6, - "health": 670, - "mana": 291, - "health_regen": 5.0, - "mana_regen": 0.9, - "armor": 3.33, - "damage_min": 56, - "damage_max": 60, - "move_speed": 315, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 3, - "key": "bane", - "name": "Bane", - "attr": "all", - "name_loc": "祸乱之源", - "roles": [ - "Support", - "Disabler", - "Nuker", - "Durable" - ], - "tags": [ - "辅助", - "爆发", - "控制", - "耐久" - ], - "aliases": [], - "abbr": [], - "base_str": 23, - "str_gain": 2.5, - "base_agi": 23, - "agi_gain": 2.5, - "base_int": 23, - "int_gain": 2.5, - "health": 626, - "mana": 351, - "health_regen": 2.55, - "mana_regen": 1.15, - "armor": 4.83, - "damage_min": 72, - "damage_max": 78, - "move_speed": 305, - "attack_range": 425, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 1200 - }, - { - "id": 4, - "key": "bloodseeker", - "name": "Bloodseeker", - "attr": "agi", - "name_loc": "血魔", - "roles": [ - "Carry", - "Disabler", - "Nuker", - "Initiator" - ], - "tags": [ - "核心", - "爆发", - "先手", - "控制" - ], - "aliases": [], - "abbr": [ - "bs" - ], - "base_str": 24, - "str_gain": 2.7, - "base_agi": 24, - "agi_gain": 3.1, - "base_int": 17, - "int_gain": 2.0, - "health": 648, - "mana": 279, - "health_regen": 2.65, - "mana_regen": 0.85, - "armor": 6.0, - "damage_min": 59, - "damage_max": 65, - "move_speed": 285, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 5, - "key": "crystal_maiden", - "name": "Crystal Maiden", - "attr": "int", - "name_loc": "水晶室女", - "roles": [ - "Support", - "Disabler", - "Nuker" - ], - "tags": [ - "辅助", - "爆发", - "控制" - ], - "aliases": [ - "冰女" - ], - "abbr": [ - "cm" - ], - "base_str": 17, - "str_gain": 2.2, - "base_agi": 16, - "agi_gain": 1.8, - "base_int": 20, - "int_gain": 3.3, - "health": 494, - "mana": 315, - "health_regen": 1.95, - "mana_regen": 1.0, - "armor": 2.67, - "damage_min": 48, - "damage_max": 54, - "move_speed": 280, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 6, - "key": "drow_ranger", - "name": "Drow Ranger", - "attr": "agi", - "name_loc": "卓尔游侠", - "roles": [ - "Carry", - "Disabler", - "Pusher" - ], - "tags": [ - "核心", - "推进", - "控制" - ], - "aliases": [ - "小黑" - ], - "abbr": [ - "drow" - ], - "base_str": 16, - "str_gain": 1.9, - "base_agi": 22, - "agi_gain": 2.8, - "base_int": 15, - "int_gain": 1.4, - "health": 472, - "mana": 255, - "health_regen": 1.85, - "mana_regen": 0.75, - "armor": 3.67, - "damage_min": 49, - "damage_max": 56, - "move_speed": 310, - "attack_range": 625, - "attack_rate": 1.7, - "projectile_speed": 1250, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 7, - "key": "earthshaker", - "name": "Earthshaker", - "attr": "str", - "name_loc": "撼地者", - "roles": [ - "Support", - "Initiator", - "Disabler", - "Nuker" - ], - "tags": [ - "辅助", - "爆发", - "先手", - "控制" - ], - "aliases": [ - "小山" - ], - "abbr": [ - "es" - ], - "base_str": 22, - "str_gain": 3.7, - "base_agi": 12, - "agi_gain": 1.4, - "base_int": 18, - "int_gain": 2.1, - "health": 604, - "mana": 291, - "health_regen": 3.2, - "mana_regen": 0.9, - "armor": 4.0, - "damage_min": 49, - "damage_max": 59, - "move_speed": 315, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 8, - "key": "juggernaut", - "name": "Juggernaut", - "attr": "agi", - "name_loc": "主宰", - "roles": [ - "Carry", - "Pusher", - "Escape" - ], - "tags": [ - "核心", - "推进", - "逃生" - ], - "aliases": [ - "剑圣" - ], - "abbr": [ - "jugg" - ], - "base_str": 20, - "str_gain": 2.0, - "base_agi": 32, - "agi_gain": 2.8, - "base_int": 14, - "int_gain": 1.4, - "health": 560, - "mana": 243, - "health_regen": 2.5, - "mana_regen": 0.7, - "armor": 5.33, - "damage_min": 54, - "damage_max": 56, - "move_speed": 300, - "attack_range": 150, - "attack_rate": 1.4, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 9, - "key": "mirana", - "name": "Mirana", - "attr": "agi", - "name_loc": "米拉娜", - "roles": [ - "Carry", - "Support", - "Escape", - "Nuker", - "Disabler" - ], - "tags": [ - "核心", - "辅助", - "逃生", - "爆发", - "控制" - ], - "aliases": [ - "白虎" - ], - "abbr": [ - "potm" - ], - "base_str": 20, - "str_gain": 2.2, - "base_agi": 26, - "agi_gain": 3.1, - "base_int": 22, - "int_gain": 1.9, - "health": 560, - "mana": 339, - "health_regen": 2.25, - "mana_regen": 1.5, - "armor": 2.33, - "damage_min": 48, - "damage_max": 54, - "move_speed": 285, - "attack_range": 630, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 10, - "key": "morphling", - "name": "Morphling", - "attr": "agi", - "name_loc": "变体精灵", - "roles": [ - "Carry", - "Escape", - "Durable", - "Nuker", - "Disabler" - ], - "tags": [ - "核心", - "逃生", - "爆发", - "控制", - "耐久" - ], - "aliases": [ - "水人" - ], - "abbr": [ - "morph" - ], - "base_str": 23, - "str_gain": 2.6, - "base_agi": 24, - "agi_gain": 3.9, - "base_int": 19, - "int_gain": 1.8, - "health": 626, - "mana": 303, - "health_regen": 2.8, - "mana_regen": 0.95, - "armor": 3.0, - "damage_min": 36, - "damage_max": 45, - "move_speed": 285, - "attack_range": 350, - "attack_rate": 1.5, - "projectile_speed": 1300, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 11, - "key": "nevermore", - "name": "Shadow Fiend", - "attr": "agi", - "name_loc": "影魔", - "roles": [ - "Carry", - "Nuker" - ], - "tags": [ - "核心", - "爆发" - ], - "aliases": [], - "abbr": [ - "sf" - ], - "base_str": 19, - "str_gain": 2.7, - "base_agi": 25, - "agi_gain": 3.6, - "base_int": 18, - "int_gain": 2.2, - "health": 538, - "mana": 291, - "health_regen": 2.15, - "mana_regen": 1.2, - "armor": 4.17, - "damage_min": 41, - "damage_max": 47, - "move_speed": 305, - "attack_range": 525, - "attack_rate": 1.6, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 12, - "key": "phantom_lancer", - "name": "Phantom Lancer", - "attr": "agi", - "name_loc": "幻影长矛手", - "roles": [ - "Carry", - "Escape", - "Pusher", - "Nuker" - ], - "tags": [ - "核心", - "推进", - "逃生", - "爆发", - "幻象" - ], - "aliases": [ - "猴子" - ], - "abbr": [ - "pl" - ], - "base_str": 21, - "str_gain": 2.2, - "base_agi": 26, - "agi_gain": 3.4, - "base_int": 21, - "int_gain": 1.8, - "health": 582, - "mana": 327, - "health_regen": 4.1, - "mana_regen": 1.05, - "armor": 4.33, - "damage_min": 52, - "damage_max": 62, - "move_speed": 285, - "attack_range": 225, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 13, - "key": "puck", - "name": "Puck", - "attr": "int", - "name_loc": "帕克", - "roles": [ - "Initiator", - "Disabler", - "Escape", - "Nuker" - ], - "tags": [ - "逃生", - "爆发", - "先手", - "控制" - ], - "aliases": [], - "abbr": [], - "base_str": 17, - "str_gain": 2.4, - "base_agi": 14, - "agi_gain": 2.3, - "base_int": 23, - "int_gain": 3.8, - "health": 494, - "mana": 351, - "health_regen": 2.2, - "mana_regen": 1.65, - "armor": 2.33, - "damage_min": 45, - "damage_max": 53, - "move_speed": 290, - "attack_range": 550, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 14, - "key": "pudge", - "name": "Pudge", - "attr": "str", - "name_loc": "帕吉", - "roles": [ - "Disabler", - "Initiator", - "Durable", - "Nuker" - ], - "tags": [ - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "屠夫" - ], - "abbr": [], - "base_str": 25, - "str_gain": 3.0, - "base_agi": 11, - "agi_gain": 1.4, - "base_int": 16, - "int_gain": 1.8, - "health": 670, - "mana": 267, - "health_regen": 5.0, - "mana_regen": 0.8, - "armor": 1.83, - "damage_min": 70, - "damage_max": 76, - "move_speed": 280, - "attack_range": 175, - "attack_rate": 1.7, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 15, - "key": "razor", - "name": "Razor", - "attr": "agi", - "name_loc": "雷泽", - "roles": [ - "Carry", - "Durable", - "Nuker", - "Pusher" - ], - "tags": [ - "核心", - "推进", - "爆发", - "耐久" - ], - "aliases": [ - "电棍" - ], - "abbr": [], - "base_str": 22, - "str_gain": 2.8, - "base_agi": 24, - "agi_gain": 2.8, - "base_int": 21, - "int_gain": 2.2, - "health": 604, - "mana": 327, - "health_regen": 3.2, - "mana_regen": 1.05, - "armor": 4.0, - "damage_min": 47, - "damage_max": 49, - "move_speed": 280, - "attack_range": 475, - "attack_rate": 1.7, - "projectile_speed": 2000, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 16, - "key": "sand_king", - "name": "Sand King", - "attr": "all", - "name_loc": "沙王", - "roles": [ - "Initiator", - "Disabler", - "Support", - "Nuker", - "Escape" - ], - "tags": [ - "辅助", - "逃生", - "爆发", - "先手", - "控制" - ], - "aliases": [], - "abbr": [ - "sk" - ], - "base_str": 23, - "str_gain": 2.3, - "base_agi": 19, - "agi_gain": 2.0, - "base_int": 17, - "int_gain": 2.0, - "health": 626, - "mana": 279, - "health_regen": 2.55, - "mana_regen": 0.85, - "armor": 4.17, - "damage_min": 60, - "damage_max": 70, - "move_speed": 290, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 17, - "key": "storm_spirit", - "name": "Storm Spirit", - "attr": "int", - "name_loc": "风暴之灵", - "roles": [ - "Carry", - "Escape", - "Nuker", - "Initiator", - "Disabler" - ], - "tags": [ - "核心", - "逃生", - "爆发", - "先手", - "控制" - ], - "aliases": [ - "蓝猫" - ], - "abbr": [ - "ss" - ], - "base_str": 21, - "str_gain": 2.0, - "base_agi": 22, - "agi_gain": 2.6, - "base_int": 23, - "int_gain": 3.7, - "health": 582, - "mana": 351, - "health_regen": 2.6, - "mana_regen": 1.15, - "armor": 4.67, - "damage_min": 47, - "damage_max": 57, - "move_speed": 285, - "attack_range": 480, - "attack_rate": 1.6, - "projectile_speed": 1100, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.8 - }, - { - "id": 18, - "key": "sven", - "name": "Sven", - "attr": "str", - "name_loc": "斯温", - "roles": [ - "Carry", - "Disabler", - "Initiator", - "Durable", - "Nuker" - ], - "tags": [ - "核心", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "流浪", - "流浪剑客" - ], - "abbr": [], - "base_str": 24, - "str_gain": 3.5, - "base_agi": 18, - "agi_gain": 2.2, - "base_int": 16, - "int_gain": 1.5, - "health": 648, - "mana": 267, - "health_regen": 3.15, - "mana_regen": 0.8, - "armor": 3.0, - "damage_min": 61, - "damage_max": 63, - "move_speed": 325, - "attack_range": 150, - "attack_rate": 1.9, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 19, - "key": "tiny", - "name": "Tiny", - "attr": "str", - "name_loc": "小小", - "roles": [ - "Carry", - "Nuker", - "Pusher", - "Initiator", - "Durable", - "Disabler" - ], - "tags": [ - "核心", - "推进", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [], - "abbr": [], - "base_str": 30, - "str_gain": 4.2, - "base_agi": 0, - "agi_gain": 0.0, - "base_int": 17, - "int_gain": 2.2, - "health": 780, - "mana": 279, - "health_regen": 5.0, - "mana_regen": 0.85, - "armor": 0.0, - "damage_min": 61, - "damage_max": 67, - "move_speed": 275, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 20, - "key": "vengefulspirit", - "name": "Vengeful Spirit", - "attr": "agi", - "name_loc": "复仇之魂", - "roles": [ - "Support", - "Initiator", - "Disabler", - "Nuker", - "Escape" - ], - "tags": [ - "辅助", - "逃生", - "爆发", - "先手", - "控制" - ], - "aliases": [], - "abbr": [ - "vs" - ], - "base_str": 20, - "str_gain": 2.6, - "base_agi": 23, - "agi_gain": 3.0, - "base_int": 19, - "int_gain": 1.5, - "health": 560, - "mana": 303, - "health_regen": 2.25, - "mana_regen": 0.95, - "armor": 3.83, - "damage_min": 51, - "damage_max": 57, - "move_speed": 300, - "attack_range": 400, - "attack_rate": 1.5, - "projectile_speed": 1500, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 21, - "key": "windrunner", - "name": "Windranger", - "attr": "all", - "name_loc": "风行者", - "roles": [ - "Carry", - "Support", - "Disabler", - "Escape", - "Nuker" - ], - "tags": [ - "核心", - "辅助", - "逃生", - "爆发", - "控制" - ], - "aliases": [ - "风行" - ], - "abbr": [ - "wr" - ], - "base_str": 18, - "str_gain": 2.6, - "base_agi": 17, - "agi_gain": 2.1, - "base_int": 18, - "int_gain": 3.2, - "health": 516, - "mana": 291, - "health_regen": 2.05, - "mana_regen": 1.15, - "armor": 2.83, - "damage_min": 61, - "damage_max": 73, - "move_speed": 285, - "attack_range": 600, - "attack_rate": 1.5, - "projectile_speed": 1250, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.8 - }, - { - "id": 22, - "key": "zuus", - "name": "Zeus", - "attr": "int", - "name_loc": "宙斯", - "roles": [ - "Nuker", - "Carry" - ], - "tags": [ - "核心", - "爆发" - ], - "aliases": [], - "abbr": [ - "zeus" - ], - "base_str": 21, - "str_gain": 2.1, - "base_agi": 11, - "agi_gain": 1.2, - "base_int": 23, - "int_gain": 3.3, - "health": 582, - "mana": 351, - "health_regen": 2.35, - "mana_regen": 1.15, - "armor": 2.83, - "damage_min": 53, - "damage_max": 63, - "move_speed": 305, - "attack_range": 380, - "attack_rate": 1.7, - "projectile_speed": 1100, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 23, - "key": "kunkka", - "name": "Kunkka", - "attr": "str", - "name_loc": "昆卡", - "roles": [ - "Carry", - "Support", - "Disabler", - "Initiator", - "Durable", - "Nuker" - ], - "tags": [ - "核心", - "辅助", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "船长" - ], - "abbr": [], - "base_str": 24, - "str_gain": 3.6, - "base_agi": 14, - "agi_gain": 1.6, - "base_int": 18, - "int_gain": 1.8, - "health": 648, - "mana": 291, - "health_regen": 3.15, - "mana_regen": 0.9, - "armor": 4.33, - "damage_min": 54, - "damage_max": 60, - "move_speed": 315, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 25, - "key": "lina", - "name": "Lina", - "attr": "int", - "name_loc": "莉娜", - "roles": [ - "Support", - "Carry", - "Nuker", - "Disabler" - ], - "tags": [ - "核心", - "辅助", - "爆发", - "控制" - ], - "aliases": [ - "火女" - ], - "abbr": [], - "base_str": 20, - "str_gain": 2.4, - "base_agi": 23, - "agi_gain": 2.4, - "base_int": 30, - "int_gain": 4.0, - "health": 560, - "mana": 435, - "health_regen": 2.25, - "mana_regen": 1.5, - "armor": 3.83, - "damage_min": 51, - "damage_max": 59, - "move_speed": 290, - "attack_range": 670, - "attack_rate": 1.6, - "projectile_speed": 1000, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 26, - "key": "lion", - "name": "Lion", - "attr": "int", - "name_loc": "莱恩", - "roles": [ - "Support", - "Disabler", - "Nuker", - "Initiator" - ], - "tags": [ - "辅助", - "爆发", - "先手", - "控制" - ], - "aliases": [], - "abbr": [], - "base_str": 18, - "str_gain": 2.4, - "base_agi": 15, - "agi_gain": 1.7, - "base_int": 19, - "int_gain": 3.5, - "health": 516, - "mana": 303, - "health_regen": 2.3, - "mana_regen": 0.95, - "armor": 2.5, - "damage_min": 48, - "damage_max": 54, - "move_speed": 290, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 27, - "key": "shadow_shaman", - "name": "Shadow Shaman", - "attr": "int", - "name_loc": "暗影萨满", - "roles": [ - "Support", - "Pusher", - "Disabler", - "Nuker", - "Initiator" - ], - "tags": [ - "辅助", - "推进", - "爆发", - "先手", - "控制" - ], - "aliases": [ - "萨满" - ], - "abbr": [ - "sshaman" - ], - "base_str": 20, - "str_gain": 2.3, - "base_agi": 16, - "agi_gain": 1.6, - "base_int": 23, - "int_gain": 3.5, - "health": 560, - "mana": 351, - "health_regen": 3.0, - "mana_regen": 1.15, - "armor": 4.67, - "damage_min": 63, - "damage_max": 70, - "move_speed": 285, - "attack_range": 400, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 28, - "key": "slardar", - "name": "Slardar", - "attr": "str", - "name_loc": "斯拉达", - "roles": [ - "Carry", - "Durable", - "Initiator", - "Disabler", - "Escape" - ], - "tags": [ - "核心", - "逃生", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "大鱼人", - "大鱼" - ], - "abbr": [], - "base_str": 21, - "str_gain": 3.4, - "base_agi": 17, - "agi_gain": 2.1, - "base_int": 15, - "int_gain": 1.5, - "health": 582, - "mana": 255, - "health_regen": 2.6, - "mana_regen": 0.75, - "armor": 4.83, - "damage_min": 51, - "damage_max": 59, - "move_speed": 300, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 29, - "key": "tidehunter", - "name": "Tidehunter", - "attr": "str", - "name_loc": "潮汐猎人", - "roles": [ - "Initiator", - "Durable", - "Disabler", - "Nuker", - "Carry" - ], - "tags": [ - "核心", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "潮汐", - "西瓜皮" - ], - "abbr": [ - "tide" - ], - "base_str": 26, - "str_gain": 3.7, - "base_agi": 15, - "agi_gain": 1.5, - "base_int": 20, - "int_gain": 1.7, - "health": 692, - "mana": 315, - "health_regen": 2.85, - "mana_regen": 1.5, - "armor": 2.5, - "damage_min": 51, - "damage_max": 57, - "move_speed": 300, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 30, - "key": "witch_doctor", - "name": "Witch Doctor", - "attr": "int", - "name_loc": "巫医", - "roles": [ - "Support", - "Nuker", - "Disabler" - ], - "tags": [ - "辅助", - "爆发", - "控制" - ], - "aliases": [], - "abbr": [ - "wd" - ], - "base_str": 20, - "str_gain": 2.1, - "base_agi": 13, - "agi_gain": 1.4, - "base_int": 22, - "int_gain": 3.1, - "health": 560, - "mana": 339, - "health_regen": 2.25, - "mana_regen": 1.1, - "armor": 2.17, - "damage_min": 48, - "damage_max": 58, - "move_speed": 300, - "attack_range": 550, - "attack_rate": 1.7, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 31, - "key": "lich", - "name": "Lich", - "attr": "int", - "name_loc": "巫妖", - "roles": [ - "Support", - "Nuker" - ], - "tags": [ - "辅助", - "爆发" - ], - "aliases": [], - "abbr": [], - "base_str": 20, - "str_gain": 2.1, - "base_agi": 17, - "agi_gain": 1.7, - "base_int": 28, - "int_gain": 3.4, - "health": 560, - "mana": 411, - "health_regen": 2.5, - "mana_regen": 0.4, - "armor": 1.83, - "damage_min": 52, - "damage_max": 61, - "move_speed": 290, - "attack_range": 550, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 32, - "key": "riki", - "name": "Riki", - "attr": "agi", - "name_loc": "力丸", - "roles": [ - "Carry", - "Escape", - "Disabler" - ], - "tags": [ - "核心", - "逃生", - "控制" - ], - "aliases": [ - "隐刺" - ], - "abbr": [], - "base_str": 18, - "str_gain": 2.0, - "base_agi": 30, - "agi_gain": 2.4, - "base_int": 14, - "int_gain": 1.3, - "health": 516, - "mana": 243, - "health_regen": 4.3, - "mana_regen": 0.7, - "armor": 5.0, - "damage_min": 52, - "damage_max": 56, - "move_speed": 315, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 33, - "key": "enigma", - "name": "Enigma", - "attr": "all", - "name_loc": "谜团", - "roles": [ - "Disabler", - "Initiator", - "Pusher" - ], - "tags": [ - "推进", - "先手", - "控制" - ], - "aliases": [], - "abbr": [], - "base_str": 21, - "str_gain": 2.5, - "base_agi": 14, - "agi_gain": 1.0, - "base_int": 19, - "int_gain": 3.6, - "health": 582, - "mana": 303, - "health_regen": 2.35, - "mana_regen": 1.45, - "armor": 3.33, - "damage_min": 57, - "damage_max": 63, - "move_speed": 280, - "attack_range": 500, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 34, - "key": "tinker", - "name": "Tinker", - "attr": "int", - "name_loc": "修补匠", - "roles": [ - "Carry", - "Nuker", - "Pusher" - ], - "tags": [ - "核心", - "推进", - "爆发" - ], - "aliases": [], - "abbr": [], - "base_str": 19, - "str_gain": 2.3, - "base_agi": 17, - "agi_gain": 1.4, - "base_int": 30, - "int_gain": 3.3, - "health": 538, - "mana": 435, - "health_regen": 2.4, - "mana_regen": 2.0, - "armor": 4.83, - "damage_min": 52, - "damage_max": 58, - "move_speed": 305, - "attack_range": 500, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 35, - "key": "sniper", - "name": "Sniper", - "attr": "agi", - "name_loc": "狙击手", - "roles": [ - "Carry", - "Nuker" - ], - "tags": [ - "核心", - "爆发" - ], - "aliases": [ - "火枪" - ], - "abbr": [], - "base_str": 19, - "str_gain": 2.0, - "base_agi": 27, - "agi_gain": 3.2, - "base_int": 15, - "int_gain": 2.6, - "health": 538, - "mana": 255, - "health_regen": 2.15, - "mana_regen": 0.75, - "armor": 4.5, - "damage_min": 40, - "damage_max": 46, - "move_speed": 285, - "attack_range": 550, - "attack_rate": 1.7, - "projectile_speed": 3000, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 1400, - "turn_rate": 0.7 - }, - { - "id": 36, - "key": "necrolyte", - "name": "Necrophos", - "attr": "int", - "name_loc": "瘟疫法师", - "roles": [ - "Carry", - "Nuker", - "Durable", - "Disabler" - ], - "tags": [ - "核心", - "爆发", - "控制", - "耐久" - ], - "aliases": [], - "abbr": [ - "nec" - ], - "base_str": 18, - "str_gain": 2.3, - "base_agi": 12, - "agi_gain": 1.3, - "base_int": 23, - "int_gain": 2.7, - "health": 516, - "mana": 351, - "health_regen": 2.05, - "mana_regen": 1.15, - "armor": 4.0, - "damage_min": 49, - "damage_max": 53, - "move_speed": 280, - "attack_range": 500, - "attack_rate": 1.7, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 37, - "key": "warlock", - "name": "Warlock", - "attr": "int", - "name_loc": "术士", - "roles": [ - "Support", - "Initiator", - "Disabler" - ], - "tags": [ - "辅助", - "先手", - "控制" - ], - "aliases": [], - "abbr": [], - "base_str": 22, - "str_gain": 2.4, - "base_agi": 10, - "agi_gain": 1.0, - "base_int": 25, - "int_gain": 3.1, - "health": 604, - "mana": 375, - "health_regen": 2.45, - "mana_regen": 1.25, - "armor": 2.67, - "damage_min": 52, - "damage_max": 62, - "move_speed": 300, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 38, - "key": "beastmaster", - "name": "Beastmaster", - "attr": "all", - "name_loc": "兽王", - "roles": [ - "Initiator", - "Disabler", - "Durable", - "Nuker" - ], - "tags": [ - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [], - "abbr": [ - "bm" - ], - "base_str": 25, - "str_gain": 2.9, - "base_agi": 19, - "agi_gain": 2.0, - "base_int": 16, - "int_gain": 1.9, - "health": 670, - "mana": 267, - "health_regen": 2.75, - "mana_regen": 0.8, - "armor": 2.17, - "damage_min": 65, - "damage_max": 69, - "move_speed": 305, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 39, - "key": "queenofpain", - "name": "Queen of Pain", - "attr": "int", - "name_loc": "痛苦女王", - "roles": [ - "Carry", - "Nuker", - "Escape" - ], - "tags": [ - "核心", - "逃生", - "爆发" - ], - "aliases": [ - "女王" - ], - "abbr": [ - "qop" - ], - "base_str": 20, - "str_gain": 2.4, - "base_agi": 22, - "agi_gain": 2.5, - "base_int": 25, - "int_gain": 3.4, - "health": 560, - "mana": 375, - "health_regen": 2.25, - "mana_regen": 1.25, - "armor": 3.67, - "damage_min": 51, - "damage_max": 57, - "move_speed": 290, - "attack_range": 550, - "attack_rate": 1.5, - "projectile_speed": 1500, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 40, - "key": "venomancer", - "name": "Venomancer", - "attr": "all", - "name_loc": "剧毒术士", - "roles": [ - "Support", - "Nuker", - "Initiator", - "Pusher", - "Disabler" - ], - "tags": [ - "辅助", - "推进", - "爆发", - "先手", - "控制" - ], - "aliases": [ - "剧毒" - ], - "abbr": [ - "veno" - ], - "base_str": 19, - "str_gain": 2.1, - "base_agi": 25, - "agi_gain": 2.8, - "base_int": 19, - "int_gain": 1.8, - "health": 538, - "mana": 303, - "health_regen": 2.15, - "mana_regen": 0.95, - "armor": 4.17, - "damage_min": 62, - "damage_max": 65, - "move_speed": 280, - "attack_range": 450, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 41, - "key": "faceless_void", - "name": "Faceless Void", - "attr": "agi", - "name_loc": "虚空假面", - "roles": [ - "Carry", - "Initiator", - "Disabler", - "Escape", - "Durable" - ], - "tags": [ - "核心", - "逃生", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "虚空" - ], - "abbr": [ - "fv" - ], - "base_str": 20, - "str_gain": 2.6, - "base_agi": 24, - "agi_gain": 3.3, - "base_int": 15, - "int_gain": 1.5, - "health": 560, - "mana": 255, - "health_regen": 3.0, - "mana_regen": 0.75, - "armor": 4.0, - "damage_min": 58, - "damage_max": 64, - "move_speed": 300, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 42, - "key": "skeleton_king", - "name": "Wraith King", - "attr": "str", - "name_loc": "冥魂大帝", - "roles": [ - "Carry", - "Support", - "Durable", - "Disabler", - "Initiator" - ], - "tags": [ - "核心", - "辅助", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "骷髅王", - "骷髅" - ], - "abbr": [ - "wk" - ], - "base_str": 22, - "str_gain": 2.8, - "base_agi": 16, - "agi_gain": 1.7, - "base_int": 16, - "int_gain": 1.6, - "health": 604, - "mana": 267, - "health_regen": 2.7, - "mana_regen": 0.8, - "armor": 2.67, - "damage_min": 60, - "damage_max": 62, - "move_speed": 310, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 43, - "key": "death_prophet", - "name": "Death Prophet", - "attr": "all", - "name_loc": "死亡先知", - "roles": [ - "Carry", - "Pusher", - "Nuker", - "Disabler" - ], - "tags": [ - "核心", - "推进", - "爆发", - "控制" - ], - "aliases": [], - "abbr": [ - "dp" - ], - "base_str": 21, - "str_gain": 2.9, - "base_agi": 17, - "agi_gain": 2.0, - "base_int": 24, - "int_gain": 3.0, - "health": 582, - "mana": 363, - "health_regen": 2.35, - "mana_regen": 1.7, - "armor": 3.83, - "damage_min": 65, - "damage_max": 75, - "move_speed": 290, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 1000, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.8 - }, - { - "id": 44, - "key": "phantom_assassin", - "name": "Phantom Assassin", - "attr": "agi", - "name_loc": "幻影刺客", - "roles": [ - "Carry", - "Escape" - ], - "tags": [ - "核心", - "逃生" - ], - "aliases": [ - "幻刺" - ], - "abbr": [ - "pa" - ], - "base_str": 19, - "str_gain": 2.0, - "base_agi": 21, - "agi_gain": 3.4, - "base_int": 15, - "int_gain": 1.7, - "health": 538, - "mana": 255, - "health_regen": 2.9, - "mana_regen": 0.75, - "armor": 4.5, - "damage_min": 56, - "damage_max": 58, - "move_speed": 310, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.8 - }, - { - "id": 45, - "key": "pugna", - "name": "Pugna", - "attr": "int", - "name_loc": "帕格纳", - "roles": [ - "Nuker", - "Pusher" - ], - "tags": [ - "推进", - "爆发" - ], - "aliases": [ - "骨法" - ], - "abbr": [], - "base_str": 19, - "str_gain": 2.0, - "base_agi": 21, - "agi_gain": 1.3, - "base_int": 26, - "int_gain": 5.2, - "health": 538, - "mana": 387, - "health_regen": 2.15, - "mana_regen": 1.8, - "armor": 3.5, - "damage_min": 47, - "damage_max": 54, - "move_speed": 325, - "attack_range": 630, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 46, - "key": "templar_assassin", - "name": "Templar Assassin", - "attr": "agi", - "name_loc": "圣堂刺客", - "roles": [ - "Carry", - "Escape" - ], - "tags": [ - "核心", - "逃生" - ], - "aliases": [ - "圣堂" - ], - "abbr": [ - "ta" - ], - "base_str": 21, - "str_gain": 2.4, - "base_agi": 23, - "agi_gain": 3.4, - "base_int": 20, - "int_gain": 2.0, - "health": 582, - "mana": 315, - "health_regen": 2.1, - "mana_regen": 1.0, - "armor": 3.83, - "damage_min": 51, - "damage_max": 56, - "move_speed": 310, - "attack_range": 250, - "attack_rate": 1.6, - "projectile_speed": 1000, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 47, - "key": "viper", - "name": "Viper", - "attr": "agi", - "name_loc": "冥界亚龙", - "roles": [ - "Carry", - "Durable", - "Initiator", - "Disabler" - ], - "tags": [ - "核心", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "毒龙" - ], - "abbr": [], - "base_str": 21, - "str_gain": 2.4, - "base_agi": 21, - "agi_gain": 2.9, - "base_int": 17, - "int_gain": 2.0, - "health": 582, - "mana": 279, - "health_regen": 2.35, - "mana_regen": 0.85, - "armor": 1.5, - "damage_min": 48, - "damage_max": 50, - "move_speed": 275, - "attack_range": 575, - "attack_rate": 1.7, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 48, - "key": "luna", - "name": "Luna", - "attr": "agi", - "name_loc": "露娜", - "roles": [ - "Carry", - "Nuker", - "Pusher" - ], - "tags": [ - "核心", - "推进", - "爆发" - ], - "aliases": [ - "月骑" - ], - "abbr": [], - "base_str": 21, - "str_gain": 2.2, - "base_agi": 24, - "agi_gain": 3.4, - "base_int": 23, - "int_gain": 1.9, - "health": 582, - "mana": 351, - "health_regen": 2.6, - "mana_regen": 1.15, - "armor": 6.0, - "damage_min": 50, - "damage_max": 56, - "move_speed": 325, - "attack_range": 330, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 49, - "key": "dragon_knight", - "name": "Dragon Knight", - "attr": "str", - "name_loc": "龙骑士", - "roles": [ - "Carry", - "Pusher", - "Durable", - "Disabler", - "Initiator", - "Nuker" - ], - "tags": [ - "核心", - "推进", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "龙骑" - ], - "abbr": [ - "dk" - ], - "base_str": 21, - "str_gain": 3.2, - "base_agi": 14, - "agi_gain": 2.0, - "base_int": 18, - "int_gain": 1.7, - "health": 582, - "mana": 291, - "health_regen": 2.35, - "mana_regen": 0.9, - "armor": 2.33, - "damage_min": 55, - "damage_max": 61, - "move_speed": 310, - "attack_range": 150, - "attack_rate": 1.6, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 50, - "key": "dazzle", - "name": "Dazzle", - "attr": "all", - "name_loc": "戴泽", - "roles": [ - "Support", - "Nuker", - "Disabler" - ], - "tags": [ - "辅助", - "爆发", - "控制" - ], - "aliases": [ - "暗牧" - ], - "abbr": [], - "base_str": 19, - "str_gain": 2.3, - "base_agi": 20, - "agi_gain": 1.7, - "base_int": 25, - "int_gain": 3.5, - "health": 538, - "mana": 375, - "health_regen": 2.15, - "mana_regen": 1.55, - "armor": 3.33, - "damage_min": 64, - "damage_max": 70, - "move_speed": 305, - "attack_range": 575, - "attack_rate": 1.7, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 51, - "key": "rattletrap", - "name": "Clockwerk", - "attr": "str", - "name_loc": "发条技师", - "roles": [ - "Initiator", - "Disabler", - "Durable", - "Nuker" - ], - "tags": [ - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "发条" - ], - "abbr": [ - "cw", - "clock" - ], - "base_str": 26, - "str_gain": 3.2, - "base_agi": 13, - "agi_gain": 2.3, - "base_int": 18, - "int_gain": 1.7, - "health": 692, - "mana": 291, - "health_regen": 3.1, - "mana_regen": 0.9, - "armor": 3.17, - "damage_min": 50, - "damage_max": 52, - "move_speed": 310, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 52, - "key": "leshrac", - "name": "Leshrac", - "attr": "int", - "name_loc": "拉席克", - "roles": [ - "Carry", - "Support", - "Nuker", - "Pusher", - "Disabler" - ], - "tags": [ - "核心", - "辅助", - "推进", - "爆发", - "控制" - ], - "aliases": [ - "老鹿" - ], - "abbr": [], - "base_str": 20, - "str_gain": 2.8, - "base_agi": 17, - "agi_gain": 2.5, - "base_int": 22, - "int_gain": 3.5, - "health": 560, - "mana": 339, - "health_regen": 2.25, - "mana_regen": 1.1, - "armor": 3.83, - "damage_min": 49, - "damage_max": 53, - "move_speed": 325, - "attack_range": 575, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 53, - "key": "furion", - "name": "Nature's Prophet", - "attr": "all", - "name_loc": "自然先知", - "roles": [ - "Carry", - "Pusher", - "Escape", - "Nuker" - ], - "tags": [ - "核心", - "推进", - "逃生", - "爆发" - ], - "aliases": [ - "先知" - ], - "abbr": [ - "np" - ], - "base_str": 19, - "str_gain": 2.6, - "base_agi": 14, - "agi_gain": 2.6, - "base_int": 22, - "int_gain": 3.5, - "health": 538, - "mana": 339, - "health_regen": 2.15, - "mana_regen": 1.1, - "armor": 2.33, - "damage_min": 58, - "damage_max": 64, - "move_speed": 295, - "attack_range": 600, - "attack_rate": 1.5, - "projectile_speed": 1125, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 54, - "key": "life_stealer", - "name": "Lifestealer", - "attr": "str", - "name_loc": "噬魂鬼", - "roles": [ - "Carry", - "Durable", - "Escape", - "Disabler" - ], - "tags": [ - "核心", - "逃生", - "控制", - "耐久" - ], - "aliases": [ - "小狗" - ], - "abbr": [ - "ls" - ], - "base_str": 23, - "str_gain": 2.4, - "base_agi": 15, - "agi_gain": 1.9, - "base_int": 15, - "int_gain": 1.8, - "health": 626, - "mana": 255, - "health_regen": 2.55, - "mana_regen": 0.75, - "armor": 3.5, - "damage_min": 49, - "damage_max": 55, - "move_speed": 320, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 55, - "key": "dark_seer", - "name": "Dark Seer", - "attr": "int", - "name_loc": "黑暗贤者", - "roles": [ - "Initiator", - "Escape", - "Disabler" - ], - "tags": [ - "逃生", - "先手", - "控制" - ], - "aliases": [ - "兔子", - "黑贤" - ], - "abbr": [ - "ds" - ], - "base_str": 20, - "str_gain": 2.6, - "base_agi": 19, - "agi_gain": 1.5, - "base_int": 22, - "int_gain": 2.7, - "health": 560, - "mana": 339, - "health_regen": 3.0, - "mana_regen": 1.1, - "armor": 5.17, - "damage_min": 53, - "damage_max": 59, - "move_speed": 295, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 56, - "key": "clinkz", - "name": "Clinkz", - "attr": "agi", - "name_loc": "克林克兹", - "roles": [ - "Carry", - "Escape", - "Pusher" - ], - "tags": [ - "核心", - "推进", - "逃生" - ], - "aliases": [ - "骨弓", - "小骷髅" - ], - "abbr": [], - "base_str": 18, - "str_gain": 2.0, - "base_agi": 25, - "agi_gain": 2.7, - "base_int": 21, - "int_gain": 2.2, - "health": 516, - "mana": 327, - "health_regen": 2.05, - "mana_regen": 1.05, - "armor": 4.17, - "damage_min": 44, - "damage_max": 50, - "move_speed": 285, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 57, - "key": "omniknight", - "name": "Omniknight", - "attr": "str", - "name_loc": "全能骑士", - "roles": [ - "Support", - "Durable", - "Nuker" - ], - "tags": [ - "辅助", - "爆发", - "耐久" - ], - "aliases": [ - "全能" - ], - "abbr": [ - "omni" - ], - "base_str": 22, - "str_gain": 3.1, - "base_agi": 15, - "agi_gain": 1.7, - "base_int": 16, - "int_gain": 2.1, - "health": 604, - "mana": 267, - "health_regen": 2.45, - "mana_regen": 0.8, - "armor": 4.5, - "damage_min": 53, - "damage_max": 63, - "move_speed": 310, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 58, - "key": "enchantress", - "name": "Enchantress", - "attr": "int", - "name_loc": "魅惑魔女", - "roles": [ - "Support", - "Pusher", - "Durable", - "Disabler" - ], - "tags": [ - "辅助", - "推进", - "控制", - "耐久" - ], - "aliases": [ - "小鹿" - ], - "abbr": [ - "ench" - ], - "base_str": 17, - "str_gain": 2.0, - "base_agi": 19, - "agi_gain": 2.1, - "base_int": 22, - "int_gain": 4.0, - "health": 494, - "mana": 339, - "health_regen": 1.95, - "mana_regen": 1.6, - "armor": 4.17, - "damage_min": 43, - "damage_max": 53, - "move_speed": 320, - "attack_range": 575, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 59, - "key": "huskar", - "name": "Huskar", - "attr": "str", - "name_loc": "哈斯卡", - "roles": [ - "Carry", - "Durable", - "Initiator" - ], - "tags": [ - "核心", - "先手", - "耐久" - ], - "aliases": [ - "神灵" - ], - "abbr": [], - "base_str": 23, - "str_gain": 3.3, - "base_agi": 10, - "agi_gain": 1.5, - "base_int": 18, - "int_gain": 0.0, - "health": 626, - "mana": 291, - "health_regen": 2.55, - "mana_regen": 0.9, - "armor": 2.67, - "damage_min": 44, - "damage_max": 49, - "move_speed": 290, - "attack_range": 400, - "attack_rate": 1.6, - "projectile_speed": 1400, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 60, - "key": "night_stalker", - "name": "Night Stalker", - "attr": "str", - "name_loc": "暗夜魔王", - "roles": [ - "Carry", - "Initiator", - "Durable", - "Disabler", - "Nuker" - ], - "tags": [ - "核心", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "夜魔" - ], - "abbr": [ - "ns" - ], - "base_str": 23, - "str_gain": 3.0, - "base_agi": 18, - "agi_gain": 2.2, - "base_int": 15, - "int_gain": 1.6, - "health": 626, - "mana": 255, - "health_regen": 3.8, - "mana_regen": 1.0, - "armor": 5.0, - "damage_min": 63, - "damage_max": 67, - "move_speed": 295, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 800, - "vision_night": 1800 - }, - { - "id": 61, - "key": "broodmother", - "name": "Broodmother", - "attr": "agi", - "name_loc": "育母蜘蛛", - "roles": [ - "Carry", - "Pusher", - "Escape", - "Nuker" - ], - "tags": [ - "核心", - "推进", - "逃生", - "爆发" - ], - "aliases": [ - "蜘蛛" - ], - "abbr": [ - "brood" - ], - "base_str": 18, - "str_gain": 2.9, - "base_agi": 18, - "agi_gain": 3.4, - "base_int": 18, - "int_gain": 2.0, - "health": 516, - "mana": 291, - "health_regen": 2.05, - "mana_regen": 0.9, - "armor": 2.0, - "damage_min": 45, - "damage_max": 51, - "move_speed": 285, - "attack_range": 175, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 62, - "key": "bounty_hunter", - "name": "Bounty Hunter", - "attr": "agi", - "name_loc": "赏金猎人", - "roles": [ - "Escape", - "Nuker" - ], - "tags": [ - "逃生", - "爆发" - ], - "aliases": [ - "赏金" - ], - "abbr": [ - "bh" - ], - "base_str": 19, - "str_gain": 2.5, - "base_agi": 21, - "agi_gain": 2.6, - "base_int": 20, - "int_gain": 1.9, - "health": 538, - "mana": 315, - "health_regen": 2.65, - "mana_regen": 1.5, - "armor": 8.5, - "damage_min": 54, - "damage_max": 62, - "move_speed": 325, - "attack_range": 150, - "attack_rate": 1.5, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 1000 - }, - { - "id": 63, - "key": "weaver", - "name": "Weaver", - "attr": "agi", - "name_loc": "编织者", - "roles": [ - "Carry", - "Escape" - ], - "tags": [ - "核心", - "逃生" - ], - "aliases": [ - "蚂蚁" - ], - "abbr": [], - "base_str": 16, - "str_gain": 2.0, - "base_agi": 17, - "agi_gain": 3.3, - "base_int": 20, - "int_gain": 2.0, - "health": 472, - "mana": 315, - "health_regen": 2.85, - "mana_regen": 1.5, - "armor": 3.83, - "damage_min": 54, - "damage_max": 60, - "move_speed": 275, - "attack_range": 425, - "attack_rate": 1.8, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 64, - "key": "jakiro", - "name": "Jakiro", - "attr": "int", - "name_loc": "杰奇洛", - "roles": [ - "Support", - "Nuker", - "Pusher", - "Disabler" - ], - "tags": [ - "辅助", - "推进", - "爆发", - "控制" - ], - "aliases": [ - "双头龙" - ], - "abbr": [], - "base_str": 25, - "str_gain": 2.5, - "base_agi": 15, - "agi_gain": 1.2, - "base_int": 25, - "int_gain": 3.0, - "health": 670, - "mana": 375, - "health_regen": 2.75, - "mana_regen": 1.25, - "armor": 3.5, - "damage_min": 52, - "damage_max": 60, - "move_speed": 290, - "attack_range": 400, - "attack_rate": 1.7, - "projectile_speed": 1100, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.8 - }, - { - "id": 65, - "key": "batrider", - "name": "Batrider", - "attr": "all", - "name_loc": "蝙蝠骑士", - "roles": [ - "Initiator", - "Disabler", - "Escape" - ], - "tags": [ - "逃生", - "先手", - "控制" - ], - "aliases": [ - "蝙蝠" - ], - "abbr": [], - "base_str": 23, - "str_gain": 2.9, - "base_agi": 13, - "agi_gain": 2.0, - "base_int": 22, - "int_gain": 2.9, - "health": 626, - "mana": 339, - "health_regen": 3.55, - "mana_regen": 1.1, - "armor": 4.17, - "damage_min": 53, - "damage_max": 57, - "move_speed": 320, - "attack_range": 400, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 66, - "key": "chen", - "name": "Chen", - "attr": "int", - "name_loc": "陈", - "roles": [ - "Support", - "Pusher" - ], - "tags": [ - "辅助", - "推进" - ], - "aliases": [], - "abbr": [], - "base_str": 23, - "str_gain": 2.0, - "base_agi": 15, - "agi_gain": 2.1, - "base_int": 19, - "int_gain": 3.2, - "health": 626, - "mana": 303, - "health_regen": 2.55, - "mana_regen": 1.45, - "armor": 1.5, - "damage_min": 48, - "damage_max": 58, - "move_speed": 305, - "attack_range": 650, - "attack_rate": 1.7, - "projectile_speed": 1100, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 67, - "key": "spectre", - "name": "Spectre", - "attr": "agi", - "name_loc": "幽鬼", - "roles": [ - "Carry", - "Durable", - "Escape" - ], - "tags": [ - "核心", - "逃生", - "耐久" - ], - "aliases": [], - "abbr": [ - "spec" - ], - "base_str": 21, - "str_gain": 2.4, - "base_agi": 26, - "agi_gain": 2.4, - "base_int": 16, - "int_gain": 1.7, - "health": 582, - "mana": 267, - "health_regen": 3.6, - "mana_regen": 0.8, - "armor": 4.33, - "damage_min": 49, - "damage_max": 53, - "move_speed": 295, - "attack_range": 150, - "attack_rate": 1.8, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 68, - "key": "ancient_apparition", - "name": "Ancient Apparition", - "attr": "int", - "name_loc": "远古冰魄", - "roles": [ - "Support", - "Disabler", - "Nuker" - ], - "tags": [ - "辅助", - "爆发", - "控制" - ], - "aliases": [ - "冰魂" - ], - "abbr": [ - "aa" - ], - "base_str": 20, - "str_gain": 1.9, - "base_agi": 20, - "agi_gain": 2.0, - "base_int": 23, - "int_gain": 3.1, - "health": 560, - "mana": 351, - "health_regen": 2.25, - "mana_regen": 1.15, - "armor": 2.33, - "damage_min": 44, - "damage_max": 54, - "move_speed": 285, - "attack_range": 675, - "attack_rate": 1.7, - "projectile_speed": 1250, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 69, - "key": "doom_bringer", - "name": "Doom", - "attr": "str", - "name_loc": "末日使者", - "roles": [ - "Carry", - "Disabler", - "Initiator", - "Durable", - "Nuker" - ], - "tags": [ - "核心", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "末日" - ], - "abbr": [ - "doom" - ], - "base_str": 24, - "str_gain": 3.6, - "base_agi": 15, - "agi_gain": 1.5, - "base_int": 15, - "int_gain": 1.9, - "health": 648, - "mana": 255, - "health_regen": 3.06, - "mana_regen": 0.75, - "armor": 4.5, - "damage_min": 56, - "damage_max": 66, - "move_speed": 290, - "attack_range": 200, - "attack_rate": 1.9, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 70, - "key": "ursa", - "name": "Ursa", - "attr": "agi", - "name_loc": "熊战士", - "roles": [ - "Carry", - "Durable", - "Disabler" - ], - "tags": [ - "核心", - "控制", - "耐久" - ], - "aliases": [ - "拍拍", - "拍拍熊" - ], - "abbr": [], - "base_str": 25, - "str_gain": 2.4, - "base_agi": 18, - "agi_gain": 2.8, - "base_int": 16, - "int_gain": 1.5, - "health": 670, - "mana": 267, - "health_regen": 3.5, - "mana_regen": 0.8, - "armor": 4.0, - "damage_min": 42, - "damage_max": 46, - "move_speed": 310, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 71, - "key": "spirit_breaker", - "name": "Spirit Breaker", - "attr": "str", - "name_loc": "裂魂人", - "roles": [ - "Carry", - "Initiator", - "Disabler", - "Durable", - "Escape" - ], - "tags": [ - "核心", - "逃生", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "白牛" - ], - "abbr": [ - "sb", - "bara" - ], - "base_str": 28, - "str_gain": 3.3, - "base_agi": 17, - "agi_gain": 1.7, - "base_int": 14, - "int_gain": 1.8, - "health": 736, - "mana": 243, - "health_regen": 4.05, - "mana_regen": 1.2, - "armor": 3.83, - "damage_min": 62, - "damage_max": 72, - "move_speed": 295, - "attack_range": 170, - "attack_rate": 1.9, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 72, - "key": "gyrocopter", - "name": "Gyrocopter", - "attr": "agi", - "name_loc": "矮人直升机", - "roles": [ - "Carry", - "Nuker", - "Disabler" - ], - "tags": [ - "核心", - "爆发", - "控制" - ], - "aliases": [ - "飞机" - ], - "abbr": [ - "gyro" - ], - "base_str": 22, - "str_gain": 2.5, - "base_agi": 25, - "agi_gain": 3.4, - "base_int": 21, - "int_gain": 2.4, - "health": 604, - "mana": 327, - "health_regen": 2.45, - "mana_regen": 1.05, - "armor": 5.17, - "damage_min": 43, - "damage_max": 49, - "move_speed": 315, - "attack_range": 365, - "attack_rate": 1.7, - "projectile_speed": 3000, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 73, - "key": "alchemist", - "name": "Alchemist", - "attr": "str", - "name_loc": "炼金术士", - "roles": [ - "Carry", - "Support", - "Durable", - "Disabler", - "Initiator", - "Nuker" - ], - "tags": [ - "核心", - "辅助", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "炼金" - ], - "abbr": [ - "alch" - ], - "base_str": 23, - "str_gain": 2.7, - "base_agi": 22, - "agi_gain": 1.5, - "base_int": 25, - "int_gain": 1.8, - "health": 626, - "mana": 375, - "health_regen": 2.55, - "mana_regen": 1.25, - "armor": 3.67, - "damage_min": 50, - "damage_max": 56, - "move_speed": 295, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 74, - "key": "invoker", - "name": "Invoker", - "attr": "int", - "name_loc": "祈求者", - "roles": [ - "Carry", - "Nuker", - "Disabler", - "Escape", - "Pusher" - ], - "tags": [ - "核心", - "推进", - "逃生", - "爆发", - "控制" - ], - "aliases": [ - "卡尔" - ], - "abbr": [ - "invo" - ], - "base_str": 19, - "str_gain": 2.5, - "base_agi": 14, - "agi_gain": 2.0, - "base_int": 20, - "int_gain": 4.0, - "health": 538, - "mana": 315, - "health_regen": 2.15, - "mana_regen": 1.0, - "armor": 1.33, - "damage_min": 39, - "damage_max": 45, - "move_speed": 285, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 75, - "key": "silencer", - "name": "Silencer", - "attr": "int", - "name_loc": "沉默术士", - "roles": [ - "Carry", - "Support", - "Disabler", - "Initiator", - "Nuker" - ], - "tags": [ - "核心", - "辅助", - "爆发", - "先手", - "控制" - ], - "aliases": [ - "沉默" - ], - "abbr": [], - "base_str": 18, - "str_gain": 2.2, - "base_agi": 22, - "agi_gain": 2.2, - "base_int": 25, - "int_gain": 2.9, - "health": 516, - "mana": 375, - "health_regen": 2.05, - "mana_regen": 1.25, - "armor": 3.67, - "damage_min": 43, - "damage_max": 57, - "move_speed": 300, - "attack_range": 600, - "attack_rate": 1.6, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 76, - "key": "obsidian_destroyer", - "name": "Outworld Destroyer", - "attr": "int", - "name_loc": "殁境神蚀者", - "roles": [ - "Carry", - "Nuker", - "Disabler" - ], - "tags": [ - "核心", - "爆发", - "控制" - ], - "aliases": [ - "黑鸟" - ], - "abbr": [ - "od" - ], - "base_str": 21, - "str_gain": 2.9, - "base_agi": 17, - "agi_gain": 1.6, - "base_int": 30, - "int_gain": 4.2, - "health": 582, - "mana": 435, - "health_regen": 3.1, - "mana_regen": 1.5, - "armor": 2.83, - "damage_min": 49, - "damage_max": 61, - "move_speed": 315, - "attack_range": 450, - "attack_rate": 1.9, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 77, - "key": "lycan", - "name": "Lycan", - "attr": "str", - "name_loc": "狼人", - "roles": [ - "Carry", - "Pusher", - "Durable", - "Escape" - ], - "tags": [ - "核心", - "推进", - "逃生", - "耐久" - ], - "aliases": [], - "abbr": [], - "base_str": 28, - "str_gain": 3.4, - "base_agi": 18, - "agi_gain": 1.7, - "base_int": 23, - "int_gain": 1.7, - "health": 736, - "mana": 351, - "health_regen": 3.05, - "mana_regen": 1.15, - "armor": 3.0, - "damage_min": 52, - "damage_max": 57, - "move_speed": 305, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 78, - "key": "brewmaster", - "name": "Brewmaster", - "attr": "all", - "name_loc": "酒仙", - "roles": [ - "Carry", - "Initiator", - "Durable", - "Disabler", - "Nuker" - ], - "tags": [ - "核心", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "熊猫", - "大熊猫" - ], - "abbr": [ - "brew" - ], - "base_str": 24, - "str_gain": 3.2, - "base_agi": 19, - "agi_gain": 2.0, - "base_int": 17, - "int_gain": 1.9, - "health": 648, - "mana": 279, - "health_regen": 2.9, - "mana_regen": 0.85, - "armor": 4.17, - "damage_min": 68, - "damage_max": 75, - "move_speed": 310, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 79, - "key": "shadow_demon", - "name": "Shadow Demon", - "attr": "int", - "name_loc": "暗影恶魔", - "roles": [ - "Support", - "Disabler", - "Initiator", - "Nuker" - ], - "tags": [ - "辅助", - "爆发", - "先手", - "控制", - "幻象" - ], - "aliases": [ - "毒狗" - ], - "abbr": [ - "sd" - ], - "base_str": 23, - "str_gain": 2.6, - "base_agi": 18, - "agi_gain": 2.2, - "base_int": 22, - "int_gain": 3.6, - "health": 626, - "mana": 339, - "health_regen": 2.55, - "mana_regen": 1.1, - "armor": 3.0, - "damage_min": 51, - "damage_max": 55, - "move_speed": 290, - "attack_range": 500, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 80, - "key": "lone_druid", - "name": "Lone Druid", - "attr": "agi", - "name_loc": "独行德鲁伊", - "roles": [ - "Carry", - "Pusher", - "Durable" - ], - "tags": [ - "核心", - "推进", - "耐久" - ], - "aliases": [ - "德鲁伊", - "熊德" - ], - "abbr": [ - "ld" - ], - "base_str": 20, - "str_gain": 2.5, - "base_agi": 22, - "agi_gain": 2.8, - "base_int": 13, - "int_gain": 1.4, - "health": 560, - "mana": 231, - "health_regen": 2.25, - "mana_regen": 0.65, - "armor": 3.67, - "damage_min": 44, - "damage_max": 48, - "move_speed": 300, - "attack_range": 550, - "attack_rate": 1.6, - "projectile_speed": 1125, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 81, - "key": "chaos_knight", - "name": "Chaos Knight", - "attr": "str", - "name_loc": "混沌骑士", - "roles": [ - "Carry", - "Disabler", - "Durable", - "Pusher", - "Initiator" - ], - "tags": [ - "核心", - "推进", - "先手", - "控制", - "耐久", - "幻象" - ], - "aliases": [ - "混沌" - ], - "abbr": [ - "ck" - ], - "base_str": 24, - "str_gain": 3.1, - "base_agi": 18, - "agi_gain": 1.8, - "base_int": 18, - "int_gain": 1.2, - "health": 648, - "mana": 291, - "health_regen": 2.65, - "mana_regen": 0.9, - "armor": 5.0, - "damage_min": 53, - "damage_max": 73, - "move_speed": 325, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 82, - "key": "meepo", - "name": "Meepo", - "attr": "agi", - "name_loc": "米波", - "roles": [ - "Carry", - "Escape", - "Nuker", - "Disabler", - "Initiator", - "Pusher" - ], - "tags": [ - "核心", - "推进", - "逃生", - "爆发", - "先手", - "控制" - ], - "aliases": [], - "abbr": [], - "base_str": 24, - "str_gain": 2.2, - "base_agi": 17, - "agi_gain": 2.1, - "base_int": 18, - "int_gain": 1.9, - "health": 648, - "mana": 291, - "health_regen": 2.65, - "mana_regen": 1.15, - "armor": 5.83, - "damage_min": 46, - "damage_max": 52, - "move_speed": 315, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 83, - "key": "treant", - "name": "Treant Protector", - "attr": "str", - "name_loc": "树精卫士", - "roles": [ - "Support", - "Initiator", - "Durable", - "Disabler", - "Escape" - ], - "tags": [ - "辅助", - "逃生", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "大树" - ], - "abbr": [ - "treant" - ], - "base_str": 25, - "str_gain": 3.4, - "base_agi": 15, - "agi_gain": 1.8, - "base_int": 20, - "int_gain": 1.8, - "health": 670, - "mana": 315, - "health_regen": 2.75, - "mana_regen": 1.25, - "armor": 3.5, - "damage_min": 85, - "damage_max": 93, - "move_speed": 280, - "attack_range": 150, - "attack_rate": 1.9, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 84, - "key": "ogre_magi", - "name": "Ogre Magi", - "attr": "str", - "name_loc": "食人魔魔法师", - "roles": [ - "Support", - "Nuker", - "Disabler", - "Durable", - "Initiator" - ], - "tags": [ - "辅助", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "蓝胖" - ], - "abbr": [ - "ogre" - ], - "base_str": 25, - "str_gain": 4.2, - "base_agi": 14, - "agi_gain": 1.5, - "base_int": 0, - "int_gain": 0.0, - "health": 670, - "mana": 120, - "health_regen": 4.0, - "mana_regen": 0.0, - "armor": 6.33, - "damage_min": 70, - "damage_max": 76, - "move_speed": 290, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 85, - "key": "undying", - "name": "Undying", - "attr": "str", - "name_loc": "不朽尸王", - "roles": [ - "Support", - "Durable", - "Disabler", - "Nuker" - ], - "tags": [ - "辅助", - "爆发", - "控制", - "耐久" - ], - "aliases": [ - "尸王" - ], - "abbr": [ - "ud" - ], - "base_str": 22, - "str_gain": 2.4, - "base_agi": 13, - "agi_gain": 1.2, - "base_int": 27, - "int_gain": 2.8, - "health": 604, - "mana": 399, - "health_regen": 1.95, - "mana_regen": 1.6, - "armor": 2.17, - "damage_min": 55, - "damage_max": 63, - "move_speed": 300, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 86, - "key": "rubick", - "name": "Rubick", - "attr": "int", - "name_loc": "拉比克", - "roles": [ - "Support", - "Disabler", - "Nuker" - ], - "tags": [ - "辅助", - "爆发", - "控制" - ], - "aliases": [ - "抄袭", - "魔术师" - ], - "abbr": [], - "base_str": 21, - "str_gain": 2.0, - "base_agi": 23, - "agi_gain": 2.5, - "base_int": 24, - "int_gain": 3.7, - "health": 582, - "mana": 363, - "health_regen": 2.35, - "mana_regen": 1.7, - "armor": 4.83, - "damage_min": 49, - "damage_max": 55, - "move_speed": 290, - "attack_range": 550, - "attack_rate": 1.7, - "projectile_speed": 1125, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 87, - "key": "disruptor", - "name": "Disruptor", - "attr": "int", - "name_loc": "干扰者", - "roles": [ - "Support", - "Disabler", - "Nuker", - "Initiator" - ], - "tags": [ - "辅助", - "爆发", - "先手", - "控制" - ], - "aliases": [ - "萨尔" - ], - "abbr": [], - "base_str": 21, - "str_gain": 2.4, - "base_agi": 15, - "agi_gain": 1.4, - "base_int": 20, - "int_gain": 2.9, - "health": 582, - "mana": 315, - "health_regen": 2.35, - "mana_regen": 1.0, - "armor": 2.5, - "damage_min": 47, - "damage_max": 51, - "move_speed": 295, - "attack_range": 625, - "attack_rate": 1.7, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 88, - "key": "nyx_assassin", - "name": "Nyx Assassin", - "attr": "all", - "name_loc": "司夜刺客", - "roles": [ - "Disabler", - "Nuker", - "Initiator", - "Escape" - ], - "tags": [ - "逃生", - "爆发", - "先手", - "控制" - ], - "aliases": [ - "小强" - ], - "abbr": [ - "nyx" - ], - "base_str": 17, - "str_gain": 2.7, - "base_agi": 18, - "agi_gain": 2.9, - "base_int": 21, - "int_gain": 2.6, - "health": 494, - "mana": 327, - "health_regen": 3.7, - "mana_regen": 1.05, - "armor": 4.0, - "damage_min": 65, - "damage_max": 69, - "move_speed": 310, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 89, - "key": "naga_siren", - "name": "Naga Siren", - "attr": "agi", - "name_loc": "娜迦海妖", - "roles": [ - "Carry", - "Support", - "Pusher", - "Disabler", - "Initiator", - "Escape" - ], - "tags": [ - "核心", - "辅助", - "推进", - "逃生", - "先手", - "控制", - "幻象" - ], - "aliases": [ - "小娜迦" - ], - "abbr": [ - "naga" - ], - "base_str": 21, - "str_gain": 2.4, - "base_agi": 22, - "agi_gain": 3.4, - "base_int": 19, - "int_gain": 2.0, - "health": 582, - "mana": 303, - "health_regen": 3.1, - "mana_regen": 0.95, - "armor": 4.67, - "damage_min": 45, - "damage_max": 47, - "move_speed": 330, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 90, - "key": "keeper_of_the_light", - "name": "Keeper of the Light", - "attr": "int", - "name_loc": "光之守卫", - "roles": [ - "Support", - "Nuker", - "Disabler" - ], - "tags": [ - "辅助", - "爆发", - "控制" - ], - "aliases": [ - "光法" - ], - "abbr": [ - "kotl" - ], - "base_str": 18, - "str_gain": 2.3, - "base_agi": 15, - "agi_gain": 1.6, - "base_int": 24, - "int_gain": 3.3, - "health": 516, - "mana": 363, - "health_regen": 2.05, - "mana_regen": 1.2, - "armor": 2.5, - "damage_min": 44, - "damage_max": 51, - "move_speed": 315, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 91, - "key": "wisp", - "name": "Io", - "attr": "all", - "name_loc": "艾欧", - "roles": [ - "Support", - "Escape", - "Nuker" - ], - "tags": [ - "辅助", - "逃生", - "爆发" - ], - "aliases": [ - "小精灵" - ], - "abbr": [ - "io" - ], - "base_str": 19, - "str_gain": 3.0, - "base_agi": 14, - "agi_gain": 1.6, - "base_int": 21, - "int_gain": 1.7, - "health": 538, - "mana": 327, - "health_regen": 2.15, - "mana_regen": 1.05, - "armor": 3.33, - "damage_min": 59, - "damage_max": 65, - "move_speed": 320, - "attack_range": 500, - "attack_rate": 1.7, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 92, - "key": "visage", - "name": "Visage", - "attr": "all", - "name_loc": "维萨吉", - "roles": [ - "Support", - "Nuker", - "Durable", - "Disabler", - "Pusher" - ], - "tags": [ - "辅助", - "推进", - "爆发", - "控制", - "耐久" - ], - "aliases": [ - "死灵龙" - ], - "abbr": [], - "base_str": 22, - "str_gain": 2.8, - "base_agi": 11, - "agi_gain": 1.3, - "base_int": 22, - "int_gain": 2.9, - "health": 604, - "mana": 339, - "health_regen": 2.45, - "mana_regen": 1.1, - "armor": 1.83, - "damage_min": 60, - "damage_max": 70, - "move_speed": 285, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 1400, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 93, - "key": "slark", - "name": "Slark", - "attr": "agi", - "name_loc": "斯拉克", - "roles": [ - "Carry", - "Escape", - "Disabler", - "Nuker" - ], - "tags": [ - "核心", - "逃生", - "爆发", - "控制" - ], - "aliases": [ - "小鱼人", - "小鱼" - ], - "abbr": [], - "base_str": 20, - "str_gain": 2.1, - "base_agi": 21, - "agi_gain": 1.5, - "base_int": 16, - "int_gain": 1.9, - "health": 560, - "mana": 267, - "health_regen": 2.25, - "mana_regen": 0.8, - "armor": 3.5, - "damage_min": 58, - "damage_max": 64, - "move_speed": 300, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 1800, - "turn_rate": 0.7 - }, - { - "id": 94, - "key": "medusa", - "name": "Medusa", - "attr": "agi", - "name_loc": "美杜莎", - "roles": [ - "Carry", - "Disabler", - "Durable" - ], - "tags": [ - "核心", - "控制", - "耐久" - ], - "aliases": [ - "一姐", - "大娜迦" - ], - "abbr": [ - "medi" - ], - "base_str": 0, - "str_gain": 0.0, - "base_agi": 22, - "agi_gain": 3.6, - "base_int": 25, - "int_gain": 3.6, - "health": 120, - "mana": 375, - "health_regen": 0.25, - "mana_regen": 1.25, - "armor": 2.67, - "damage_min": 48, - "damage_max": 54, - "move_speed": 275, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 95, - "key": "troll_warlord", - "name": "Troll Warlord", - "attr": "agi", - "name_loc": "巨魔战将", - "roles": [ - "Carry", - "Pusher", - "Disabler", - "Durable" - ], - "tags": [ - "核心", - "推进", - "控制", - "耐久" - ], - "aliases": [ - "巨魔" - ], - "abbr": [ - "troll" - ], - "base_str": 21, - "str_gain": 2.5, - "base_agi": 23, - "agi_gain": 3.3, - "base_int": 13, - "int_gain": 1.0, - "health": 582, - "mana": 231, - "health_regen": 2.35, - "mana_regen": 0.65, - "armor": 2.83, - "damage_min": 50, - "damage_max": 58, - "move_speed": 290, - "attack_range": 500, - "attack_rate": 1.7, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 96, - "key": "centaur", - "name": "Centaur Warrunner", - "attr": "str", - "name_loc": "半人马战行者", - "roles": [ - "Durable", - "Initiator", - "Disabler", - "Nuker", - "Escape" - ], - "tags": [ - "逃生", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "人马", - "小牛" - ], - "abbr": [ - "cent" - ], - "base_str": 27, - "str_gain": 4.2, - "base_agi": 15, - "agi_gain": 1.0, - "base_int": 15, - "int_gain": 1.6, - "health": 714, - "mana": 255, - "health_regen": 5.7, - "mana_regen": 0.75, - "armor": 0.5, - "damage_min": 63, - "damage_max": 65, - "move_speed": 300, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 97, - "key": "magnataur", - "name": "Magnus", - "attr": "all", - "name_loc": "马格纳斯", - "roles": [ - "Initiator", - "Disabler", - "Nuker", - "Escape" - ], - "tags": [ - "逃生", - "爆发", - "先手", - "控制" - ], - "aliases": [ - "猛犸" - ], - "abbr": [ - "mag" - ], - "base_str": 23, - "str_gain": 3.1, - "base_agi": 14, - "agi_gain": 2.0, - "base_int": 20, - "int_gain": 2.1, - "health": 626, - "mana": 315, - "health_regen": 2.8, - "mana_regen": 1.5, - "armor": 3.33, - "damage_min": 71, - "damage_max": 79, - "move_speed": 305, - "attack_range": 200, - "attack_rate": 1.8, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.8 - }, - { - "id": 98, - "key": "shredder", - "name": "Timbersaw", - "attr": "str", - "name_loc": "伐木机", - "roles": [ - "Nuker", - "Durable", - "Escape" - ], - "tags": [ - "逃生", - "爆发", - "耐久" - ], - "aliases": [], - "abbr": [ - "timber" - ], - "base_str": 23, - "str_gain": 3.6, - "base_agi": 16, - "agi_gain": 1.6, - "base_int": 23, - "int_gain": 2.7, - "health": 626, - "mana": 351, - "health_regen": 2.55, - "mana_regen": 1.65, - "armor": 2.67, - "damage_min": 46, - "damage_max": 50, - "move_speed": 280, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 99, - "key": "bristleback", - "name": "Bristleback", - "attr": "str", - "name_loc": "钢背兽", - "roles": [ - "Carry", - "Durable", - "Initiator", - "Nuker" - ], - "tags": [ - "核心", - "爆发", - "先手", - "耐久" - ], - "aliases": [ - "钢背", - "刚被" - ], - "abbr": [ - "bb" - ], - "base_str": 22, - "str_gain": 2.8, - "base_agi": 17, - "agi_gain": 1.8, - "base_int": 14, - "int_gain": 2.8, - "health": 604, - "mana": 243, - "health_regen": 3.2, - "mana_regen": 1.1, - "armor": 3.83, - "damage_min": 53, - "damage_max": 59, - "move_speed": 295, - "attack_range": 150, - "attack_rate": 1.8, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 100, - "key": "tusk", - "name": "Tusk", - "attr": "str", - "name_loc": "巨牙海民", - "roles": [ - "Initiator", - "Disabler", - "Nuker" - ], - "tags": [ - "爆发", - "先手", - "控制" - ], - "aliases": [ - "海民" - ], - "abbr": [], - "base_str": 23, - "str_gain": 3.9, - "base_agi": 23, - "agi_gain": 1.9, - "base_int": 18, - "int_gain": 1.7, - "health": 626, - "mana": 291, - "health_regen": 3.3, - "mana_regen": 1.4, - "armor": 4.83, - "damage_min": 50, - "damage_max": 54, - "move_speed": 310, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 101, - "key": "skywrath_mage", - "name": "Skywrath Mage", - "attr": "int", - "name_loc": "天怒法师", - "roles": [ - "Support", - "Nuker", - "Disabler" - ], - "tags": [ - "辅助", - "爆发", - "控制" - ], - "aliases": [ - "天怒" - ], - "abbr": [ - "sky" - ], - "base_str": 21, - "str_gain": 2.0, - "base_agi": 13, - "agi_gain": 0.8, - "base_int": 25, - "int_gain": 4.1, - "health": 582, - "mana": 375, - "health_regen": 2.35, - "mana_regen": 1.5, - "armor": 0.17, - "damage_min": 41, - "damage_max": 51, - "move_speed": 325, - "attack_range": 625, - "attack_rate": 1.7, - "projectile_speed": 1000, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 102, - "key": "abaddon", - "name": "Abaddon", - "attr": "all", - "name_loc": "亚巴顿", - "roles": [ - "Support", - "Carry", - "Durable" - ], - "tags": [ - "核心", - "辅助", - "耐久" - ], - "aliases": [], - "abbr": [ - "aba" - ], - "base_str": 21, - "str_gain": 2.6, - "base_agi": 22, - "agi_gain": 1.5, - "base_int": 18, - "int_gain": 2.0, - "health": 582, - "mana": 291, - "health_regen": 3.1, - "mana_regen": 0.9, - "armor": 2.67, - "damage_min": 65, - "damage_max": 75, - "move_speed": 325, - "attack_range": 150, - "attack_rate": 1.5, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.6 - }, - { - "id": 103, - "key": "elder_titan", - "name": "Elder Titan", - "attr": "str", - "name_loc": "上古巨神", - "roles": [ - "Initiator", - "Disabler", - "Nuker", - "Durable" - ], - "tags": [ - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "大牛" - ], - "abbr": [ - "et" - ], - "base_str": 26, - "str_gain": 3.0, - "base_agi": 16, - "agi_gain": 1.8, - "base_int": 23, - "int_gain": 1.6, - "health": 692, - "mana": 351, - "health_regen": 3.35, - "mana_regen": 1.4, - "armor": 2.67, - "damage_min": 49, - "damage_max": 59, - "move_speed": 305, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 104, - "key": "legion_commander", - "name": "Legion Commander", - "attr": "str", - "name_loc": "军团指挥官", - "roles": [ - "Carry", - "Disabler", - "Initiator", - "Durable", - "Nuker" - ], - "tags": [ - "核心", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "军团" - ], - "abbr": [ - "lc" - ], - "base_str": 24, - "str_gain": 3.1, - "base_agi": 18, - "agi_gain": 1.7, - "base_int": 20, - "int_gain": 2.2, - "health": 648, - "mana": 315, - "health_regen": 2.65, - "mana_regen": 1.0, - "armor": 2.0, - "damage_min": 57, - "damage_max": 61, - "move_speed": 325, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 105, - "key": "techies", - "name": "Techies", - "attr": "all", - "name_loc": "工程师", - "roles": [ - "Nuker", - "Disabler" - ], - "tags": [ - "爆发", - "控制" - ], - "aliases": [ - "炸弹人" - ], - "abbr": [], - "base_str": 19, - "str_gain": 2.6, - "base_agi": 16, - "agi_gain": 1.8, - "base_int": 25, - "int_gain": 3.0, - "health": 538, - "mana": 375, - "health_regen": 2.9, - "mana_regen": 2.25, - "armor": 4.67, - "damage_min": 62, - "damage_max": 64, - "move_speed": 310, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 106, - "key": "ember_spirit", - "name": "Ember Spirit", - "attr": "agi", - "name_loc": "灰烬之灵", - "roles": [ - "Carry", - "Escape", - "Nuker", - "Disabler", - "Initiator" - ], - "tags": [ - "核心", - "逃生", - "爆发", - "先手", - "控制" - ], - "aliases": [ - "火猫" - ], - "abbr": [ - "ember" - ], - "base_str": 22, - "str_gain": 2.5, - "base_agi": 22, - "agi_gain": 3.2, - "base_int": 20, - "int_gain": 2.2, - "health": 604, - "mana": 315, - "health_regen": 2.7, - "mana_regen": 1.0, - "armor": 3.67, - "damage_min": 55, - "damage_max": 59, - "move_speed": 300, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.8 - }, - { - "id": 107, - "key": "earth_spirit", - "name": "Earth Spirit", - "attr": "str", - "name_loc": "大地之灵", - "roles": [ - "Nuker", - "Escape", - "Disabler", - "Initiator", - "Durable" - ], - "tags": [ - "逃生", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "土猫" - ], - "abbr": [ - "earth" - ], - "base_str": 22, - "str_gain": 3.8, - "base_agi": 17, - "agi_gain": 2.4, - "base_int": 18, - "int_gain": 2.1, - "health": 604, - "mana": 291, - "health_regen": 3.2, - "mana_regen": 0.9, - "armor": 2.83, - "damage_min": 47, - "damage_max": 51, - "move_speed": 290, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 108, - "key": "abyssal_underlord", - "name": "Underlord", - "attr": "str", - "name_loc": "孽主", - "roles": [ - "Support", - "Nuker", - "Disabler", - "Durable", - "Escape" - ], - "tags": [ - "辅助", - "逃生", - "爆发", - "控制", - "耐久" - ], - "aliases": [ - "大屁股" - ], - "abbr": [ - "underlord" - ], - "base_str": 25, - "str_gain": 3.2, - "base_agi": 12, - "agi_gain": 1.6, - "base_int": 18, - "int_gain": 2.3, - "health": 670, - "mana": 291, - "health_regen": 2.75, - "mana_regen": 0.9, - "armor": 5.0, - "damage_min": 62, - "damage_max": 68, - "move_speed": 290, - "attack_range": 200, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 109, - "key": "terrorblade", - "name": "Terrorblade", - "attr": "agi", - "name_loc": "恐怖利刃", - "roles": [ - "Carry", - "Pusher", - "Nuker" - ], - "tags": [ - "核心", - "推进", - "爆发", - "幻象" - ], - "aliases": [], - "abbr": [ - "tb" - ], - "base_str": 18, - "str_gain": 2.0, - "base_agi": 23, - "agi_gain": 4.0, - "base_int": 19, - "int_gain": 1.6, - "health": 516, - "mana": 303, - "health_regen": 2.3, - "mana_regen": 0.95, - "armor": 9.83, - "damage_min": 49, - "damage_max": 55, - "move_speed": 315, - "attack_range": 150, - "attack_rate": 1.5, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.8 - }, - { - "id": 110, - "key": "phoenix", - "name": "Phoenix", - "attr": "str", - "name_loc": "凤凰", - "roles": [ - "Support", - "Nuker", - "Initiator", - "Escape", - "Disabler" - ], - "tags": [ - "辅助", - "逃生", - "爆发", - "先手", - "控制" - ], - "aliases": [], - "abbr": [], - "base_str": 23, - "str_gain": 3.3, - "base_agi": 12, - "agi_gain": 1.5, - "base_int": 16, - "int_gain": 1.8, - "health": 626, - "mana": 267, - "health_regen": 4.3, - "mana_regen": 0.8, - "armor": 1.0, - "damage_min": 44, - "damage_max": 54, - "move_speed": 280, - "attack_range": 525, - "attack_rate": 1.7, - "projectile_speed": 1100, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 111, - "key": "oracle", - "name": "Oracle", - "attr": "int", - "name_loc": "神谕者", - "roles": [ - "Support", - "Nuker", - "Disabler", - "Escape" - ], - "tags": [ - "辅助", - "逃生", - "爆发", - "控制" - ], - "aliases": [], - "abbr": [], - "base_str": 20, - "str_gain": 2.2, - "base_agi": 15, - "agi_gain": 1.7, - "base_int": 26, - "int_gain": 3.6, - "health": 560, - "mana": 387, - "health_regen": 2.25, - "mana_regen": 1.8, - "armor": 3.5, - "damage_min": 39, - "damage_max": 45, - "move_speed": 295, - "attack_range": 625, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 112, - "key": "winter_wyvern", - "name": "Winter Wyvern", - "attr": "int", - "name_loc": "寒冬飞龙", - "roles": [ - "Support", - "Disabler", - "Nuker" - ], - "tags": [ - "辅助", - "爆发", - "控制" - ], - "aliases": [ - "冰龙" - ], - "abbr": [ - "ww" - ], - "base_str": 22, - "str_gain": 2.5, - "base_agi": 16, - "agi_gain": 1.7, - "base_int": 26, - "int_gain": 3.6, - "health": 604, - "mana": 387, - "health_regen": 2.45, - "mana_regen": 1.8, - "armor": 3.67, - "damage_min": 42, - "damage_max": 49, - "move_speed": 285, - "attack_range": 450, - "attack_rate": 1.7, - "projectile_speed": 700, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 1200 - }, - { - "id": 113, - "key": "arc_warden", - "name": "Arc Warden", - "attr": "all", - "name_loc": "天穹守望者", - "roles": [ - "Carry", - "Escape", - "Nuker" - ], - "tags": [ - "核心", - "逃生", - "爆发" - ], - "aliases": [ - "电狗" - ], - "abbr": [ - "aw" - ], - "base_str": 20, - "str_gain": 2.2, - "base_agi": 20, - "agi_gain": 2.7, - "base_int": 24, - "int_gain": 2.6, - "health": 560, - "mana": 363, - "health_regen": 2.25, - "mana_regen": 1.2, - "armor": 3.33, - "damage_min": 68, - "damage_max": 74, - "move_speed": 300, - "attack_range": 625, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 114, - "key": "monkey_king", - "name": "Monkey King", - "attr": "agi", - "name_loc": "齐天大圣", - "roles": [ - "Carry", - "Escape", - "Disabler", - "Initiator" - ], - "tags": [ - "核心", - "逃生", - "先手", - "控制" - ], - "aliases": [ - "大圣", - "猴哥" - ], - "abbr": [ - "mk" - ], - "base_str": 19, - "str_gain": 2.8, - "base_agi": 24, - "agi_gain": 3.7, - "base_int": 20, - "int_gain": 1.8, - "health": 538, - "mana": 315, - "health_regen": 2.9, - "mana_regen": 1.0, - "armor": 5.0, - "damage_min": 53, - "damage_max": 57, - "move_speed": 300, - "attack_range": 300, - "attack_rate": 1.6, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 119, - "key": "dark_willow", - "name": "Dark Willow", - "attr": "int", - "name_loc": "邪影芳灵", - "roles": [ - "Support", - "Nuker", - "Disabler", - "Escape" - ], - "tags": [ - "辅助", - "逃生", - "爆发", - "控制" - ], - "aliases": [ - "小仙女" - ], - "abbr": [ - "willow" - ], - "base_str": 20, - "str_gain": 2.0, - "base_agi": 18, - "agi_gain": 1.6, - "base_int": 21, - "int_gain": 3.5, - "health": 560, - "mana": 327, - "health_regen": 2.75, - "mana_regen": 1.05, - "armor": 3.0, - "damage_min": 48, - "damage_max": 56, - "move_speed": 290, - "attack_range": 475, - "attack_rate": 1.5, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 120, - "key": "pangolier", - "name": "Pangolier", - "attr": "all", - "name_loc": "石鳞剑士", - "roles": [ - "Carry", - "Nuker", - "Disabler", - "Durable", - "Escape", - "Initiator" - ], - "tags": [ - "核心", - "逃生", - "爆发", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "滚滚" - ], - "abbr": [ - "pango" - ], - "base_str": 20, - "str_gain": 2.7, - "base_agi": 18, - "agi_gain": 3.2, - "base_int": 16, - "int_gain": 2.5, - "health": 560, - "mana": 267, - "health_regen": 3.25, - "mana_regen": 0.8, - "armor": 5.0, - "damage_min": 64, - "damage_max": 70, - "move_speed": 300, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 121, - "key": "grimstroke", - "name": "Grimstroke", - "attr": "int", - "name_loc": "天涯墨客", - "roles": [ - "Support", - "Nuker", - "Disabler", - "Escape" - ], - "tags": [ - "辅助", - "逃生", - "爆发", - "控制" - ], - "aliases": [ - "墨客" - ], - "abbr": [ - "grim" - ], - "base_str": 21, - "str_gain": 2.4, - "base_agi": 18, - "agi_gain": 1.9, - "base_int": 25, - "int_gain": 3.8, - "health": 582, - "mana": 375, - "health_regen": 2.35, - "mana_regen": 1.25, - "armor": 3.0, - "damage_min": 47, - "damage_max": 51, - "move_speed": 290, - "attack_range": 600, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 123, - "key": "hoodwink", - "name": "Hoodwink", - "attr": "agi", - "name_loc": "森海飞霞", - "roles": [ - "Support", - "Nuker", - "Escape", - "Disabler" - ], - "tags": [ - "辅助", - "逃生", - "爆发", - "控制" - ], - "aliases": [ - "小松鼠", - "松鼠" - ], - "abbr": [ - "hw" - ], - "base_str": 17, - "str_gain": 2.0, - "base_agi": 25, - "agi_gain": 3.6, - "base_int": 21, - "int_gain": 2.9, - "health": 494, - "mana": 327, - "health_regen": 1.95, - "mana_regen": 1.05, - "armor": 4.17, - "damage_min": 47, - "damage_max": 54, - "move_speed": 315, - "attack_range": 575, - "attack_rate": 2.0, - "projectile_speed": 1800, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 126, - "key": "void_spirit", - "name": "Void Spirit", - "attr": "all", - "name_loc": "虚无之灵", - "roles": [ - "Carry", - "Escape", - "Nuker", - "Disabler" - ], - "tags": [ - "核心", - "逃生", - "爆发", - "控制" - ], - "aliases": [ - "紫猫" - ], - "abbr": [ - "void" - ], - "base_str": 22, - "str_gain": 2.6, - "base_agi": 21, - "agi_gain": 2.2, - "base_int": 24, - "int_gain": 3.1, - "health": 604, - "mana": 363, - "health_regen": 2.45, - "mana_regen": 1.8, - "armor": 2.5, - "damage_min": 65, - "damage_max": 69, - "move_speed": 290, - "attack_range": 200, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 128, - "key": "snapfire", - "name": "Snapfire", - "attr": "all", - "name_loc": "电炎绝手", - "roles": [ - "Support", - "Nuker", - "Disabler", - "Escape" - ], - "tags": [ - "辅助", - "逃生", - "爆发", - "控制" - ], - "aliases": [ - "老奶奶" - ], - "abbr": [ - "snap" - ], - "base_str": 21, - "str_gain": 3.5, - "base_agi": 16, - "agi_gain": 1.9, - "base_int": 21, - "int_gain": 2.2, - "health": 582, - "mana": 327, - "health_regen": 2.85, - "mana_regen": 1.05, - "armor": 3.67, - "damage_min": 66, - "damage_max": 72, - "move_speed": 300, - "attack_range": 400, - "attack_rate": 1.8, - "projectile_speed": 1800, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800 - }, - { - "id": 129, - "key": "mars", - "name": "Mars", - "attr": "str", - "name_loc": "玛尔斯", - "roles": [ - "Carry", - "Initiator", - "Disabler", - "Durable" - ], - "tags": [ - "核心", - "先手", - "控制", - "耐久" - ], - "aliases": [ - "马尔斯", - "战神" - ], - "abbr": [], - "base_str": 23, - "str_gain": 3.7, - "base_agi": 18, - "agi_gain": 1.7, - "base_int": 21, - "int_gain": 2.2, - "health": 626, - "mana": 327, - "health_regen": 2.55, - "mana_regen": 1.55, - "armor": 2.0, - "damage_min": 49, - "damage_max": 57, - "move_speed": 310, - "attack_range": 250, - "attack_rate": 1.8, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.8 - }, - { - "id": 131, - "key": "ringmaster", - "name": "Ringmaster", - "attr": "int", - "name_loc": "百戏大王", - "roles": [ - "Support", - "Nuker", - "Escape", - "Disabler" - ], - "tags": [ - "辅助", - "逃生", - "爆发", - "控制" - ], - "aliases": [ - "百戏" - ], - "abbr": [ - "ring" - ], - "base_str": 21, - "str_gain": 2.1, - "base_agi": 11, - "agi_gain": 1.6, - "base_int": 25, - "int_gain": 3.0, - "health": 582, - "mana": 375, - "health_regen": 2.35, - "mana_regen": 1.25, - "armor": 2.83, - "damage_min": 48, - "damage_max": 58, - "move_speed": 310, - "attack_range": 575, - "attack_rate": 1.7, - "projectile_speed": 1200, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.6 - }, - { - "id": 135, - "key": "dawnbreaker", - "name": "Dawnbreaker", - "attr": "str", - "name_loc": "破晓辰星", - "roles": [ - "Carry", - "Durable" - ], - "tags": [ - "核心", - "耐久" - ], - "aliases": [ - "大锤" - ], - "abbr": [ - "db", - "dawn" - ], - "base_str": 25, - "str_gain": 3.4, - "base_agi": 14, - "agi_gain": 1.7, - "base_int": 20, - "int_gain": 2.0, - "health": 670, - "mana": 315, - "health_regen": 3.0, - "mana_regen": 1.0, - "armor": 4.33, - "damage_min": 56, - "damage_max": 60, - "move_speed": 300, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.6 - }, - { - "id": 136, - "key": "marci", - "name": "Marci", - "attr": "all", - "name_loc": "玛西", - "roles": [ - "Support", - "Carry", - "Initiator", - "Disabler", - "Escape" - ], - "tags": [ - "核心", - "辅助", - "逃生", - "先手", - "控制" - ], - "aliases": [ - "马西" - ], - "abbr": [], - "base_str": 23, - "str_gain": 3.2, - "base_agi": 20, - "agi_gain": 2.2, - "base_int": 19, - "int_gain": 1.9, - "health": 626, - "mana": 303, - "health_regen": 2.55, - "mana_regen": 0.95, - "armor": 3.33, - "damage_min": 72, - "damage_max": 76, - "move_speed": 315, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.9 - }, - { - "id": 137, - "key": "primal_beast", - "name": "Primal Beast", - "attr": "str", - "name_loc": "獸", - "roles": [ - "Initiator", - "Durable", - "Disabler" - ], - "tags": [ - "先手", - "控制", - "耐久" - ], - "aliases": [ - "兽", - "一霸" - ], - "abbr": [ - "pb", - "primal" - ], - "base_str": 26, - "str_gain": 4.3, - "base_agi": 15, - "agi_gain": 1.0, - "base_int": 16, - "int_gain": 1.7, - "health": 692, - "mana": 267, - "health_regen": 2.85, - "mana_regen": 0.8, - "armor": 4.5, - "damage_min": 62, - "damage_max": 64, - "move_speed": 305, - "attack_range": 150, - "attack_rate": 1.8, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.6 - }, - { - "id": 138, - "key": "muerta", - "name": "Muerta", - "attr": "int", - "name_loc": "琼英碧灵", - "roles": [ - "Carry", - "Nuker", - "Disabler" - ], - "tags": [ - "核心", - "爆发", - "控制" - ], - "aliases": [ - "穷逼", - "琼碧" - ], - "abbr": [], - "base_str": 19, - "str_gain": 2.0, - "base_agi": 21, - "agi_gain": 2.7, - "base_int": 23, - "int_gain": 3.6, - "health": 538, - "mana": 351, - "health_regen": 2.15, - "mana_regen": 1.15, - "armor": 3.5, - "damage_min": 47, - "damage_max": 55, - "move_speed": 295, - "attack_range": 575, - "attack_rate": 1.7, - "projectile_speed": 3000, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - }, - { - "id": 145, - "key": "kez", - "name": "Kez", - "attr": "agi", - "name_loc": "凯", - "roles": [ - "Carry", - "Escape", - "Disabler" - ], - "tags": [ - "核心", - "逃生", - "控制" - ], - "aliases": [], - "abbr": [], - "base_str": 20, - "str_gain": 2.8, - "base_agi": 27, - "agi_gain": 3.5, - "base_int": 20, - "int_gain": 1.7, - "health": 560, - "mana": 315, - "health_regen": 3.5, - "mana_regen": 1.25, - "armor": 5.5, - "damage_min": 50, - "damage_max": 56, - "move_speed": 310, - "attack_range": 225, - "attack_rate": 1.9, - "projectile_speed": 900, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 1.0 - }, - { - "id": 155, - "key": "largo", - "name": "Largo", - "attr": "str", - "name_loc": "朗戈", - "roles": [ - "Durable", - "Disabler", - "Support" - ], - "tags": [ - "辅助", - "控制", - "耐久" - ], - "aliases": [], - "abbr": [], - "base_str": 24, - "str_gain": 3.6, - "base_agi": 14, - "agi_gain": 1.3, - "base_int": 21, - "int_gain": 2.6, - "health": 648, - "mana": 327, - "health_regen": 2.65, - "mana_regen": 1.05, - "armor": 4.33, - "damage_min": 63, - "damage_max": 69, - "move_speed": 290, - "attack_range": 150, - "attack_rate": 1.7, - "projectile_speed": 0, - "magic_resist": 25, - "vision_day": 1800, - "vision_night": 800, - "turn_rate": 0.7 - } -] \ No newline at end of file + { + "id": 1, + "key": "antimage", + "name": "Anti-Mage", + "attr": "agi", + "name_loc": "敌法师", + "roles": [ + "Carry", + "Escape", + "Nuker" + ], + "tags": [ + "核心", + "逃生", + "爆发" + ], + "aliases": [ + "敌法" + ], + "abbr": [ + "am" + ], + "base_str": 21, + "str_gain": 1.6, + "base_agi": 24, + "agi_gain": 2.8, + "base_int": 12, + "int_gain": 1.8, + "health": 582, + "mana": 219, + "health_regen": 3.1, + "mana_regen": 0.6, + "armor": 6.0, + "damage_min": 53, + "damage_max": 57, + "move_speed": 310, + "attack_range": 150, + "attack_rate": 1.4, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 2, + "key": "axe", + "name": "Axe", + "attr": "str", + "name_loc": "斧王", + "roles": [ + "Initiator", + "Durable", + "Disabler", + "Carry" + ], + "tags": [ + "核心", + "先手", + "控制", + "耐久" + ], + "aliases": [], + "abbr": [], + "base_str": 25, + "str_gain": 2.7, + "base_agi": 20, + "agi_gain": 1.7, + "base_int": 18, + "int_gain": 1.6, + "health": 670, + "mana": 291, + "health_regen": 5.0, + "mana_regen": 0.9, + "armor": 3.33, + "damage_min": 56, + "damage_max": 60, + "move_speed": 315, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 3, + "key": "bane", + "name": "Bane", + "attr": "all", + "name_loc": "祸乱之源", + "roles": [ + "Support", + "Disabler", + "Nuker", + "Durable" + ], + "tags": [ + "辅助", + "爆发", + "控制", + "耐久" + ], + "aliases": [], + "abbr": [], + "base_str": 23, + "str_gain": 2.5, + "base_agi": 23, + "agi_gain": 2.5, + "base_int": 23, + "int_gain": 2.5, + "health": 626, + "mana": 351, + "health_regen": 2.55, + "mana_regen": 1.15, + "armor": 4.83, + "damage_min": 72, + "damage_max": 78, + "move_speed": 305, + "attack_range": 425, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 1200, + "turn_rate": 0.6 + }, + { + "id": 4, + "key": "bloodseeker", + "name": "Bloodseeker", + "attr": "agi", + "name_loc": "血魔", + "roles": [ + "Carry", + "Disabler", + "Nuker", + "Initiator" + ], + "tags": [ + "核心", + "爆发", + "先手", + "控制" + ], + "aliases": [], + "abbr": [ + "bs" + ], + "base_str": 24, + "str_gain": 2.7, + "base_agi": 24, + "agi_gain": 3.1, + "base_int": 17, + "int_gain": 2.0, + "health": 648, + "mana": 279, + "health_regen": 2.65, + "mana_regen": 0.85, + "armor": 6.0, + "damage_min": 59, + "damage_max": 65, + "move_speed": 285, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 5, + "key": "crystal_maiden", + "name": "Crystal Maiden", + "attr": "int", + "name_loc": "水晶室女", + "roles": [ + "Support", + "Disabler", + "Nuker" + ], + "tags": [ + "辅助", + "爆发", + "控制" + ], + "aliases": [ + "冰女" + ], + "abbr": [ + "cm" + ], + "base_str": 17, + "str_gain": 2.2, + "base_agi": 16, + "agi_gain": 1.8, + "base_int": 20, + "int_gain": 3.3, + "health": 494, + "mana": 315, + "health_regen": 1.95, + "mana_regen": 1.0, + "armor": 2.67, + "damage_min": 48, + "damage_max": 54, + "move_speed": 280, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 6, + "key": "drow_ranger", + "name": "Drow Ranger", + "attr": "agi", + "name_loc": "卓尔游侠", + "roles": [ + "Carry", + "Disabler", + "Pusher" + ], + "tags": [ + "核心", + "推进", + "控制" + ], + "aliases": [ + "小黑" + ], + "abbr": [ + "drow" + ], + "base_str": 16, + "str_gain": 1.9, + "base_agi": 22, + "agi_gain": 2.8, + "base_int": 15, + "int_gain": 1.4, + "health": 472, + "mana": 255, + "health_regen": 1.85, + "mana_regen": 0.75, + "armor": 3.67, + "damage_min": 49, + "damage_max": 56, + "move_speed": 310, + "attack_range": 625, + "attack_rate": 1.7, + "projectile_speed": 1250, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 7, + "key": "earthshaker", + "name": "Earthshaker", + "attr": "str", + "name_loc": "撼地者", + "roles": [ + "Support", + "Initiator", + "Disabler", + "Nuker" + ], + "tags": [ + "辅助", + "爆发", + "先手", + "控制" + ], + "aliases": [ + "小山" + ], + "abbr": [ + "es" + ], + "base_str": 22, + "str_gain": 3.7, + "base_agi": 12, + "agi_gain": 1.4, + "base_int": 18, + "int_gain": 2.1, + "health": 604, + "mana": 291, + "health_regen": 3.2, + "mana_regen": 0.9, + "armor": 4.0, + "damage_min": 49, + "damage_max": 59, + "move_speed": 315, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 8, + "key": "juggernaut", + "name": "Juggernaut", + "attr": "agi", + "name_loc": "主宰", + "roles": [ + "Carry", + "Pusher", + "Escape" + ], + "tags": [ + "核心", + "推进", + "逃生" + ], + "aliases": [ + "剑圣" + ], + "abbr": [ + "jugg" + ], + "base_str": 20, + "str_gain": 2.0, + "base_agi": 32, + "agi_gain": 2.8, + "base_int": 14, + "int_gain": 1.4, + "health": 560, + "mana": 243, + "health_regen": 2.5, + "mana_regen": 0.7, + "armor": 5.33, + "damage_min": 54, + "damage_max": 56, + "move_speed": 300, + "attack_range": 150, + "attack_rate": 1.4, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 9, + "key": "mirana", + "name": "Mirana", + "attr": "agi", + "name_loc": "米拉娜", + "roles": [ + "Carry", + "Support", + "Escape", + "Nuker", + "Disabler" + ], + "tags": [ + "核心", + "辅助", + "逃生", + "爆发", + "控制" + ], + "aliases": [ + "白虎" + ], + "abbr": [ + "potm" + ], + "base_str": 20, + "str_gain": 2.2, + "base_agi": 26, + "agi_gain": 3.1, + "base_int": 22, + "int_gain": 1.9, + "health": 560, + "mana": 339, + "health_regen": 2.25, + "mana_regen": 1.5, + "armor": 2.33, + "damage_min": 48, + "damage_max": 54, + "move_speed": 285, + "attack_range": 630, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 10, + "key": "morphling", + "name": "Morphling", + "attr": "agi", + "name_loc": "变体精灵", + "roles": [ + "Carry", + "Escape", + "Durable", + "Nuker", + "Disabler" + ], + "tags": [ + "核心", + "逃生", + "爆发", + "控制", + "耐久" + ], + "aliases": [ + "水人" + ], + "abbr": [ + "morph" + ], + "base_str": 23, + "str_gain": 2.6, + "base_agi": 24, + "agi_gain": 3.9, + "base_int": 19, + "int_gain": 1.8, + "health": 626, + "mana": 303, + "health_regen": 2.8, + "mana_regen": 0.95, + "armor": 3.0, + "damage_min": 36, + "damage_max": 45, + "move_speed": 285, + "attack_range": 350, + "attack_rate": 1.5, + "projectile_speed": 1300, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 11, + "key": "nevermore", + "name": "Shadow Fiend", + "attr": "agi", + "name_loc": "影魔", + "roles": [ + "Carry", + "Nuker" + ], + "tags": [ + "核心", + "爆发" + ], + "aliases": [], + "abbr": [ + "sf" + ], + "base_str": 19, + "str_gain": 2.7, + "base_agi": 25, + "agi_gain": 3.6, + "base_int": 18, + "int_gain": 2.2, + "health": 538, + "mana": 291, + "health_regen": 2.15, + "mana_regen": 1.2, + "armor": 4.17, + "damage_min": 41, + "damage_max": 47, + "move_speed": 305, + "attack_range": 525, + "attack_rate": 1.6, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 12, + "key": "phantom_lancer", + "name": "Phantom Lancer", + "attr": "agi", + "name_loc": "幻影长矛手", + "roles": [ + "Carry", + "Escape", + "Pusher", + "Nuker" + ], + "tags": [ + "核心", + "推进", + "逃生", + "爆发", + "幻象" + ], + "aliases": [ + "猴子" + ], + "abbr": [ + "pl" + ], + "base_str": 21, + "str_gain": 2.2, + "base_agi": 26, + "agi_gain": 3.4, + "base_int": 21, + "int_gain": 1.8, + "health": 582, + "mana": 327, + "health_regen": 4.1, + "mana_regen": 1.05, + "armor": 4.33, + "damage_min": 52, + "damage_max": 62, + "move_speed": 285, + "attack_range": 225, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 13, + "key": "puck", + "name": "Puck", + "attr": "int", + "name_loc": "帕克", + "roles": [ + "Initiator", + "Disabler", + "Escape", + "Nuker" + ], + "tags": [ + "逃生", + "爆发", + "先手", + "控制" + ], + "aliases": [], + "abbr": [], + "base_str": 17, + "str_gain": 2.4, + "base_agi": 14, + "agi_gain": 2.3, + "base_int": 23, + "int_gain": 3.8, + "health": 494, + "mana": 351, + "health_regen": 2.2, + "mana_regen": 1.65, + "armor": 2.33, + "damage_min": 45, + "damage_max": 53, + "move_speed": 290, + "attack_range": 550, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 14, + "key": "pudge", + "name": "Pudge", + "attr": "str", + "name_loc": "帕吉", + "roles": [ + "Disabler", + "Initiator", + "Durable", + "Nuker" + ], + "tags": [ + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "屠夫" + ], + "abbr": [], + "base_str": 25, + "str_gain": 3.0, + "base_agi": 11, + "agi_gain": 1.4, + "base_int": 16, + "int_gain": 1.8, + "health": 670, + "mana": 267, + "health_regen": 5.0, + "mana_regen": 0.8, + "armor": 1.83, + "damage_min": 70, + "damage_max": 76, + "move_speed": 280, + "attack_range": 175, + "attack_rate": 1.7, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 15, + "key": "razor", + "name": "Razor", + "attr": "agi", + "name_loc": "雷泽", + "roles": [ + "Carry", + "Durable", + "Nuker", + "Pusher" + ], + "tags": [ + "核心", + "推进", + "爆发", + "耐久" + ], + "aliases": [ + "电棍" + ], + "abbr": [], + "base_str": 22, + "str_gain": 2.8, + "base_agi": 24, + "agi_gain": 2.8, + "base_int": 21, + "int_gain": 2.2, + "health": 604, + "mana": 327, + "health_regen": 3.2, + "mana_regen": 1.05, + "armor": 4.0, + "damage_min": 47, + "damage_max": 49, + "move_speed": 280, + "attack_range": 475, + "attack_rate": 1.7, + "projectile_speed": 2000, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 16, + "key": "sand_king", + "name": "Sand King", + "attr": "all", + "name_loc": "沙王", + "roles": [ + "Initiator", + "Disabler", + "Support", + "Nuker", + "Escape" + ], + "tags": [ + "辅助", + "逃生", + "爆发", + "先手", + "控制" + ], + "aliases": [], + "abbr": [ + "sk" + ], + "base_str": 23, + "str_gain": 2.3, + "base_agi": 19, + "agi_gain": 2.0, + "base_int": 17, + "int_gain": 2.0, + "health": 626, + "mana": 279, + "health_regen": 2.55, + "mana_regen": 0.85, + "armor": 4.17, + "damage_min": 60, + "damage_max": 70, + "move_speed": 290, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 17, + "key": "storm_spirit", + "name": "Storm Spirit", + "attr": "int", + "name_loc": "风暴之灵", + "roles": [ + "Carry", + "Escape", + "Nuker", + "Initiator", + "Disabler" + ], + "tags": [ + "核心", + "逃生", + "爆发", + "先手", + "控制" + ], + "aliases": [ + "蓝猫" + ], + "abbr": [ + "ss" + ], + "base_str": 21, + "str_gain": 2.0, + "base_agi": 22, + "agi_gain": 2.6, + "base_int": 23, + "int_gain": 3.7, + "health": 582, + "mana": 351, + "health_regen": 2.6, + "mana_regen": 1.15, + "armor": 4.67, + "damage_min": 47, + "damage_max": 57, + "move_speed": 285, + "attack_range": 480, + "attack_rate": 1.6, + "projectile_speed": 1100, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.8 + }, + { + "id": 18, + "key": "sven", + "name": "Sven", + "attr": "str", + "name_loc": "斯温", + "roles": [ + "Carry", + "Disabler", + "Initiator", + "Durable", + "Nuker" + ], + "tags": [ + "核心", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "流浪", + "流浪剑客" + ], + "abbr": [], + "base_str": 24, + "str_gain": 3.5, + "base_agi": 18, + "agi_gain": 2.2, + "base_int": 16, + "int_gain": 1.5, + "health": 648, + "mana": 267, + "health_regen": 3.15, + "mana_regen": 0.8, + "armor": 3.0, + "damage_min": 61, + "damage_max": 63, + "move_speed": 325, + "attack_range": 150, + "attack_rate": 1.9, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 19, + "key": "tiny", + "name": "Tiny", + "attr": "str", + "name_loc": "小小", + "roles": [ + "Carry", + "Nuker", + "Pusher", + "Initiator", + "Durable", + "Disabler" + ], + "tags": [ + "核心", + "推进", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [], + "abbr": [], + "base_str": 30, + "str_gain": 4.2, + "base_agi": 0, + "agi_gain": 0.0, + "base_int": 17, + "int_gain": 2.2, + "health": 780, + "mana": 279, + "health_regen": 5.0, + "mana_regen": 0.85, + "armor": 0.0, + "damage_min": 61, + "damage_max": 67, + "move_speed": 275, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 20, + "key": "vengefulspirit", + "name": "Vengeful Spirit", + "attr": "agi", + "name_loc": "复仇之魂", + "roles": [ + "Support", + "Initiator", + "Disabler", + "Nuker", + "Escape" + ], + "tags": [ + "辅助", + "逃生", + "爆发", + "先手", + "控制" + ], + "aliases": [], + "abbr": [ + "vs" + ], + "base_str": 20, + "str_gain": 2.6, + "base_agi": 23, + "agi_gain": 3.0, + "base_int": 19, + "int_gain": 1.5, + "health": 560, + "mana": 303, + "health_regen": 2.25, + "mana_regen": 0.95, + "armor": 3.83, + "damage_min": 51, + "damage_max": 57, + "move_speed": 300, + "attack_range": 400, + "attack_rate": 1.5, + "projectile_speed": 1500, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 21, + "key": "windrunner", + "name": "Windranger", + "attr": "all", + "name_loc": "风行者", + "roles": [ + "Carry", + "Support", + "Disabler", + "Escape", + "Nuker" + ], + "tags": [ + "核心", + "辅助", + "逃生", + "爆发", + "控制" + ], + "aliases": [ + "风行" + ], + "abbr": [ + "wr" + ], + "base_str": 18, + "str_gain": 2.6, + "base_agi": 17, + "agi_gain": 2.1, + "base_int": 18, + "int_gain": 3.2, + "health": 516, + "mana": 291, + "health_regen": 2.05, + "mana_regen": 1.15, + "armor": 2.83, + "damage_min": 61, + "damage_max": 73, + "move_speed": 285, + "attack_range": 600, + "attack_rate": 1.5, + "projectile_speed": 1250, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.8 + }, + { + "id": 22, + "key": "zuus", + "name": "Zeus", + "attr": "int", + "name_loc": "宙斯", + "roles": [ + "Nuker", + "Carry" + ], + "tags": [ + "核心", + "爆发" + ], + "aliases": [], + "abbr": [ + "zeus" + ], + "base_str": 21, + "str_gain": 2.1, + "base_agi": 11, + "agi_gain": 1.2, + "base_int": 23, + "int_gain": 3.3, + "health": 582, + "mana": 351, + "health_regen": 2.35, + "mana_regen": 1.15, + "armor": 2.83, + "damage_min": 53, + "damage_max": 63, + "move_speed": 305, + "attack_range": 380, + "attack_rate": 1.7, + "projectile_speed": 1100, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 23, + "key": "kunkka", + "name": "Kunkka", + "attr": "str", + "name_loc": "昆卡", + "roles": [ + "Carry", + "Support", + "Disabler", + "Initiator", + "Durable", + "Nuker" + ], + "tags": [ + "核心", + "辅助", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "船长" + ], + "abbr": [], + "base_str": 24, + "str_gain": 3.6, + "base_agi": 14, + "agi_gain": 1.6, + "base_int": 18, + "int_gain": 1.8, + "health": 648, + "mana": 291, + "health_regen": 3.15, + "mana_regen": 0.9, + "armor": 4.33, + "damage_min": 54, + "damage_max": 60, + "move_speed": 315, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 25, + "key": "lina", + "name": "Lina", + "attr": "int", + "name_loc": "莉娜", + "roles": [ + "Support", + "Carry", + "Nuker", + "Disabler" + ], + "tags": [ + "核心", + "辅助", + "爆发", + "控制" + ], + "aliases": [ + "火女" + ], + "abbr": [], + "base_str": 20, + "str_gain": 2.4, + "base_agi": 23, + "agi_gain": 2.4, + "base_int": 30, + "int_gain": 4.0, + "health": 560, + "mana": 435, + "health_regen": 2.25, + "mana_regen": 1.5, + "armor": 3.83, + "damage_min": 51, + "damage_max": 59, + "move_speed": 290, + "attack_range": 670, + "attack_rate": 1.6, + "projectile_speed": 1000, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 26, + "key": "lion", + "name": "Lion", + "attr": "int", + "name_loc": "莱恩", + "roles": [ + "Support", + "Disabler", + "Nuker", + "Initiator" + ], + "tags": [ + "辅助", + "爆发", + "先手", + "控制" + ], + "aliases": [], + "abbr": [], + "base_str": 18, + "str_gain": 2.4, + "base_agi": 15, + "agi_gain": 1.7, + "base_int": 19, + "int_gain": 3.5, + "health": 516, + "mana": 303, + "health_regen": 2.3, + "mana_regen": 0.95, + "armor": 2.5, + "damage_min": 48, + "damage_max": 54, + "move_speed": 290, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 27, + "key": "shadow_shaman", + "name": "Shadow Shaman", + "attr": "int", + "name_loc": "暗影萨满", + "roles": [ + "Support", + "Pusher", + "Disabler", + "Nuker", + "Initiator" + ], + "tags": [ + "辅助", + "推进", + "爆发", + "先手", + "控制" + ], + "aliases": [ + "萨满" + ], + "abbr": [ + "sshaman" + ], + "base_str": 20, + "str_gain": 2.3, + "base_agi": 16, + "agi_gain": 1.6, + "base_int": 23, + "int_gain": 3.5, + "health": 560, + "mana": 351, + "health_regen": 3.0, + "mana_regen": 1.15, + "armor": 4.67, + "damage_min": 63, + "damage_max": 70, + "move_speed": 285, + "attack_range": 400, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 28, + "key": "slardar", + "name": "Slardar", + "attr": "str", + "name_loc": "斯拉达", + "roles": [ + "Carry", + "Durable", + "Initiator", + "Disabler", + "Escape" + ], + "tags": [ + "核心", + "逃生", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "大鱼人", + "大鱼" + ], + "abbr": [], + "base_str": 21, + "str_gain": 3.4, + "base_agi": 17, + "agi_gain": 2.1, + "base_int": 15, + "int_gain": 1.5, + "health": 582, + "mana": 255, + "health_regen": 2.6, + "mana_regen": 0.75, + "armor": 4.83, + "damage_min": 51, + "damage_max": 59, + "move_speed": 300, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 29, + "key": "tidehunter", + "name": "Tidehunter", + "attr": "str", + "name_loc": "潮汐猎人", + "roles": [ + "Initiator", + "Durable", + "Disabler", + "Nuker", + "Carry" + ], + "tags": [ + "核心", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "潮汐", + "西瓜皮" + ], + "abbr": [ + "tide" + ], + "base_str": 26, + "str_gain": 3.7, + "base_agi": 15, + "agi_gain": 1.5, + "base_int": 20, + "int_gain": 1.7, + "health": 692, + "mana": 315, + "health_regen": 2.85, + "mana_regen": 1.5, + "armor": 2.5, + "damage_min": 51, + "damage_max": 57, + "move_speed": 300, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 30, + "key": "witch_doctor", + "name": "Witch Doctor", + "attr": "int", + "name_loc": "巫医", + "roles": [ + "Support", + "Nuker", + "Disabler" + ], + "tags": [ + "辅助", + "爆发", + "控制" + ], + "aliases": [], + "abbr": [ + "wd" + ], + "base_str": 20, + "str_gain": 2.1, + "base_agi": 13, + "agi_gain": 1.4, + "base_int": 22, + "int_gain": 3.1, + "health": 560, + "mana": 339, + "health_regen": 2.25, + "mana_regen": 1.1, + "armor": 2.17, + "damage_min": 48, + "damage_max": 58, + "move_speed": 300, + "attack_range": 550, + "attack_rate": 1.7, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 31, + "key": "lich", + "name": "Lich", + "attr": "int", + "name_loc": "巫妖", + "roles": [ + "Support", + "Nuker" + ], + "tags": [ + "辅助", + "爆发" + ], + "aliases": [], + "abbr": [], + "base_str": 20, + "str_gain": 2.1, + "base_agi": 17, + "agi_gain": 1.7, + "base_int": 28, + "int_gain": 3.4, + "health": 560, + "mana": 411, + "health_regen": 2.5, + "mana_regen": 0.4, + "armor": 1.83, + "damage_min": 52, + "damage_max": 61, + "move_speed": 290, + "attack_range": 550, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 32, + "key": "riki", + "name": "Riki", + "attr": "agi", + "name_loc": "力丸", + "roles": [ + "Carry", + "Escape", + "Disabler" + ], + "tags": [ + "核心", + "逃生", + "控制" + ], + "aliases": [ + "隐刺" + ], + "abbr": [], + "base_str": 18, + "str_gain": 2.0, + "base_agi": 30, + "agi_gain": 2.4, + "base_int": 14, + "int_gain": 1.3, + "health": 516, + "mana": 243, + "health_regen": 4.3, + "mana_regen": 0.7, + "armor": 5.0, + "damage_min": 52, + "damage_max": 56, + "move_speed": 315, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 33, + "key": "enigma", + "name": "Enigma", + "attr": "all", + "name_loc": "谜团", + "roles": [ + "Disabler", + "Initiator", + "Pusher" + ], + "tags": [ + "推进", + "先手", + "控制" + ], + "aliases": [], + "abbr": [], + "base_str": 21, + "str_gain": 2.5, + "base_agi": 14, + "agi_gain": 1.0, + "base_int": 19, + "int_gain": 3.6, + "health": 582, + "mana": 303, + "health_regen": 2.35, + "mana_regen": 1.45, + "armor": 3.33, + "damage_min": 57, + "damage_max": 63, + "move_speed": 280, + "attack_range": 500, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 34, + "key": "tinker", + "name": "Tinker", + "attr": "int", + "name_loc": "修补匠", + "roles": [ + "Carry", + "Nuker", + "Pusher" + ], + "tags": [ + "核心", + "推进", + "爆发" + ], + "aliases": [], + "abbr": [], + "base_str": 19, + "str_gain": 2.3, + "base_agi": 17, + "agi_gain": 1.4, + "base_int": 30, + "int_gain": 3.3, + "health": 538, + "mana": 435, + "health_regen": 2.4, + "mana_regen": 2.0, + "armor": 4.83, + "damage_min": 52, + "damage_max": 58, + "move_speed": 305, + "attack_range": 500, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 35, + "key": "sniper", + "name": "Sniper", + "attr": "agi", + "name_loc": "狙击手", + "roles": [ + "Carry", + "Nuker" + ], + "tags": [ + "核心", + "爆发" + ], + "aliases": [ + "火枪" + ], + "abbr": [], + "base_str": 19, + "str_gain": 2.0, + "base_agi": 27, + "agi_gain": 3.2, + "base_int": 15, + "int_gain": 2.6, + "health": 538, + "mana": 255, + "health_regen": 2.15, + "mana_regen": 0.75, + "armor": 4.5, + "damage_min": 40, + "damage_max": 46, + "move_speed": 285, + "attack_range": 550, + "attack_rate": 1.7, + "projectile_speed": 3000, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 1400, + "turn_rate": 0.7 + }, + { + "id": 36, + "key": "necrolyte", + "name": "Necrophos", + "attr": "int", + "name_loc": "瘟疫法师", + "roles": [ + "Carry", + "Nuker", + "Durable", + "Disabler" + ], + "tags": [ + "核心", + "爆发", + "控制", + "耐久" + ], + "aliases": [], + "abbr": [ + "nec" + ], + "base_str": 18, + "str_gain": 2.3, + "base_agi": 12, + "agi_gain": 1.3, + "base_int": 23, + "int_gain": 2.7, + "health": 516, + "mana": 351, + "health_regen": 2.05, + "mana_regen": 1.15, + "armor": 4.0, + "damage_min": 49, + "damage_max": 53, + "move_speed": 280, + "attack_range": 500, + "attack_rate": 1.7, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 37, + "key": "warlock", + "name": "Warlock", + "attr": "int", + "name_loc": "术士", + "roles": [ + "Support", + "Initiator", + "Disabler" + ], + "tags": [ + "辅助", + "先手", + "控制" + ], + "aliases": [], + "abbr": [], + "base_str": 22, + "str_gain": 2.4, + "base_agi": 10, + "agi_gain": 1.0, + "base_int": 25, + "int_gain": 3.1, + "health": 604, + "mana": 375, + "health_regen": 2.45, + "mana_regen": 1.25, + "armor": 2.67, + "damage_min": 52, + "damage_max": 62, + "move_speed": 300, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 38, + "key": "beastmaster", + "name": "Beastmaster", + "attr": "all", + "name_loc": "兽王", + "roles": [ + "Initiator", + "Disabler", + "Durable", + "Nuker" + ], + "tags": [ + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [], + "abbr": [ + "bm" + ], + "base_str": 25, + "str_gain": 2.9, + "base_agi": 19, + "agi_gain": 2.0, + "base_int": 16, + "int_gain": 1.9, + "health": 670, + "mana": 267, + "health_regen": 2.75, + "mana_regen": 0.8, + "armor": 2.17, + "damage_min": 65, + "damage_max": 69, + "move_speed": 305, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 39, + "key": "queenofpain", + "name": "Queen of Pain", + "attr": "int", + "name_loc": "痛苦女王", + "roles": [ + "Carry", + "Nuker", + "Escape" + ], + "tags": [ + "核心", + "逃生", + "爆发" + ], + "aliases": [ + "女王" + ], + "abbr": [ + "qop" + ], + "base_str": 20, + "str_gain": 2.4, + "base_agi": 22, + "agi_gain": 2.5, + "base_int": 25, + "int_gain": 3.4, + "health": 560, + "mana": 375, + "health_regen": 2.25, + "mana_regen": 1.25, + "armor": 3.67, + "damage_min": 51, + "damage_max": 57, + "move_speed": 290, + "attack_range": 550, + "attack_rate": 1.5, + "projectile_speed": 1500, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 40, + "key": "venomancer", + "name": "Venomancer", + "attr": "all", + "name_loc": "剧毒术士", + "roles": [ + "Support", + "Nuker", + "Initiator", + "Pusher", + "Disabler" + ], + "tags": [ + "辅助", + "推进", + "爆发", + "先手", + "控制" + ], + "aliases": [ + "剧毒" + ], + "abbr": [ + "veno" + ], + "base_str": 19, + "str_gain": 2.1, + "base_agi": 25, + "agi_gain": 2.8, + "base_int": 19, + "int_gain": 1.8, + "health": 538, + "mana": 303, + "health_regen": 2.15, + "mana_regen": 0.95, + "armor": 4.17, + "damage_min": 62, + "damage_max": 65, + "move_speed": 280, + "attack_range": 450, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 41, + "key": "faceless_void", + "name": "Faceless Void", + "attr": "agi", + "name_loc": "虚空假面", + "roles": [ + "Carry", + "Initiator", + "Disabler", + "Escape", + "Durable" + ], + "tags": [ + "核心", + "逃生", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "虚空" + ], + "abbr": [ + "fv" + ], + "base_str": 20, + "str_gain": 2.6, + "base_agi": 24, + "agi_gain": 3.3, + "base_int": 15, + "int_gain": 1.5, + "health": 560, + "mana": 255, + "health_regen": 3.0, + "mana_regen": 0.75, + "armor": 4.0, + "damage_min": 58, + "damage_max": 64, + "move_speed": 300, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 42, + "key": "skeleton_king", + "name": "Wraith King", + "attr": "str", + "name_loc": "冥魂大帝", + "roles": [ + "Carry", + "Support", + "Durable", + "Disabler", + "Initiator" + ], + "tags": [ + "核心", + "辅助", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "骷髅王", + "骷髅" + ], + "abbr": [ + "wk" + ], + "base_str": 22, + "str_gain": 2.8, + "base_agi": 16, + "agi_gain": 1.7, + "base_int": 16, + "int_gain": 1.6, + "health": 604, + "mana": 267, + "health_regen": 2.7, + "mana_regen": 0.8, + "armor": 2.67, + "damage_min": 60, + "damage_max": 62, + "move_speed": 310, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 43, + "key": "death_prophet", + "name": "Death Prophet", + "attr": "all", + "name_loc": "死亡先知", + "roles": [ + "Carry", + "Pusher", + "Nuker", + "Disabler" + ], + "tags": [ + "核心", + "推进", + "爆发", + "控制" + ], + "aliases": [], + "abbr": [ + "dp" + ], + "base_str": 21, + "str_gain": 2.9, + "base_agi": 17, + "agi_gain": 2.0, + "base_int": 24, + "int_gain": 3.0, + "health": 582, + "mana": 363, + "health_regen": 2.35, + "mana_regen": 1.7, + "armor": 3.83, + "damage_min": 65, + "damage_max": 75, + "move_speed": 290, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 1000, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.8 + }, + { + "id": 44, + "key": "phantom_assassin", + "name": "Phantom Assassin", + "attr": "agi", + "name_loc": "幻影刺客", + "roles": [ + "Carry", + "Escape" + ], + "tags": [ + "核心", + "逃生" + ], + "aliases": [ + "幻刺" + ], + "abbr": [ + "pa" + ], + "base_str": 19, + "str_gain": 2.0, + "base_agi": 21, + "agi_gain": 3.4, + "base_int": 15, + "int_gain": 1.7, + "health": 538, + "mana": 255, + "health_regen": 2.9, + "mana_regen": 0.75, + "armor": 4.5, + "damage_min": 56, + "damage_max": 58, + "move_speed": 310, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.8 + }, + { + "id": 45, + "key": "pugna", + "name": "Pugna", + "attr": "int", + "name_loc": "帕格纳", + "roles": [ + "Nuker", + "Pusher" + ], + "tags": [ + "推进", + "爆发" + ], + "aliases": [ + "骨法" + ], + "abbr": [], + "base_str": 19, + "str_gain": 2.0, + "base_agi": 21, + "agi_gain": 1.3, + "base_int": 26, + "int_gain": 5.2, + "health": 538, + "mana": 387, + "health_regen": 2.15, + "mana_regen": 1.8, + "armor": 3.5, + "damage_min": 47, + "damage_max": 54, + "move_speed": 325, + "attack_range": 630, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 46, + "key": "templar_assassin", + "name": "Templar Assassin", + "attr": "agi", + "name_loc": "圣堂刺客", + "roles": [ + "Carry", + "Escape" + ], + "tags": [ + "核心", + "逃生" + ], + "aliases": [ + "圣堂" + ], + "abbr": [ + "ta" + ], + "base_str": 21, + "str_gain": 2.4, + "base_agi": 23, + "agi_gain": 3.4, + "base_int": 20, + "int_gain": 2.0, + "health": 582, + "mana": 315, + "health_regen": 2.1, + "mana_regen": 1.0, + "armor": 3.83, + "damage_min": 51, + "damage_max": 56, + "move_speed": 310, + "attack_range": 250, + "attack_rate": 1.6, + "projectile_speed": 1000, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 47, + "key": "viper", + "name": "Viper", + "attr": "agi", + "name_loc": "冥界亚龙", + "roles": [ + "Carry", + "Durable", + "Initiator", + "Disabler" + ], + "tags": [ + "核心", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "毒龙" + ], + "abbr": [], + "base_str": 21, + "str_gain": 2.4, + "base_agi": 21, + "agi_gain": 2.9, + "base_int": 17, + "int_gain": 2.0, + "health": 582, + "mana": 279, + "health_regen": 2.35, + "mana_regen": 0.85, + "armor": 1.5, + "damage_min": 48, + "damage_max": 50, + "move_speed": 275, + "attack_range": 575, + "attack_rate": 1.7, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 48, + "key": "luna", + "name": "Luna", + "attr": "agi", + "name_loc": "露娜", + "roles": [ + "Carry", + "Nuker", + "Pusher" + ], + "tags": [ + "核心", + "推进", + "爆发" + ], + "aliases": [ + "月骑" + ], + "abbr": [], + "base_str": 21, + "str_gain": 2.2, + "base_agi": 24, + "agi_gain": 3.4, + "base_int": 23, + "int_gain": 1.9, + "health": 582, + "mana": 351, + "health_regen": 2.6, + "mana_regen": 1.15, + "armor": 6.0, + "damage_min": 50, + "damage_max": 56, + "move_speed": 325, + "attack_range": 330, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 49, + "key": "dragon_knight", + "name": "Dragon Knight", + "attr": "str", + "name_loc": "龙骑士", + "roles": [ + "Carry", + "Pusher", + "Durable", + "Disabler", + "Initiator", + "Nuker" + ], + "tags": [ + "核心", + "推进", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "龙骑" + ], + "abbr": [ + "dk" + ], + "base_str": 21, + "str_gain": 3.2, + "base_agi": 14, + "agi_gain": 2.0, + "base_int": 18, + "int_gain": 1.7, + "health": 582, + "mana": 291, + "health_regen": 2.35, + "mana_regen": 0.9, + "armor": 2.33, + "damage_min": 55, + "damage_max": 61, + "move_speed": 310, + "attack_range": 150, + "attack_rate": 1.6, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 50, + "key": "dazzle", + "name": "Dazzle", + "attr": "all", + "name_loc": "戴泽", + "roles": [ + "Support", + "Nuker", + "Disabler" + ], + "tags": [ + "辅助", + "爆发", + "控制" + ], + "aliases": [ + "暗牧" + ], + "abbr": [], + "base_str": 19, + "str_gain": 2.3, + "base_agi": 20, + "agi_gain": 1.7, + "base_int": 25, + "int_gain": 3.5, + "health": 538, + "mana": 375, + "health_regen": 2.15, + "mana_regen": 1.55, + "armor": 3.33, + "damage_min": 64, + "damage_max": 70, + "move_speed": 305, + "attack_range": 575, + "attack_rate": 1.7, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 51, + "key": "rattletrap", + "name": "Clockwerk", + "attr": "str", + "name_loc": "发条技师", + "roles": [ + "Initiator", + "Disabler", + "Durable", + "Nuker" + ], + "tags": [ + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "发条" + ], + "abbr": [ + "cw", + "clock" + ], + "base_str": 26, + "str_gain": 3.2, + "base_agi": 13, + "agi_gain": 2.3, + "base_int": 18, + "int_gain": 1.7, + "health": 692, + "mana": 291, + "health_regen": 3.1, + "mana_regen": 0.9, + "armor": 3.17, + "damage_min": 50, + "damage_max": 52, + "move_speed": 310, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 52, + "key": "leshrac", + "name": "Leshrac", + "attr": "int", + "name_loc": "拉席克", + "roles": [ + "Carry", + "Support", + "Nuker", + "Pusher", + "Disabler" + ], + "tags": [ + "核心", + "辅助", + "推进", + "爆发", + "控制" + ], + "aliases": [ + "老鹿" + ], + "abbr": [], + "base_str": 20, + "str_gain": 2.8, + "base_agi": 17, + "agi_gain": 2.5, + "base_int": 22, + "int_gain": 3.5, + "health": 560, + "mana": 339, + "health_regen": 2.25, + "mana_regen": 1.1, + "armor": 3.83, + "damage_min": 49, + "damage_max": 53, + "move_speed": 325, + "attack_range": 575, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 53, + "key": "furion", + "name": "Nature's Prophet", + "attr": "all", + "name_loc": "自然先知", + "roles": [ + "Carry", + "Pusher", + "Escape", + "Nuker" + ], + "tags": [ + "核心", + "推进", + "逃生", + "爆发" + ], + "aliases": [ + "先知" + ], + "abbr": [ + "np" + ], + "base_str": 19, + "str_gain": 2.6, + "base_agi": 14, + "agi_gain": 2.6, + "base_int": 22, + "int_gain": 3.5, + "health": 538, + "mana": 339, + "health_regen": 2.15, + "mana_regen": 1.1, + "armor": 2.33, + "damage_min": 58, + "damage_max": 64, + "move_speed": 295, + "attack_range": 600, + "attack_rate": 1.5, + "projectile_speed": 1125, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 54, + "key": "life_stealer", + "name": "Lifestealer", + "attr": "str", + "name_loc": "噬魂鬼", + "roles": [ + "Carry", + "Durable", + "Escape", + "Disabler" + ], + "tags": [ + "核心", + "逃生", + "控制", + "耐久" + ], + "aliases": [ + "小狗" + ], + "abbr": [ + "ls" + ], + "base_str": 23, + "str_gain": 2.4, + "base_agi": 15, + "agi_gain": 1.9, + "base_int": 15, + "int_gain": 1.8, + "health": 626, + "mana": 255, + "health_regen": 2.55, + "mana_regen": 0.75, + "armor": 3.5, + "damage_min": 49, + "damage_max": 55, + "move_speed": 320, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 55, + "key": "dark_seer", + "name": "Dark Seer", + "attr": "int", + "name_loc": "黑暗贤者", + "roles": [ + "Initiator", + "Escape", + "Disabler" + ], + "tags": [ + "逃生", + "先手", + "控制" + ], + "aliases": [ + "兔子", + "黑贤" + ], + "abbr": [ + "ds" + ], + "base_str": 20, + "str_gain": 2.6, + "base_agi": 19, + "agi_gain": 1.5, + "base_int": 22, + "int_gain": 2.7, + "health": 560, + "mana": 339, + "health_regen": 3.0, + "mana_regen": 1.1, + "armor": 5.17, + "damage_min": 53, + "damage_max": 59, + "move_speed": 295, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 56, + "key": "clinkz", + "name": "Clinkz", + "attr": "agi", + "name_loc": "克林克兹", + "roles": [ + "Carry", + "Escape", + "Pusher" + ], + "tags": [ + "核心", + "推进", + "逃生" + ], + "aliases": [ + "骨弓", + "小骷髅" + ], + "abbr": [], + "base_str": 18, + "str_gain": 2.0, + "base_agi": 25, + "agi_gain": 2.7, + "base_int": 21, + "int_gain": 2.2, + "health": 516, + "mana": 327, + "health_regen": 2.05, + "mana_regen": 1.05, + "armor": 4.17, + "damage_min": 44, + "damage_max": 50, + "move_speed": 285, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 57, + "key": "omniknight", + "name": "Omniknight", + "attr": "str", + "name_loc": "全能骑士", + "roles": [ + "Support", + "Durable", + "Nuker" + ], + "tags": [ + "辅助", + "爆发", + "耐久" + ], + "aliases": [ + "全能" + ], + "abbr": [ + "omni" + ], + "base_str": 22, + "str_gain": 3.1, + "base_agi": 15, + "agi_gain": 1.7, + "base_int": 16, + "int_gain": 2.1, + "health": 604, + "mana": 267, + "health_regen": 2.45, + "mana_regen": 0.8, + "armor": 4.5, + "damage_min": 53, + "damage_max": 63, + "move_speed": 310, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 58, + "key": "enchantress", + "name": "Enchantress", + "attr": "int", + "name_loc": "魅惑魔女", + "roles": [ + "Support", + "Pusher", + "Durable", + "Disabler" + ], + "tags": [ + "辅助", + "推进", + "控制", + "耐久" + ], + "aliases": [ + "小鹿" + ], + "abbr": [ + "ench" + ], + "base_str": 17, + "str_gain": 2.0, + "base_agi": 19, + "agi_gain": 2.1, + "base_int": 22, + "int_gain": 4.0, + "health": 494, + "mana": 339, + "health_regen": 1.95, + "mana_regen": 1.6, + "armor": 4.17, + "damage_min": 43, + "damage_max": 53, + "move_speed": 320, + "attack_range": 575, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 59, + "key": "huskar", + "name": "Huskar", + "attr": "str", + "name_loc": "哈斯卡", + "roles": [ + "Carry", + "Durable", + "Initiator" + ], + "tags": [ + "核心", + "先手", + "耐久" + ], + "aliases": [ + "神灵" + ], + "abbr": [], + "base_str": 23, + "str_gain": 3.3, + "base_agi": 10, + "agi_gain": 1.5, + "base_int": 18, + "int_gain": 0.0, + "health": 626, + "mana": 291, + "health_regen": 2.55, + "mana_regen": 0.9, + "armor": 2.67, + "damage_min": 44, + "damage_max": 49, + "move_speed": 290, + "attack_range": 400, + "attack_rate": 1.6, + "projectile_speed": 1400, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 60, + "key": "night_stalker", + "name": "Night Stalker", + "attr": "str", + "name_loc": "暗夜魔王", + "roles": [ + "Carry", + "Initiator", + "Durable", + "Disabler", + "Nuker" + ], + "tags": [ + "核心", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "夜魔" + ], + "abbr": [ + "ns" + ], + "base_str": 23, + "str_gain": 3.0, + "base_agi": 18, + "agi_gain": 2.2, + "base_int": 15, + "int_gain": 1.6, + "health": 626, + "mana": 255, + "health_regen": 3.8, + "mana_regen": 1.0, + "armor": 5.0, + "damage_min": 63, + "damage_max": 67, + "move_speed": 295, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 800, + "vision_night": 1800, + "turn_rate": 0.6 + }, + { + "id": 61, + "key": "broodmother", + "name": "Broodmother", + "attr": "agi", + "name_loc": "育母蜘蛛", + "roles": [ + "Carry", + "Pusher", + "Escape", + "Nuker" + ], + "tags": [ + "核心", + "推进", + "逃生", + "爆发" + ], + "aliases": [ + "蜘蛛" + ], + "abbr": [ + "brood" + ], + "base_str": 18, + "str_gain": 2.9, + "base_agi": 18, + "agi_gain": 3.4, + "base_int": 18, + "int_gain": 2.0, + "health": 516, + "mana": 291, + "health_regen": 2.05, + "mana_regen": 0.9, + "armor": 2.0, + "damage_min": 45, + "damage_max": 51, + "move_speed": 285, + "attack_range": 175, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 62, + "key": "bounty_hunter", + "name": "Bounty Hunter", + "attr": "agi", + "name_loc": "赏金猎人", + "roles": [ + "Escape", + "Nuker" + ], + "tags": [ + "逃生", + "爆发" + ], + "aliases": [ + "赏金" + ], + "abbr": [ + "bh" + ], + "base_str": 19, + "str_gain": 2.5, + "base_agi": 21, + "agi_gain": 2.6, + "base_int": 20, + "int_gain": 1.9, + "health": 538, + "mana": 315, + "health_regen": 2.65, + "mana_regen": 1.5, + "armor": 8.5, + "damage_min": 54, + "damage_max": 62, + "move_speed": 325, + "attack_range": 150, + "attack_rate": 1.5, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 1000, + "turn_rate": 0.6 + }, + { + "id": 63, + "key": "weaver", + "name": "Weaver", + "attr": "agi", + "name_loc": "编织者", + "roles": [ + "Carry", + "Escape" + ], + "tags": [ + "核心", + "逃生" + ], + "aliases": [ + "蚂蚁" + ], + "abbr": [], + "base_str": 16, + "str_gain": 2.0, + "base_agi": 17, + "agi_gain": 3.3, + "base_int": 20, + "int_gain": 2.0, + "health": 472, + "mana": 315, + "health_regen": 2.85, + "mana_regen": 1.5, + "armor": 3.83, + "damage_min": 54, + "damage_max": 60, + "move_speed": 275, + "attack_range": 425, + "attack_rate": 1.8, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 64, + "key": "jakiro", + "name": "Jakiro", + "attr": "int", + "name_loc": "杰奇洛", + "roles": [ + "Support", + "Nuker", + "Pusher", + "Disabler" + ], + "tags": [ + "辅助", + "推进", + "爆发", + "控制" + ], + "aliases": [ + "双头龙" + ], + "abbr": [], + "base_str": 25, + "str_gain": 2.5, + "base_agi": 15, + "agi_gain": 1.2, + "base_int": 25, + "int_gain": 3.0, + "health": 670, + "mana": 375, + "health_regen": 2.75, + "mana_regen": 1.25, + "armor": 3.5, + "damage_min": 52, + "damage_max": 60, + "move_speed": 290, + "attack_range": 400, + "attack_rate": 1.7, + "projectile_speed": 1100, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.8 + }, + { + "id": 65, + "key": "batrider", + "name": "Batrider", + "attr": "all", + "name_loc": "蝙蝠骑士", + "roles": [ + "Initiator", + "Disabler", + "Escape" + ], + "tags": [ + "逃生", + "先手", + "控制" + ], + "aliases": [ + "蝙蝠" + ], + "abbr": [], + "base_str": 23, + "str_gain": 2.9, + "base_agi": 13, + "agi_gain": 2.0, + "base_int": 22, + "int_gain": 2.9, + "health": 626, + "mana": 339, + "health_regen": 3.55, + "mana_regen": 1.1, + "armor": 4.17, + "damage_min": 53, + "damage_max": 57, + "move_speed": 320, + "attack_range": 400, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 66, + "key": "chen", + "name": "Chen", + "attr": "int", + "name_loc": "陈", + "roles": [ + "Support", + "Pusher" + ], + "tags": [ + "辅助", + "推进" + ], + "aliases": [], + "abbr": [], + "base_str": 23, + "str_gain": 2.0, + "base_agi": 15, + "agi_gain": 2.1, + "base_int": 19, + "int_gain": 3.2, + "health": 626, + "mana": 303, + "health_regen": 2.55, + "mana_regen": 1.45, + "armor": 1.5, + "damage_min": 48, + "damage_max": 58, + "move_speed": 305, + "attack_range": 650, + "attack_rate": 1.7, + "projectile_speed": 1100, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 67, + "key": "spectre", + "name": "Spectre", + "attr": "agi", + "name_loc": "幽鬼", + "roles": [ + "Carry", + "Durable", + "Escape" + ], + "tags": [ + "核心", + "逃生", + "耐久" + ], + "aliases": [], + "abbr": [ + "spec" + ], + "base_str": 21, + "str_gain": 2.4, + "base_agi": 26, + "agi_gain": 2.4, + "base_int": 16, + "int_gain": 1.7, + "health": 582, + "mana": 267, + "health_regen": 3.6, + "mana_regen": 0.8, + "armor": 4.33, + "damage_min": 49, + "damage_max": 53, + "move_speed": 295, + "attack_range": 150, + "attack_rate": 1.8, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 68, + "key": "ancient_apparition", + "name": "Ancient Apparition", + "attr": "int", + "name_loc": "远古冰魄", + "roles": [ + "Support", + "Disabler", + "Nuker" + ], + "tags": [ + "辅助", + "爆发", + "控制" + ], + "aliases": [ + "冰魂" + ], + "abbr": [ + "aa" + ], + "base_str": 20, + "str_gain": 1.9, + "base_agi": 20, + "agi_gain": 2.0, + "base_int": 23, + "int_gain": 3.1, + "health": 560, + "mana": 351, + "health_regen": 2.25, + "mana_regen": 1.15, + "armor": 2.33, + "damage_min": 44, + "damage_max": 54, + "move_speed": 285, + "attack_range": 675, + "attack_rate": 1.7, + "projectile_speed": 1250, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 69, + "key": "doom_bringer", + "name": "Doom", + "attr": "str", + "name_loc": "末日使者", + "roles": [ + "Carry", + "Disabler", + "Initiator", + "Durable", + "Nuker" + ], + "tags": [ + "核心", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "末日" + ], + "abbr": [ + "doom" + ], + "base_str": 24, + "str_gain": 3.6, + "base_agi": 15, + "agi_gain": 1.5, + "base_int": 15, + "int_gain": 1.9, + "health": 648, + "mana": 255, + "health_regen": 3.06, + "mana_regen": 0.75, + "armor": 4.5, + "damage_min": 56, + "damage_max": 66, + "move_speed": 290, + "attack_range": 200, + "attack_rate": 1.9, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 70, + "key": "ursa", + "name": "Ursa", + "attr": "agi", + "name_loc": "熊战士", + "roles": [ + "Carry", + "Durable", + "Disabler" + ], + "tags": [ + "核心", + "控制", + "耐久" + ], + "aliases": [ + "拍拍", + "拍拍熊" + ], + "abbr": [], + "base_str": 25, + "str_gain": 2.4, + "base_agi": 18, + "agi_gain": 2.8, + "base_int": 16, + "int_gain": 1.5, + "health": 670, + "mana": 267, + "health_regen": 3.5, + "mana_regen": 0.8, + "armor": 4.0, + "damage_min": 42, + "damage_max": 46, + "move_speed": 310, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 71, + "key": "spirit_breaker", + "name": "Spirit Breaker", + "attr": "str", + "name_loc": "裂魂人", + "roles": [ + "Carry", + "Initiator", + "Disabler", + "Durable", + "Escape" + ], + "tags": [ + "核心", + "逃生", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "白牛" + ], + "abbr": [ + "sb", + "bara" + ], + "base_str": 28, + "str_gain": 3.3, + "base_agi": 17, + "agi_gain": 1.7, + "base_int": 14, + "int_gain": 1.8, + "health": 736, + "mana": 243, + "health_regen": 4.05, + "mana_regen": 1.2, + "armor": 3.83, + "damage_min": 62, + "damage_max": 72, + "move_speed": 295, + "attack_range": 170, + "attack_rate": 1.9, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 72, + "key": "gyrocopter", + "name": "Gyrocopter", + "attr": "agi", + "name_loc": "矮人直升机", + "roles": [ + "Carry", + "Nuker", + "Disabler" + ], + "tags": [ + "核心", + "爆发", + "控制" + ], + "aliases": [ + "飞机" + ], + "abbr": [ + "gyro" + ], + "base_str": 22, + "str_gain": 2.5, + "base_agi": 25, + "agi_gain": 3.4, + "base_int": 21, + "int_gain": 2.4, + "health": 604, + "mana": 327, + "health_regen": 2.45, + "mana_regen": 1.05, + "armor": 5.17, + "damage_min": 43, + "damage_max": 49, + "move_speed": 315, + "attack_range": 365, + "attack_rate": 1.7, + "projectile_speed": 3000, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 73, + "key": "alchemist", + "name": "Alchemist", + "attr": "str", + "name_loc": "炼金术士", + "roles": [ + "Carry", + "Support", + "Durable", + "Disabler", + "Initiator", + "Nuker" + ], + "tags": [ + "核心", + "辅助", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "炼金" + ], + "abbr": [ + "alch" + ], + "base_str": 23, + "str_gain": 2.7, + "base_agi": 22, + "agi_gain": 1.5, + "base_int": 25, + "int_gain": 1.8, + "health": 626, + "mana": 375, + "health_regen": 2.55, + "mana_regen": 1.25, + "armor": 3.67, + "damage_min": 50, + "damage_max": 56, + "move_speed": 295, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 74, + "key": "invoker", + "name": "Invoker", + "attr": "int", + "name_loc": "祈求者", + "roles": [ + "Carry", + "Nuker", + "Disabler", + "Escape", + "Pusher" + ], + "tags": [ + "核心", + "推进", + "逃生", + "爆发", + "控制" + ], + "aliases": [ + "卡尔" + ], + "abbr": [ + "invo" + ], + "base_str": 19, + "str_gain": 2.5, + "base_agi": 14, + "agi_gain": 2.0, + "base_int": 20, + "int_gain": 4.0, + "health": 538, + "mana": 315, + "health_regen": 2.15, + "mana_regen": 1.0, + "armor": 1.33, + "damage_min": 39, + "damage_max": 45, + "move_speed": 285, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 75, + "key": "silencer", + "name": "Silencer", + "attr": "int", + "name_loc": "沉默术士", + "roles": [ + "Carry", + "Support", + "Disabler", + "Initiator", + "Nuker" + ], + "tags": [ + "核心", + "辅助", + "爆发", + "先手", + "控制" + ], + "aliases": [ + "沉默" + ], + "abbr": [], + "base_str": 18, + "str_gain": 2.2, + "base_agi": 22, + "agi_gain": 2.2, + "base_int": 25, + "int_gain": 2.9, + "health": 516, + "mana": 375, + "health_regen": 2.05, + "mana_regen": 1.25, + "armor": 3.67, + "damage_min": 43, + "damage_max": 57, + "move_speed": 300, + "attack_range": 600, + "attack_rate": 1.6, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 76, + "key": "obsidian_destroyer", + "name": "Outworld Destroyer", + "attr": "int", + "name_loc": "殁境神蚀者", + "roles": [ + "Carry", + "Nuker", + "Disabler" + ], + "tags": [ + "核心", + "爆发", + "控制" + ], + "aliases": [ + "黑鸟" + ], + "abbr": [ + "od" + ], + "base_str": 21, + "str_gain": 2.9, + "base_agi": 17, + "agi_gain": 1.6, + "base_int": 30, + "int_gain": 4.2, + "health": 582, + "mana": 435, + "health_regen": 3.1, + "mana_regen": 1.5, + "armor": 2.83, + "damage_min": 49, + "damage_max": 61, + "move_speed": 315, + "attack_range": 450, + "attack_rate": 1.9, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 77, + "key": "lycan", + "name": "Lycan", + "attr": "str", + "name_loc": "狼人", + "roles": [ + "Carry", + "Pusher", + "Durable", + "Escape" + ], + "tags": [ + "核心", + "推进", + "逃生", + "耐久" + ], + "aliases": [], + "abbr": [], + "base_str": 28, + "str_gain": 3.4, + "base_agi": 18, + "agi_gain": 1.7, + "base_int": 23, + "int_gain": 1.7, + "health": 736, + "mana": 351, + "health_regen": 3.05, + "mana_regen": 1.15, + "armor": 3.0, + "damage_min": 52, + "damage_max": 57, + "move_speed": 305, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 78, + "key": "brewmaster", + "name": "Brewmaster", + "attr": "all", + "name_loc": "酒仙", + "roles": [ + "Carry", + "Initiator", + "Durable", + "Disabler", + "Nuker" + ], + "tags": [ + "核心", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "熊猫", + "大熊猫" + ], + "abbr": [ + "brew" + ], + "base_str": 24, + "str_gain": 3.2, + "base_agi": 19, + "agi_gain": 2.0, + "base_int": 17, + "int_gain": 1.9, + "health": 648, + "mana": 279, + "health_regen": 2.9, + "mana_regen": 0.85, + "armor": 4.17, + "damage_min": 68, + "damage_max": 75, + "move_speed": 310, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 79, + "key": "shadow_demon", + "name": "Shadow Demon", + "attr": "int", + "name_loc": "暗影恶魔", + "roles": [ + "Support", + "Disabler", + "Initiator", + "Nuker" + ], + "tags": [ + "辅助", + "爆发", + "先手", + "控制", + "幻象" + ], + "aliases": [ + "毒狗" + ], + "abbr": [ + "sd" + ], + "base_str": 23, + "str_gain": 2.6, + "base_agi": 18, + "agi_gain": 2.2, + "base_int": 22, + "int_gain": 3.6, + "health": 626, + "mana": 339, + "health_regen": 2.55, + "mana_regen": 1.1, + "armor": 3.0, + "damage_min": 51, + "damage_max": 55, + "move_speed": 290, + "attack_range": 500, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 80, + "key": "lone_druid", + "name": "Lone Druid", + "attr": "agi", + "name_loc": "独行德鲁伊", + "roles": [ + "Carry", + "Pusher", + "Durable" + ], + "tags": [ + "核心", + "推进", + "耐久" + ], + "aliases": [ + "德鲁伊", + "熊德" + ], + "abbr": [ + "ld" + ], + "base_str": 20, + "str_gain": 2.5, + "base_agi": 22, + "agi_gain": 2.8, + "base_int": 13, + "int_gain": 1.4, + "health": 560, + "mana": 231, + "health_regen": 2.25, + "mana_regen": 0.65, + "armor": 3.67, + "damage_min": 44, + "damage_max": 48, + "move_speed": 300, + "attack_range": 550, + "attack_rate": 1.6, + "projectile_speed": 1125, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 81, + "key": "chaos_knight", + "name": "Chaos Knight", + "attr": "str", + "name_loc": "混沌骑士", + "roles": [ + "Carry", + "Disabler", + "Durable", + "Pusher", + "Initiator" + ], + "tags": [ + "核心", + "推进", + "先手", + "控制", + "耐久", + "幻象" + ], + "aliases": [ + "混沌" + ], + "abbr": [ + "ck" + ], + "base_str": 24, + "str_gain": 3.1, + "base_agi": 18, + "agi_gain": 1.8, + "base_int": 18, + "int_gain": 1.2, + "health": 648, + "mana": 291, + "health_regen": 2.65, + "mana_regen": 0.9, + "armor": 5.0, + "damage_min": 53, + "damage_max": 73, + "move_speed": 325, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 82, + "key": "meepo", + "name": "Meepo", + "attr": "agi", + "name_loc": "米波", + "roles": [ + "Carry", + "Escape", + "Nuker", + "Disabler", + "Initiator", + "Pusher" + ], + "tags": [ + "核心", + "推进", + "逃生", + "爆发", + "先手", + "控制" + ], + "aliases": [], + "abbr": [], + "base_str": 24, + "str_gain": 2.2, + "base_agi": 17, + "agi_gain": 2.1, + "base_int": 18, + "int_gain": 1.9, + "health": 648, + "mana": 291, + "health_regen": 2.65, + "mana_regen": 1.15, + "armor": 5.83, + "damage_min": 46, + "damage_max": 52, + "move_speed": 315, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 83, + "key": "treant", + "name": "Treant Protector", + "attr": "str", + "name_loc": "树精卫士", + "roles": [ + "Support", + "Initiator", + "Durable", + "Disabler", + "Escape" + ], + "tags": [ + "辅助", + "逃生", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "大树" + ], + "abbr": [ + "treant" + ], + "base_str": 25, + "str_gain": 3.4, + "base_agi": 15, + "agi_gain": 1.8, + "base_int": 20, + "int_gain": 1.8, + "health": 670, + "mana": 315, + "health_regen": 2.75, + "mana_regen": 1.25, + "armor": 3.5, + "damage_min": 85, + "damage_max": 93, + "move_speed": 280, + "attack_range": 150, + "attack_rate": 1.9, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 84, + "key": "ogre_magi", + "name": "Ogre Magi", + "attr": "str", + "name_loc": "食人魔魔法师", + "roles": [ + "Support", + "Nuker", + "Disabler", + "Durable", + "Initiator" + ], + "tags": [ + "辅助", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "蓝胖" + ], + "abbr": [ + "ogre" + ], + "base_str": 25, + "str_gain": 4.2, + "base_agi": 14, + "agi_gain": 1.5, + "base_int": 0, + "int_gain": 0.0, + "health": 670, + "mana": 120, + "health_regen": 4.0, + "mana_regen": 0.0, + "armor": 6.33, + "damage_min": 70, + "damage_max": 76, + "move_speed": 290, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 85, + "key": "undying", + "name": "Undying", + "attr": "str", + "name_loc": "不朽尸王", + "roles": [ + "Support", + "Durable", + "Disabler", + "Nuker" + ], + "tags": [ + "辅助", + "爆发", + "控制", + "耐久" + ], + "aliases": [ + "尸王" + ], + "abbr": [ + "ud" + ], + "base_str": 22, + "str_gain": 2.4, + "base_agi": 13, + "agi_gain": 1.2, + "base_int": 27, + "int_gain": 2.8, + "health": 604, + "mana": 399, + "health_regen": 1.95, + "mana_regen": 1.6, + "armor": 2.17, + "damage_min": 55, + "damage_max": 63, + "move_speed": 300, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 86, + "key": "rubick", + "name": "Rubick", + "attr": "int", + "name_loc": "拉比克", + "roles": [ + "Support", + "Disabler", + "Nuker" + ], + "tags": [ + "辅助", + "爆发", + "控制" + ], + "aliases": [ + "抄袭", + "魔术师" + ], + "abbr": [], + "base_str": 21, + "str_gain": 2.0, + "base_agi": 23, + "agi_gain": 2.5, + "base_int": 24, + "int_gain": 3.7, + "health": 582, + "mana": 363, + "health_regen": 2.35, + "mana_regen": 1.7, + "armor": 4.83, + "damage_min": 49, + "damage_max": 55, + "move_speed": 290, + "attack_range": 550, + "attack_rate": 1.7, + "projectile_speed": 1125, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 87, + "key": "disruptor", + "name": "Disruptor", + "attr": "int", + "name_loc": "干扰者", + "roles": [ + "Support", + "Disabler", + "Nuker", + "Initiator" + ], + "tags": [ + "辅助", + "爆发", + "先手", + "控制" + ], + "aliases": [ + "萨尔" + ], + "abbr": [], + "base_str": 21, + "str_gain": 2.4, + "base_agi": 15, + "agi_gain": 1.4, + "base_int": 20, + "int_gain": 2.9, + "health": 582, + "mana": 315, + "health_regen": 2.35, + "mana_regen": 1.0, + "armor": 2.5, + "damage_min": 47, + "damage_max": 51, + "move_speed": 295, + "attack_range": 625, + "attack_rate": 1.7, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 88, + "key": "nyx_assassin", + "name": "Nyx Assassin", + "attr": "all", + "name_loc": "司夜刺客", + "roles": [ + "Disabler", + "Nuker", + "Initiator", + "Escape" + ], + "tags": [ + "逃生", + "爆发", + "先手", + "控制" + ], + "aliases": [ + "小强" + ], + "abbr": [ + "nyx" + ], + "base_str": 17, + "str_gain": 2.7, + "base_agi": 18, + "agi_gain": 2.9, + "base_int": 21, + "int_gain": 2.6, + "health": 494, + "mana": 327, + "health_regen": 3.7, + "mana_regen": 1.05, + "armor": 4.0, + "damage_min": 65, + "damage_max": 69, + "move_speed": 310, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 89, + "key": "naga_siren", + "name": "Naga Siren", + "attr": "agi", + "name_loc": "娜迦海妖", + "roles": [ + "Carry", + "Support", + "Pusher", + "Disabler", + "Initiator", + "Escape" + ], + "tags": [ + "核心", + "辅助", + "推进", + "逃生", + "先手", + "控制", + "幻象" + ], + "aliases": [ + "小娜迦" + ], + "abbr": [ + "naga" + ], + "base_str": 21, + "str_gain": 2.4, + "base_agi": 22, + "agi_gain": 3.4, + "base_int": 19, + "int_gain": 2.0, + "health": 582, + "mana": 303, + "health_regen": 3.1, + "mana_regen": 0.95, + "armor": 4.67, + "damage_min": 45, + "damage_max": 47, + "move_speed": 330, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 90, + "key": "keeper_of_the_light", + "name": "Keeper of the Light", + "attr": "int", + "name_loc": "光之守卫", + "roles": [ + "Support", + "Nuker", + "Disabler" + ], + "tags": [ + "辅助", + "爆发", + "控制" + ], + "aliases": [ + "光法" + ], + "abbr": [ + "kotl" + ], + "base_str": 18, + "str_gain": 2.3, + "base_agi": 15, + "agi_gain": 1.6, + "base_int": 24, + "int_gain": 3.3, + "health": 516, + "mana": 363, + "health_regen": 2.05, + "mana_regen": 1.2, + "armor": 2.5, + "damage_min": 44, + "damage_max": 51, + "move_speed": 315, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 91, + "key": "wisp", + "name": "Io", + "attr": "all", + "name_loc": "艾欧", + "roles": [ + "Support", + "Escape", + "Nuker" + ], + "tags": [ + "辅助", + "逃生", + "爆发" + ], + "aliases": [ + "小精灵" + ], + "abbr": [ + "io" + ], + "base_str": 19, + "str_gain": 3.0, + "base_agi": 14, + "agi_gain": 1.6, + "base_int": 21, + "int_gain": 1.7, + "health": 538, + "mana": 327, + "health_regen": 2.15, + "mana_regen": 1.05, + "armor": 3.33, + "damage_min": 59, + "damage_max": 65, + "move_speed": 320, + "attack_range": 500, + "attack_rate": 1.7, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 92, + "key": "visage", + "name": "Visage", + "attr": "all", + "name_loc": "维萨吉", + "roles": [ + "Support", + "Nuker", + "Durable", + "Disabler", + "Pusher" + ], + "tags": [ + "辅助", + "推进", + "爆发", + "控制", + "耐久" + ], + "aliases": [ + "死灵龙" + ], + "abbr": [], + "base_str": 22, + "str_gain": 2.8, + "base_agi": 11, + "agi_gain": 1.3, + "base_int": 22, + "int_gain": 2.9, + "health": 604, + "mana": 339, + "health_regen": 2.45, + "mana_regen": 1.1, + "armor": 1.83, + "damage_min": 60, + "damage_max": 70, + "move_speed": 285, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 1400, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 93, + "key": "slark", + "name": "Slark", + "attr": "agi", + "name_loc": "斯拉克", + "roles": [ + "Carry", + "Escape", + "Disabler", + "Nuker" + ], + "tags": [ + "核心", + "逃生", + "爆发", + "控制" + ], + "aliases": [ + "小鱼人", + "小鱼" + ], + "abbr": [], + "base_str": 20, + "str_gain": 2.1, + "base_agi": 21, + "agi_gain": 1.5, + "base_int": 16, + "int_gain": 1.9, + "health": 560, + "mana": 267, + "health_regen": 2.25, + "mana_regen": 0.8, + "armor": 3.5, + "damage_min": 58, + "damage_max": 64, + "move_speed": 300, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 1800, + "turn_rate": 0.7 + }, + { + "id": 94, + "key": "medusa", + "name": "Medusa", + "attr": "agi", + "name_loc": "美杜莎", + "roles": [ + "Carry", + "Disabler", + "Durable" + ], + "tags": [ + "核心", + "控制", + "耐久" + ], + "aliases": [ + "一姐", + "大娜迦" + ], + "abbr": [ + "medi" + ], + "base_str": 0, + "str_gain": 0.0, + "base_agi": 22, + "agi_gain": 3.6, + "base_int": 25, + "int_gain": 3.6, + "health": 120, + "mana": 375, + "health_regen": 0.25, + "mana_regen": 1.25, + "armor": 2.67, + "damage_min": 48, + "damage_max": 54, + "move_speed": 275, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 95, + "key": "troll_warlord", + "name": "Troll Warlord", + "attr": "agi", + "name_loc": "巨魔战将", + "roles": [ + "Carry", + "Pusher", + "Disabler", + "Durable" + ], + "tags": [ + "核心", + "推进", + "控制", + "耐久" + ], + "aliases": [ + "巨魔" + ], + "abbr": [ + "troll" + ], + "base_str": 21, + "str_gain": 2.5, + "base_agi": 23, + "agi_gain": 3.3, + "base_int": 13, + "int_gain": 1.0, + "health": 582, + "mana": 231, + "health_regen": 2.35, + "mana_regen": 0.65, + "armor": 2.83, + "damage_min": 50, + "damage_max": 58, + "move_speed": 290, + "attack_range": 500, + "attack_rate": 1.7, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 96, + "key": "centaur", + "name": "Centaur Warrunner", + "attr": "str", + "name_loc": "半人马战行者", + "roles": [ + "Durable", + "Initiator", + "Disabler", + "Nuker", + "Escape" + ], + "tags": [ + "逃生", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "人马", + "小牛" + ], + "abbr": [ + "cent" + ], + "base_str": 27, + "str_gain": 4.2, + "base_agi": 15, + "agi_gain": 1.0, + "base_int": 15, + "int_gain": 1.6, + "health": 714, + "mana": 255, + "health_regen": 5.7, + "mana_regen": 0.75, + "armor": 0.5, + "damage_min": 63, + "damage_max": 65, + "move_speed": 300, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 97, + "key": "magnataur", + "name": "Magnus", + "attr": "all", + "name_loc": "马格纳斯", + "roles": [ + "Initiator", + "Disabler", + "Nuker", + "Escape" + ], + "tags": [ + "逃生", + "爆发", + "先手", + "控制" + ], + "aliases": [ + "猛犸" + ], + "abbr": [ + "mag" + ], + "base_str": 23, + "str_gain": 3.1, + "base_agi": 14, + "agi_gain": 2.0, + "base_int": 20, + "int_gain": 2.1, + "health": 626, + "mana": 315, + "health_regen": 2.8, + "mana_regen": 1.5, + "armor": 3.33, + "damage_min": 71, + "damage_max": 79, + "move_speed": 305, + "attack_range": 200, + "attack_rate": 1.8, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.8 + }, + { + "id": 98, + "key": "shredder", + "name": "Timbersaw", + "attr": "str", + "name_loc": "伐木机", + "roles": [ + "Nuker", + "Durable", + "Escape" + ], + "tags": [ + "逃生", + "爆发", + "耐久" + ], + "aliases": [], + "abbr": [ + "timber" + ], + "base_str": 23, + "str_gain": 3.6, + "base_agi": 16, + "agi_gain": 1.6, + "base_int": 23, + "int_gain": 2.7, + "health": 626, + "mana": 351, + "health_regen": 2.55, + "mana_regen": 1.65, + "armor": 2.67, + "damage_min": 46, + "damage_max": 50, + "move_speed": 280, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 99, + "key": "bristleback", + "name": "Bristleback", + "attr": "str", + "name_loc": "钢背兽", + "roles": [ + "Carry", + "Durable", + "Initiator", + "Nuker" + ], + "tags": [ + "核心", + "爆发", + "先手", + "耐久" + ], + "aliases": [ + "钢背", + "刚被" + ], + "abbr": [ + "bb" + ], + "base_str": 22, + "str_gain": 2.8, + "base_agi": 17, + "agi_gain": 1.8, + "base_int": 14, + "int_gain": 2.8, + "health": 604, + "mana": 243, + "health_regen": 3.2, + "mana_regen": 1.1, + "armor": 3.83, + "damage_min": 53, + "damage_max": 59, + "move_speed": 295, + "attack_range": 150, + "attack_rate": 1.8, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 100, + "key": "tusk", + "name": "Tusk", + "attr": "str", + "name_loc": "巨牙海民", + "roles": [ + "Initiator", + "Disabler", + "Nuker" + ], + "tags": [ + "爆发", + "先手", + "控制" + ], + "aliases": [ + "海民" + ], + "abbr": [], + "base_str": 23, + "str_gain": 3.9, + "base_agi": 23, + "agi_gain": 1.9, + "base_int": 18, + "int_gain": 1.7, + "health": 626, + "mana": 291, + "health_regen": 3.3, + "mana_regen": 1.4, + "armor": 4.83, + "damage_min": 50, + "damage_max": 54, + "move_speed": 310, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 101, + "key": "skywrath_mage", + "name": "Skywrath Mage", + "attr": "int", + "name_loc": "天怒法师", + "roles": [ + "Support", + "Nuker", + "Disabler" + ], + "tags": [ + "辅助", + "爆发", + "控制" + ], + "aliases": [ + "天怒" + ], + "abbr": [ + "sky" + ], + "base_str": 21, + "str_gain": 2.0, + "base_agi": 13, + "agi_gain": 0.8, + "base_int": 25, + "int_gain": 4.1, + "health": 582, + "mana": 375, + "health_regen": 2.35, + "mana_regen": 1.5, + "armor": 0.17, + "damage_min": 41, + "damage_max": 51, + "move_speed": 325, + "attack_range": 625, + "attack_rate": 1.7, + "projectile_speed": 1000, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 102, + "key": "abaddon", + "name": "Abaddon", + "attr": "all", + "name_loc": "亚巴顿", + "roles": [ + "Support", + "Carry", + "Durable" + ], + "tags": [ + "核心", + "辅助", + "耐久" + ], + "aliases": [], + "abbr": [ + "aba" + ], + "base_str": 21, + "str_gain": 2.6, + "base_agi": 22, + "agi_gain": 1.5, + "base_int": 18, + "int_gain": 2.0, + "health": 582, + "mana": 291, + "health_regen": 3.1, + "mana_regen": 0.9, + "armor": 2.67, + "damage_min": 65, + "damage_max": 75, + "move_speed": 325, + "attack_range": 150, + "attack_rate": 1.5, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 103, + "key": "elder_titan", + "name": "Elder Titan", + "attr": "str", + "name_loc": "上古巨神", + "roles": [ + "Initiator", + "Disabler", + "Nuker", + "Durable" + ], + "tags": [ + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "大牛" + ], + "abbr": [ + "et" + ], + "base_str": 26, + "str_gain": 3.0, + "base_agi": 16, + "agi_gain": 1.8, + "base_int": 23, + "int_gain": 1.6, + "health": 692, + "mana": 351, + "health_regen": 3.35, + "mana_regen": 1.4, + "armor": 2.67, + "damage_min": 49, + "damage_max": 59, + "move_speed": 305, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 104, + "key": "legion_commander", + "name": "Legion Commander", + "attr": "str", + "name_loc": "军团指挥官", + "roles": [ + "Carry", + "Disabler", + "Initiator", + "Durable", + "Nuker" + ], + "tags": [ + "核心", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "军团" + ], + "abbr": [ + "lc" + ], + "base_str": 24, + "str_gain": 3.1, + "base_agi": 18, + "agi_gain": 1.7, + "base_int": 20, + "int_gain": 2.2, + "health": 648, + "mana": 315, + "health_regen": 2.65, + "mana_regen": 1.0, + "armor": 2.0, + "damage_min": 57, + "damage_max": 61, + "move_speed": 325, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 105, + "key": "techies", + "name": "Techies", + "attr": "all", + "name_loc": "工程师", + "roles": [ + "Nuker", + "Disabler" + ], + "tags": [ + "爆发", + "控制" + ], + "aliases": [ + "炸弹人" + ], + "abbr": [], + "base_str": 19, + "str_gain": 2.6, + "base_agi": 16, + "agi_gain": 1.8, + "base_int": 25, + "int_gain": 3.0, + "health": 538, + "mana": 375, + "health_regen": 2.9, + "mana_regen": 2.25, + "armor": 4.67, + "damage_min": 62, + "damage_max": 64, + "move_speed": 310, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 106, + "key": "ember_spirit", + "name": "Ember Spirit", + "attr": "agi", + "name_loc": "灰烬之灵", + "roles": [ + "Carry", + "Escape", + "Nuker", + "Disabler", + "Initiator" + ], + "tags": [ + "核心", + "逃生", + "爆发", + "先手", + "控制" + ], + "aliases": [ + "火猫" + ], + "abbr": [ + "ember" + ], + "base_str": 22, + "str_gain": 2.5, + "base_agi": 22, + "agi_gain": 3.2, + "base_int": 20, + "int_gain": 2.2, + "health": 604, + "mana": 315, + "health_regen": 2.7, + "mana_regen": 1.0, + "armor": 3.67, + "damage_min": 55, + "damage_max": 59, + "move_speed": 300, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.8 + }, + { + "id": 107, + "key": "earth_spirit", + "name": "Earth Spirit", + "attr": "str", + "name_loc": "大地之灵", + "roles": [ + "Nuker", + "Escape", + "Disabler", + "Initiator", + "Durable" + ], + "tags": [ + "逃生", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "土猫" + ], + "abbr": [ + "earth" + ], + "base_str": 22, + "str_gain": 3.8, + "base_agi": 17, + "agi_gain": 2.4, + "base_int": 18, + "int_gain": 2.1, + "health": 604, + "mana": 291, + "health_regen": 3.2, + "mana_regen": 0.9, + "armor": 2.83, + "damage_min": 47, + "damage_max": 51, + "move_speed": 290, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 108, + "key": "abyssal_underlord", + "name": "Underlord", + "attr": "str", + "name_loc": "孽主", + "roles": [ + "Support", + "Nuker", + "Disabler", + "Durable", + "Escape" + ], + "tags": [ + "辅助", + "逃生", + "爆发", + "控制", + "耐久" + ], + "aliases": [ + "大屁股" + ], + "abbr": [ + "underlord" + ], + "base_str": 25, + "str_gain": 3.2, + "base_agi": 12, + "agi_gain": 1.6, + "base_int": 18, + "int_gain": 2.3, + "health": 670, + "mana": 291, + "health_regen": 2.75, + "mana_regen": 0.9, + "armor": 5.0, + "damage_min": 62, + "damage_max": 68, + "move_speed": 290, + "attack_range": 200, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 109, + "key": "terrorblade", + "name": "Terrorblade", + "attr": "agi", + "name_loc": "恐怖利刃", + "roles": [ + "Carry", + "Pusher", + "Nuker" + ], + "tags": [ + "核心", + "推进", + "爆发", + "幻象" + ], + "aliases": [], + "abbr": [ + "tb" + ], + "base_str": 18, + "str_gain": 2.0, + "base_agi": 23, + "agi_gain": 4.0, + "base_int": 19, + "int_gain": 1.6, + "health": 516, + "mana": 303, + "health_regen": 2.3, + "mana_regen": 0.95, + "armor": 9.83, + "damage_min": 49, + "damage_max": 55, + "move_speed": 315, + "attack_range": 150, + "attack_rate": 1.5, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.8 + }, + { + "id": 110, + "key": "phoenix", + "name": "Phoenix", + "attr": "str", + "name_loc": "凤凰", + "roles": [ + "Support", + "Nuker", + "Initiator", + "Escape", + "Disabler" + ], + "tags": [ + "辅助", + "逃生", + "爆发", + "先手", + "控制" + ], + "aliases": [], + "abbr": [], + "base_str": 23, + "str_gain": 3.3, + "base_agi": 12, + "agi_gain": 1.5, + "base_int": 16, + "int_gain": 1.8, + "health": 626, + "mana": 267, + "health_regen": 4.3, + "mana_regen": 0.8, + "armor": 1.0, + "damage_min": 44, + "damage_max": 54, + "move_speed": 280, + "attack_range": 525, + "attack_rate": 1.7, + "projectile_speed": 1100, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 111, + "key": "oracle", + "name": "Oracle", + "attr": "int", + "name_loc": "神谕者", + "roles": [ + "Support", + "Nuker", + "Disabler", + "Escape" + ], + "tags": [ + "辅助", + "逃生", + "爆发", + "控制" + ], + "aliases": [], + "abbr": [], + "base_str": 20, + "str_gain": 2.2, + "base_agi": 15, + "agi_gain": 1.7, + "base_int": 26, + "int_gain": 3.6, + "health": 560, + "mana": 387, + "health_regen": 2.25, + "mana_regen": 1.8, + "armor": 3.5, + "damage_min": 39, + "damage_max": 45, + "move_speed": 295, + "attack_range": 625, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 112, + "key": "winter_wyvern", + "name": "Winter Wyvern", + "attr": "int", + "name_loc": "寒冬飞龙", + "roles": [ + "Support", + "Disabler", + "Nuker" + ], + "tags": [ + "辅助", + "爆发", + "控制" + ], + "aliases": [ + "冰龙" + ], + "abbr": [ + "ww" + ], + "base_str": 22, + "str_gain": 2.5, + "base_agi": 16, + "agi_gain": 1.7, + "base_int": 26, + "int_gain": 3.6, + "health": 604, + "mana": 387, + "health_regen": 2.45, + "mana_regen": 1.8, + "armor": 3.67, + "damage_min": 42, + "damage_max": 49, + "move_speed": 285, + "attack_range": 450, + "attack_rate": 1.7, + "projectile_speed": 700, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 1200, + "turn_rate": 0.6 + }, + { + "id": 113, + "key": "arc_warden", + "name": "Arc Warden", + "attr": "all", + "name_loc": "天穹守望者", + "roles": [ + "Carry", + "Escape", + "Nuker" + ], + "tags": [ + "核心", + "逃生", + "爆发" + ], + "aliases": [ + "电狗" + ], + "abbr": [ + "aw" + ], + "base_str": 20, + "str_gain": 2.2, + "base_agi": 20, + "agi_gain": 2.7, + "base_int": 24, + "int_gain": 2.6, + "health": 560, + "mana": 363, + "health_regen": 2.25, + "mana_regen": 1.2, + "armor": 3.33, + "damage_min": 68, + "damage_max": 74, + "move_speed": 300, + "attack_range": 625, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 114, + "key": "monkey_king", + "name": "Monkey King", + "attr": "agi", + "name_loc": "齐天大圣", + "roles": [ + "Carry", + "Escape", + "Disabler", + "Initiator" + ], + "tags": [ + "核心", + "逃生", + "先手", + "控制" + ], + "aliases": [ + "大圣", + "猴哥" + ], + "abbr": [ + "mk" + ], + "base_str": 19, + "str_gain": 2.8, + "base_agi": 24, + "agi_gain": 3.7, + "base_int": 20, + "int_gain": 1.8, + "health": 538, + "mana": 315, + "health_regen": 2.9, + "mana_regen": 1.0, + "armor": 5.0, + "damage_min": 53, + "damage_max": 57, + "move_speed": 300, + "attack_range": 300, + "attack_rate": 1.6, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 119, + "key": "dark_willow", + "name": "Dark Willow", + "attr": "int", + "name_loc": "邪影芳灵", + "roles": [ + "Support", + "Nuker", + "Disabler", + "Escape" + ], + "tags": [ + "辅助", + "逃生", + "爆发", + "控制" + ], + "aliases": [ + "小仙女" + ], + "abbr": [ + "willow" + ], + "base_str": 20, + "str_gain": 2.0, + "base_agi": 18, + "agi_gain": 1.6, + "base_int": 21, + "int_gain": 3.5, + "health": 560, + "mana": 327, + "health_regen": 2.75, + "mana_regen": 1.05, + "armor": 3.0, + "damage_min": 48, + "damage_max": 56, + "move_speed": 290, + "attack_range": 475, + "attack_rate": 1.5, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 120, + "key": "pangolier", + "name": "Pangolier", + "attr": "all", + "name_loc": "石鳞剑士", + "roles": [ + "Carry", + "Nuker", + "Disabler", + "Durable", + "Escape", + "Initiator" + ], + "tags": [ + "核心", + "逃生", + "爆发", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "滚滚" + ], + "abbr": [ + "pango" + ], + "base_str": 20, + "str_gain": 2.7, + "base_agi": 18, + "agi_gain": 3.2, + "base_int": 16, + "int_gain": 2.5, + "health": 560, + "mana": 267, + "health_regen": 3.25, + "mana_regen": 0.8, + "armor": 5.0, + "damage_min": 64, + "damage_max": 70, + "move_speed": 300, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 121, + "key": "grimstroke", + "name": "Grimstroke", + "attr": "int", + "name_loc": "天涯墨客", + "roles": [ + "Support", + "Nuker", + "Disabler", + "Escape" + ], + "tags": [ + "辅助", + "逃生", + "爆发", + "控制" + ], + "aliases": [ + "墨客" + ], + "abbr": [ + "grim" + ], + "base_str": 21, + "str_gain": 2.4, + "base_agi": 18, + "agi_gain": 1.9, + "base_int": 25, + "int_gain": 3.8, + "health": 582, + "mana": 375, + "health_regen": 2.35, + "mana_regen": 1.25, + "armor": 3.0, + "damage_min": 47, + "damage_max": 51, + "move_speed": 290, + "attack_range": 600, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 123, + "key": "hoodwink", + "name": "Hoodwink", + "attr": "agi", + "name_loc": "森海飞霞", + "roles": [ + "Support", + "Nuker", + "Escape", + "Disabler" + ], + "tags": [ + "辅助", + "逃生", + "爆发", + "控制" + ], + "aliases": [ + "小松鼠", + "松鼠" + ], + "abbr": [ + "hw" + ], + "base_str": 17, + "str_gain": 2.0, + "base_agi": 25, + "agi_gain": 3.6, + "base_int": 21, + "int_gain": 2.9, + "health": 494, + "mana": 327, + "health_regen": 1.95, + "mana_regen": 1.05, + "armor": 4.17, + "damage_min": 47, + "damage_max": 54, + "move_speed": 315, + "attack_range": 575, + "attack_rate": 2.0, + "projectile_speed": 1800, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 126, + "key": "void_spirit", + "name": "Void Spirit", + "attr": "all", + "name_loc": "虚无之灵", + "roles": [ + "Carry", + "Escape", + "Nuker", + "Disabler" + ], + "tags": [ + "核心", + "逃生", + "爆发", + "控制" + ], + "aliases": [ + "紫猫" + ], + "abbr": [ + "void" + ], + "base_str": 22, + "str_gain": 2.6, + "base_agi": 21, + "agi_gain": 2.2, + "base_int": 24, + "int_gain": 3.1, + "health": 604, + "mana": 363, + "health_regen": 2.45, + "mana_regen": 1.8, + "armor": 2.5, + "damage_min": 65, + "damage_max": 69, + "move_speed": 290, + "attack_range": 200, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 128, + "key": "snapfire", + "name": "Snapfire", + "attr": "all", + "name_loc": "电炎绝手", + "roles": [ + "Support", + "Nuker", + "Disabler", + "Escape" + ], + "tags": [ + "辅助", + "逃生", + "爆发", + "控制" + ], + "aliases": [ + "老奶奶" + ], + "abbr": [ + "snap" + ], + "base_str": 21, + "str_gain": 3.5, + "base_agi": 16, + "agi_gain": 1.9, + "base_int": 21, + "int_gain": 2.2, + "health": 582, + "mana": 327, + "health_regen": 2.85, + "mana_regen": 1.05, + "armor": 3.67, + "damage_min": 66, + "damage_max": 72, + "move_speed": 300, + "attack_range": 400, + "attack_rate": 1.8, + "projectile_speed": 1800, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 129, + "key": "mars", + "name": "Mars", + "attr": "str", + "name_loc": "玛尔斯", + "roles": [ + "Carry", + "Initiator", + "Disabler", + "Durable" + ], + "tags": [ + "核心", + "先手", + "控制", + "耐久" + ], + "aliases": [ + "马尔斯", + "战神" + ], + "abbr": [], + "base_str": 23, + "str_gain": 3.7, + "base_agi": 18, + "agi_gain": 1.7, + "base_int": 21, + "int_gain": 2.2, + "health": 626, + "mana": 327, + "health_regen": 2.55, + "mana_regen": 1.55, + "armor": 2.0, + "damage_min": 49, + "damage_max": 57, + "move_speed": 310, + "attack_range": 250, + "attack_rate": 1.8, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.8 + }, + { + "id": 131, + "key": "ringmaster", + "name": "Ringmaster", + "attr": "int", + "name_loc": "百戏大王", + "roles": [ + "Support", + "Nuker", + "Escape", + "Disabler" + ], + "tags": [ + "辅助", + "逃生", + "爆发", + "控制" + ], + "aliases": [ + "百戏" + ], + "abbr": [ + "ring" + ], + "base_str": 21, + "str_gain": 2.1, + "base_agi": 11, + "agi_gain": 1.6, + "base_int": 25, + "int_gain": 3.0, + "health": 582, + "mana": 375, + "health_regen": 2.35, + "mana_regen": 1.25, + "armor": 2.83, + "damage_min": 48, + "damage_max": 58, + "move_speed": 310, + "attack_range": 575, + "attack_rate": 1.7, + "projectile_speed": 1200, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 135, + "key": "dawnbreaker", + "name": "Dawnbreaker", + "attr": "str", + "name_loc": "破晓辰星", + "roles": [ + "Carry", + "Durable" + ], + "tags": [ + "核心", + "耐久" + ], + "aliases": [ + "大锤" + ], + "abbr": [ + "db", + "dawn" + ], + "base_str": 25, + "str_gain": 3.4, + "base_agi": 14, + "agi_gain": 1.7, + "base_int": 20, + "int_gain": 2.0, + "health": 670, + "mana": 315, + "health_regen": 3.0, + "mana_regen": 1.0, + "armor": 4.33, + "damage_min": 56, + "damage_max": 60, + "move_speed": 300, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 136, + "key": "marci", + "name": "Marci", + "attr": "all", + "name_loc": "玛西", + "roles": [ + "Support", + "Carry", + "Initiator", + "Disabler", + "Escape" + ], + "tags": [ + "核心", + "辅助", + "逃生", + "先手", + "控制" + ], + "aliases": [ + "马西" + ], + "abbr": [], + "base_str": 23, + "str_gain": 3.2, + "base_agi": 20, + "agi_gain": 2.2, + "base_int": 19, + "int_gain": 1.9, + "health": 626, + "mana": 303, + "health_regen": 2.55, + "mana_regen": 0.95, + "armor": 3.33, + "damage_min": 72, + "damage_max": 76, + "move_speed": 315, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.9 + }, + { + "id": 137, + "key": "primal_beast", + "name": "Primal Beast", + "attr": "str", + "name_loc": "獸", + "roles": [ + "Initiator", + "Durable", + "Disabler" + ], + "tags": [ + "先手", + "控制", + "耐久" + ], + "aliases": [ + "兽", + "一霸" + ], + "abbr": [ + "pb", + "primal" + ], + "base_str": 26, + "str_gain": 4.3, + "base_agi": 15, + "agi_gain": 1.0, + "base_int": 16, + "int_gain": 1.7, + "health": 692, + "mana": 267, + "health_regen": 2.85, + "mana_regen": 0.8, + "armor": 4.5, + "damage_min": 62, + "damage_max": 64, + "move_speed": 305, + "attack_range": 150, + "attack_rate": 1.8, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.6 + }, + { + "id": 138, + "key": "muerta", + "name": "Muerta", + "attr": "int", + "name_loc": "琼英碧灵", + "roles": [ + "Carry", + "Nuker", + "Disabler" + ], + "tags": [ + "核心", + "爆发", + "控制" + ], + "aliases": [ + "穷逼", + "琼碧" + ], + "abbr": [], + "base_str": 19, + "str_gain": 2.0, + "base_agi": 21, + "agi_gain": 2.7, + "base_int": 23, + "int_gain": 3.6, + "health": 538, + "mana": 351, + "health_regen": 2.15, + "mana_regen": 1.15, + "armor": 3.5, + "damage_min": 47, + "damage_max": 55, + "move_speed": 295, + "attack_range": 575, + "attack_rate": 1.7, + "projectile_speed": 3000, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + }, + { + "id": 145, + "key": "kez", + "name": "Kez", + "attr": "agi", + "name_loc": "凯", + "roles": [ + "Carry", + "Escape", + "Disabler" + ], + "tags": [ + "核心", + "逃生", + "控制" + ], + "aliases": [], + "abbr": [], + "base_str": 20, + "str_gain": 2.8, + "base_agi": 27, + "agi_gain": 3.5, + "base_int": 20, + "int_gain": 1.7, + "health": 560, + "mana": 315, + "health_regen": 3.5, + "mana_regen": 1.25, + "armor": 5.5, + "damage_min": 50, + "damage_max": 56, + "move_speed": 310, + "attack_range": 225, + "attack_rate": 1.9, + "projectile_speed": 900, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 1.0 + }, + { + "id": 155, + "key": "largo", + "name": "Largo", + "attr": "str", + "name_loc": "朗戈", + "roles": [ + "Durable", + "Disabler", + "Support" + ], + "tags": [ + "辅助", + "控制", + "耐久" + ], + "aliases": [], + "abbr": [], + "base_str": 24, + "str_gain": 3.6, + "base_agi": 14, + "agi_gain": 1.3, + "base_int": 21, + "int_gain": 2.6, + "health": 648, + "mana": 327, + "health_regen": 2.65, + "mana_regen": 1.05, + "armor": 4.33, + "damage_min": 63, + "damage_max": 69, + "move_speed": 290, + "attack_range": 150, + "attack_rate": 1.7, + "projectile_speed": 0, + "magic_resist": 25, + "vision_day": 1800, + "vision_night": 800, + "turn_rate": 0.7 + } +] diff --git a/shared/fetch_matchups.py b/shared/fetch_matchups.py deleted file mode 100644 index 919ba6a..0000000 --- a/shared/fetch_matchups.py +++ /dev/null @@ -1,86 +0,0 @@ -"""Fetch OpenDota hero matchups into data/matchups.json (for audit_relations). - -Usage: - python fetch_matchups.py - python fetch_matchups.py --delay 1.0 -""" - -from __future__ import annotations - -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parents[1])) - -import argparse -import json -import time -import urllib.error -from datetime import datetime, timezone - -from shared.grid import hero_table -from shared.http_utils import http_json -from shared.paths import SHARED_DATA - -API = "https://api.opendota.com/api/heroes/{hero_id}/matchups" -OUT = SHARED_DATA / "matchups.json" - - -def fetch_one(hero_id: int, timeout: float = 30.0) -> list[dict]: - return http_json(API.format(hero_id=hero_id), timeout=int(timeout)) - - -def main() -> None: - ap = argparse.ArgumentParser(description=__doc__) - ap.add_argument("--delay", type=float, default=1.0, help="seconds between requests") - ap.add_argument("--out", type=Path, default=OUT) - args = ap.parse_args() - - table = hero_table() - ids = sorted({int(h["id"]) for h in table}) - by_hero: dict[str, dict] = {} - if args.out.is_file(): - try: - prev = json.loads(args.out.read_text(encoding="utf-8")) - by_hero = dict(prev.get("by_hero") or {}) - print(f"resuming with {len(by_hero)} heroes already cached", flush=True) - except (OSError, json.JSONDecodeError): - pass - - pending = [i for i in ids if str(i) not in by_hero] - print(f"fetching {len(pending)} / {len(ids)} heroes -> {args.out}", flush=True) - for n, hid in enumerate(pending, start=1): - try: - rows = fetch_one(hid) - except (urllib.error.URLError, TimeoutError, json.JSONDecodeError) as e: - print(f" [{n}/{len(pending)}] hero {hid} failed: {e}", flush=True) - time.sleep(args.delay * 2) - continue - cell: dict[str, dict] = {} - for r in rows: - opp = r.get("hero_id") - games = int(r.get("games_played") or 0) - wins = int(r.get("wins") or 0) - if opp is None or games <= 0: - continue - cell[str(int(opp))] = {"games": games, "wins": wins} - by_hero[str(hid)] = cell - print(f" [{n}/{len(pending)}] hero {hid}: {len(cell)} matchups", flush=True) - args.out.parent.mkdir(parents=True, exist_ok=True) - payload = { - "fetched_at": datetime.now(timezone.utc).isoformat(), - "source": "opendota", - "attribution": "https://opendota.com", - "by_hero": by_hero, - } - args.out.write_text( - json.dumps(payload, ensure_ascii=False, indent=2) + "\n", - encoding="utf-8", - ) - time.sleep(args.delay) - - print(f"done: {len(by_hero)} heroes in {args.out}", flush=True) - - -if __name__ == "__main__": - main() diff --git a/shared/matchup_cross.py b/shared/matchup_cross.py deleted file mode 100644 index d5f356f..0000000 --- a/shared/matchup_cross.py +++ /dev/null @@ -1,281 +0,0 @@ -"""Cross-check STRATZ web matchup tops against OpenDota hero matchups. - -Web-only observation evidence — never merge into relations.json or recommend. - -Statuses: - agree — OpenDota baseline-adjusted advantage agrees with STRATZ direction - conflict — OpenDota disagrees with STRATZ direction (enough games) - weak — OpenDota sample too small or advantage near zero -""" - -from __future__ import annotations - -from typing import Any - -# Defaults aligned with shared/audit_relations.py thresholds. -DEFAULT_MIN_GAMES = 80 -DEFAULT_ODOTA_AGREE = 0.015 -DEFAULT_ODOTA_DISAGREE = -0.015 - - -def matchup_wr(by_hero: dict, aid: int, bid: int) -> tuple[float | None, int]: - cell = (by_hero.get(str(aid)) or {}).get(str(bid)) - if not cell: - return None, 0 - games = int(cell.get("games") or 0) - wins = int(cell.get("wins") or 0) - if games <= 0: - return None, 0 - return wins / games, games - - -def build_baseline(by_hero: dict) -> dict[int, float]: - out: dict[int, float] = {} - for hid_s, opps in by_hero.items(): - tw = tg = 0 - for cell in opps.values(): - g = int(cell.get("games") or 0) - w = int(cell.get("wins") or 0) - tw += w - tg += g - if tg > 0: - out[int(hid_s)] = tw / tg - return out - - -def odota_adv( - by_hero: dict, baseline: dict[int, float], aid: int, bid: int -) -> tuple[float | None, int]: - wr, games = matchup_wr(by_hero, aid, bid) - if wr is None: - return None, 0 - base = baseline.get(aid) - if base is None: - return None, games - return wr - base, games - - -def classify_cross_source( - *, - stratz_signed: float, - odota_adv_val: float | None, - odota_games: int, - min_games: int = DEFAULT_MIN_GAMES, - odota_agree: float = DEFAULT_ODOTA_AGREE, - odota_disagree: float = DEFAULT_ODOTA_DISAGREE, -) -> str: - """Classify whether OpenDota agrees with a STRATZ signed advantage. - - ``stratz_signed`` > 0 means STRATZ says A is favored vs B (counters). - For countered rows the caller should pass the original vs advantage - (positive = A still favored), not the negated display value. - """ - if odota_adv_val is None or odota_games < min_games: - return "weak" - if stratz_signed >= 0: - if odota_adv_val >= odota_agree: - return "agree" - if odota_adv_val <= odota_disagree: - return "conflict" - return "weak" - if odota_adv_val <= -odota_agree: - return "agree" - if odota_adv_val >= -odota_disagree: - return "conflict" - return "weak" - - -def enrich_entry_cross( - entry: dict, - *, - hero_id: int, - kind: str, - by_odota: dict, - baseline: dict[int, float], - min_games: int = DEFAULT_MIN_GAMES, -) -> dict: - """Attach ``cross`` quality blob to one counters/countered/synergies row. - - For ``countered`` rows, STRATZ stores negated advantage for display; we - restore the original vs-sign for classification (``-advantage``). - Synergies skip OpenDota (no teammate WR in matchups.json). - """ - out = dict(entry) - peer = int(entry.get("hero_id") or 0) - if kind == "synergies" or peer <= 0 or hero_id <= 0: - out["cross"] = {"status": "weak", "reason": "no_odota_synergy"} - return out - - o_adv, o_games = odota_adv(by_odota, baseline, hero_id, peer) - raw_adv = float(entry.get("advantage") or 0.0) - # countered display advantage is already negated; restore original vs sign. - stratz_signed = -raw_adv if kind == "countered" else raw_adv - status = classify_cross_source( - stratz_signed=stratz_signed, - odota_adv_val=o_adv, - odota_games=o_games, - min_games=min_games, - ) - out["cross"] = { - "status": status, - "opendota_adv": None if o_adv is None else round(o_adv, 4), - "opendota_games": o_games, - "min_games": min_games, - } - return out - - -def enrich_hero_matchups_cross( - cell: dict, - *, - hero_id: int, - by_odota: dict, - baseline: dict[int, float], - min_games: int = DEFAULT_MIN_GAMES, -) -> dict: - """Return a shallow-copied hero matchup cell with per-row ``cross`` fields.""" - out = { - "counters": [ - enrich_entry_cross( - e, - hero_id=hero_id, - kind="counters", - by_odota=by_odota, - baseline=baseline, - min_games=min_games, - ) - for e in (cell.get("counters") or []) - if isinstance(e, dict) - ], - "countered": [ - enrich_entry_cross( - e, - hero_id=hero_id, - kind="countered", - by_odota=by_odota, - baseline=baseline, - min_games=min_games, - ) - for e in (cell.get("countered") or []) - if isinstance(e, dict) - ], - "synergies": [ - enrich_entry_cross( - e, - hero_id=hero_id, - kind="synergies", - by_odota=by_odota, - baseline=baseline, - min_games=min_games, - ) - for e in (cell.get("synergies") or []) - if isinstance(e, dict) - ], - } - for k in ("fetched_at", "stale"): - if k in cell: - out[k] = cell[k] - return out - - -def summarize_cross_rows(rows: list[dict]) -> dict[str, int]: - counts = {"agree": 0, "conflict": 0, "weak": 0} - for e in rows: - status = ((e.get("cross") or {}).get("status")) or "weak" - if status not in counts: - status = "weak" - counts[status] += 1 - return counts - - -def audit_matchup_tops( - tops: dict, - odota: dict, - *, - id_to_key: dict[int, str], - key_to_id: dict[str, int], - names: dict[str, str], - min_games: int = DEFAULT_MIN_GAMES, -) -> dict[str, Any]: - """Audit STRATZ web tops vs OpenDota; return report section (no file IO).""" - by_odota = odota.get("by_hero") or {} - baseline = build_baseline(by_odota) - by_hero = tops.get("by_hero") or {} - - pairs: list[dict] = [] - summary = { - "heroes": 0, - "counter_rows": 0, - "agree": 0, - "conflict": 0, - "weak": 0, - } - - for key, cell in sorted(by_hero.items()): - if not isinstance(cell, dict): - continue - hid = key_to_id.get(key) - if hid is None: - # Prefer embedded id when key map lags new heroes. - hid = int(cell.get("id") or 0) or None - if hid is None: - continue - summary["heroes"] += 1 - enriched = enrich_hero_matchups_cross( - cell, - hero_id=hid, - by_odota=by_odota, - baseline=baseline, - min_games=min_games, - ) - for kind in ("counters", "countered"): - for e in enriched.get(kind) or []: - summary["counter_rows"] += 1 - cross = e.get("cross") or {} - status = cross.get("status") or "weak" - summary[status] = summary.get(status, 0) + 1 - peer_id = int(e.get("hero_id") or 0) - peer_key = id_to_key.get(peer_id, str(peer_id)) - pairs.append( - { - "hero": key, - "hero_loc": names.get(key, key), - "peer": peer_key, - "peer_loc": names.get(peer_key, peer_key), - "kind": kind, - "stratz_advantage": e.get("advantage"), - "stratz_wr": e.get("wr"), - "stratz_games": e.get("games"), - "status": status, - "opendota_adv": cross.get("opendota_adv"), - "opendota_games": cross.get("opendota_games"), - } - ) - - conflicts = [p for p in pairs if p["status"] == "conflict"] - conflicts.sort( - key=lambda r: ( - abs(float(r.get("stratz_advantage") or 0)), - -(int(r.get("opendota_games") or 0)), - ), - reverse=True, - ) - agrees = [p for p in pairs if p["status"] == "agree"] - agrees.sort( - key=lambda r: ( - abs(float(r.get("stratz_advantage") or 0)), - -(int(r.get("opendota_games") or 0)), - ), - reverse=True, - ) - - return { - "summary": summary, - "thresholds": {"min_games": min_games}, - "manual_review_note": ( - "Dota2ProTracker (7k+ MMR / pro) is a manual high-MMR reference for " - "conflict rows; not automated (login-gated)." - ), - "conflicts": conflicts[:100], - "agrees_sample": agrees[:40], - } diff --git a/shared/paths.py b/shared/paths.py index f2cb5bb..e0e220c 100644 --- a/shared/paths.py +++ b/shared/paths.py @@ -34,6 +34,8 @@ WEB_ASSETS = WEB_ROOT / "assets" HERO_PORTRAITS = WEB_ASSETS / "hero_portraits" # Primary-attribute icons from dota2.com (dota_react/icons/hero_*.png); UI only. ATTR_ICONS = WEB_ASSETS / "attr_icons" +# Valve hero-selection role filter icons, chroma keyed to transparent PNGs. +ROLE_ICONS = WEB_ASSETS / "role_icons" # Item icons from Steam CDN (dota_react/items/{key}.png); Climperor web site only. ITEM_ICONS = WEB_ASSETS / "item_icons" # Shop category header icons from dota2.com.cn/items/images/itemcat_*.png. diff --git a/shared/tests/test_matchup_cross.py b/shared/tests/test_stratz_matchups.py similarity index 62% rename from shared/tests/test_matchup_cross.py rename to shared/tests/test_stratz_matchups.py index c34663c..611d554 100644 --- a/shared/tests/test_matchup_cross.py +++ b/shared/tests/test_stratz_matchups.py @@ -1,7 +1,7 @@ -"""Unit tests for STRATZ matchup ranking + OpenDota cross classification. +"""Unit tests for STRATZ matchup ranking + cache helpers. Run from repo root: - python -m unittest shared.tests.test_matchup_cross -v + python -m unittest shared.tests.test_stratz_matchups -v """ from __future__ import annotations @@ -13,11 +13,6 @@ from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[2])) -from shared.matchup_cross import ( # noqa: E402 - classify_cross_source, - enrich_entry_cross, - enrich_hero_matchups_cross, -) from web.fetch_stratz_meta import ( # noqa: E402 annotate_matchup_cell, build_matchup_file_payload, @@ -125,80 +120,5 @@ class MatchupCacheHelpersTests(unittest.TestCase): self.assertIn("antimage", loaded) -class CrossClassifyTests(unittest.TestCase): - def test_agree_conflict_weak(self) -> None: - self.assertEqual( - classify_cross_source( - stratz_signed=5.0, odota_adv_val=0.04, odota_games=200 - ), - "agree", - ) - self.assertEqual( - classify_cross_source( - stratz_signed=5.0, odota_adv_val=-0.04, odota_games=200 - ), - "conflict", - ) - self.assertEqual( - classify_cross_source( - stratz_signed=5.0, odota_adv_val=0.04, odota_games=10 - ), - "weak", - ) - - def test_countered_uses_restored_sign(self) -> None: - # Display advantage for countered is +8 (negated); original vs was -8. - by_odota = { - "1": { - "2": {"games": 200, "wins": 80}, # AM wr 40% vs peer - } - } - # Baseline for hero 1 ≈ 0.4 from only this matchup. - from shared.matchup_cross import build_baseline - - baseline = build_baseline(by_odota) - entry = enrich_entry_cross( - { - "hero_id": 2, - "games": 100, - "wins": 40, - "advantage": 8.0, - "wr": 0.6, - }, - hero_id=1, - kind="countered", - by_odota=by_odota, - baseline=baseline, - min_games=80, - ) - # odota_adv = 0.4 - 0.4 = 0 → weak - self.assertEqual(entry["cross"]["status"], "weak") - - def test_enrich_hero_keeps_stale_flag(self) -> None: - cell = { - "counters": [ - { - "hero_id": 94, - "games": 100, - "wins": 60, - "advantage": 3.0, - "wr": 0.6, - } - ], - "countered": [], - "synergies": [], - "fetched_at": "t0", - "stale": True, - } - out = enrich_hero_matchups_cross( - cell, - hero_id=1, - by_odota={}, - baseline={}, - ) - self.assertTrue(out["stale"]) - self.assertEqual(out["cross"] if False else out["counters"][0]["cross"]["status"], "weak") - - if __name__ == "__main__": unittest.main() diff --git a/web/_oss_static_assets.py b/web/_oss_static_assets.py index 7abd3c1..1ba1612 100644 --- a/web/_oss_static_assets.py +++ b/web/_oss_static_assets.py @@ -37,6 +37,7 @@ ENDPOINT = "https://oss-cn-shanghai.aliyuncs.com" REGION = "cn-shanghai" ASSET_DIRS = ( "attr", + "role-icon", "rank", "item", "item-cat", diff --git a/web/assets/role_icons/Carry.png b/web/assets/role_icons/Carry.png new file mode 100644 index 0000000000000000000000000000000000000000..dc3d8480852d994ad0b120b24dfc8260e97217ef GIT binary patch literal 4889 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 PuQ4QBhjlq?cO3pN!();Q literal 0 HcmV?d00001 diff --git a/web/assets/role_icons/Disabler.png b/web/assets/role_icons/Disabler.png new file mode 100644 index 0000000000000000000000000000000000000000..dd236e78defbc990e7c5bca1aa45c7a131aed952 GIT binary patch literal 6579 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 literal 0 HcmV?d00001 diff --git a/web/assets/role_icons/Escape.png b/web/assets/role_icons/Escape.png new file mode 100644 index 0000000000000000000000000000000000000000..fa0b12065b9e306f36725bfdc80b23332ad5384d GIT binary patch literal 4352 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 Q|JmTN*n7~FX?!&7Uzf;q1poj5 literal 0 HcmV?d00001 diff --git a/web/assets/role_icons/Initiator.png b/web/assets/role_icons/Initiator.png new file mode 100644 index 0000000000000000000000000000000000000000..d0a12ea9eef8b8fc87af1b3662929591a8bfc72a GIT binary patch literal 5804 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 literal 0 HcmV?d00001 diff --git a/web/assets/role_icons/Nuker.png b/web/assets/role_icons/Nuker.png new file mode 100644 index 0000000000000000000000000000000000000000..696b3d50a83ce16f52f8b82cfc3e812dbd381661 GIT binary patch literal 5534 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 aY}~hnMhG?_r`CuH5U_JEIe)6 zG%-sH6Ds^02!EXC3c=qVjSpp}O`Gp;X|jHE*z^zOHg+;jFWAk>(VIm)pCg&a2gPnvcaV`?#b$ zDeiQMdlFac9?e+s_+O#3R*?^UKCtrXLxW7Qv+GHg&-D*W|7aLdMxy&=*E6h_9qOJ- z`(^n3h!_8v`kE=T;3d<0I0K0<0?=<*7BgBU>JCy!E*e_=3ro-iYu5jBLA{V)>QF%M zP}}U+Z7X+6?w0Z8spd4Os;YgG^dbKM)%)V^9xywU_m75^4~1FFn!4opXTI8#afEGp zNpFPWRaR2<#;2ODxl>m=cUxX*UEt%_$x^41t8Uxh4!O&9vdwXFboN<{A7?u8xU2)D zg4P!AJ(}%BbM@Q%Of^QxvroTVqPoZeHlbxUL%udT5kE7##cp=1tuk2<4e@%e|YBDaUOfz z?+nWP&NWw+EE=b%K67lyefVRtWW!Gj_#;)85UP;6B()fZdiQcePV=vnHz9@U?yC0r zACBcp-gwtn5%-UKrmS4C0>ishk+}YA)7u9B)ug^6mz*@O{cU2rfKC9IB8F)&-;)yg zBK$1O=A!6#IzOj$ZeVoH-hqrHTgFhvcS=naT=N*8&hlFxgSYyPW(v`zvu{n5$zl3( zicD#0RUzk7<-dMdYqp{)(uSs1OAASa$>o}@F#gPofR_|nmO7qu9H>CzphrB%pYWU2*;EMp6EPcwk>1d*Uo!CKqW0BU)`-My-TJ3$F2H?GQFc??>Vh4 zzM68qCDl?J9_`d`SDAgm6s_N;pG1##qTQ)$cpEEmvrJppAk190DpG^si(g87I~9g# zK3}LJ1P!snPcWEByNTP5qy1L+A^L@0qsVVutzInwn@d*^1!~psdwHG$y=38D7VrI{ zwM|T@M7?!lAQ_0;A1z>A;_+R&uxSI0im`LLwprMPj#X@1rzoUO=(uS9;dtlhPM#P}9lKbS7drD<{!z<R6BZ6<;QLl!rH3*k1BM z#F)&@2pgbr9TjN&d280)WW$2XSE~*1Y^K9(WY7fddLEB*nGl>_`Q4;rnsBOVu-h(?`55zq#@x$~t` zhTHnYX+O)e1#u$K4wiHguFjLF&iP-}-Bw)YbBT7rIJ8qB!78-eSp-*h-)JWBq`0Q zw!rBhuYTX7H?fB=0RmpaqERc*NtU*N+4%{}7cmzVswa?NDj79vbLqibBu!ZeCFwa=RG@un9>EN6N5f=by-_Pp&o zrIQfX7I#lme&C)1-(%dTZhUZRoip>ppL%yrAGi%Y8KDgbg$=kF#3$k^am|$B2b~X* zuUJP@6E=q*vDU7t3_pSP7oL^V5d2cfOH=o}zRofRd9;wpJ{47O$Z_H~C5iAR)S|IH zarwJzOM*_!2wjel)(4H;kCkOL%VP9R2)6SJ^4LU59nDG|e~J16DZ*ND`<^+mFt*4P ztNhC_PzRMWZcQ!;=i=JVuLErtDp3Fv*g1tUI5CBSdVLK4Vm7OZAYqAr@e;Il-A<{O?h_m{tVAqu!^OyiUXJ{OyYH_ zN^A?ZL$yz^f1X1PQ>2Mo-)LfQ(mfLJzH7B~mTd5X@ycuRUNzK+H%iK2m%`Lzg9yb)Qbz#=)GPc(md`y zRU}oB(G-##WD_vBJRC1r{7 zVL?=vens)g2;i|I&2pzjX#n>X98o zQY19;)s}+WAc7q7f_SbLXBiqxK8(c}kA-`0sYcxSxjX$*Hed}+V(|A7kO=u!8aF5& z)?w%_O$Op;D)DH#S!T*IbU&IUQWtl5(W9|gU4EXL4xX8e8?q_aCc zZSpwl@wo#cSUcOF*P#u&6}V0=*iSc$=INK>hXd+rfK?T7QM?}?{2g4*NxxpAVLt&*mFEn}5kZmADyR@x~G!p4mn z$kpiBV61)z3v&N&Yy8XpA<$_5Cz831d^r;AvBYB_b7L~d4BMp62p_KSP{-R1+s{3KmE2j$d3ali}* z%UA-l)|r!m={|%!bkrCI10P1oKh)MYil)HFlo-IT9+?EE8EGcyl3y^2-~M~i z_5(UkF*@U4O5m~DpfOPm%#P;w)h1-oqfRJV^zwfIr*>7m-uzE5@Y_Y>zvX|biKmcg w5S}Yq$G`T#ml=R@1HhPV;|j;cvJu0BtUW6XJ(42e^V2j-Q(KcuMjnU%3odUl8=l7c#W83F_-e>W5AtNzz;U#hZ_pSd<6tv&Gv`K!;Gx*3@p zT4f2FTWCX(ag9sjl7?M-*%mccxfJck&CAA*y+6G1Zz(b=+p{^{c+j?uZy!omIJlNEM>l^opawW!rmN|H1T%ZZxDCzG#r8YmCESdniL)p=uV z?VZ+ghOc1#gFT7X{riL=dJghy%7niW%}P1|B=P)xRonHMSI&nH)$g5pvI3I1&+Qm7 z3Eguopp@O#bR;T~!{4``HK`V0kfiBDcfBz*QH%Qiuz&X0(|~5%z{*xQtkadx{-v(@ zIOAc;({Xd>LzVrRt3IEgeQ?=>8MtUF^!IV|McrFC1no?Z|EPZkz4)oaq@ve(9qNWN z8Xe!HML6(BD`DVAt#iyqsr6Y>*hnKKDC!kmHa!h5S%j1b->v|@{E|NsjG^|?ac_i~qL4t@h>wYewk*kaDZaM5uWtDmEu@3HNP(pyef0AytMH^wUFFjCo z$FRJ;4p&1#fv`Ajlhx*%gvKYjMY<{!&!0?sN7cnJsU1ffXft^M~Mm)7eSn|LMLEn_av%_6qd zsQDP-j#)H&la~M`E01b!V*B}r=mlMU~U1{1&-MJ^AcMka5zr*E2(_B&0 z#?}W=Ill;)AS0_^_%nQM^z5Vrpd^rL|Hs(UsJz|URRfzXBFE3hkJ$07QRBA*N1uG` z`?zG^b8QYQERW%@g6^CSodFoxtgnz0X#sn?V-k0{n8&Q zgL*OA9i-ySQbocjaTCoq@#yql_kY!nV{5J&I02e^$YyLGvk9IV7MeWdwLZEl_7Hx)MRn( z-_XtKKgAB>_xHoYGNBOH7^aTui}@JnUKjZ`L!^&tT?OYVgYvPN(kVBFYEsaUt&-o= zE&-$_vAoWVotN`DiO?A0WTsIKuFlRMaX5u2p^auC)soY zthLdU=6NP~KkG^{27r_tCeEQr2woDZfta-mQ2r(ly!V7>uIzphLAY?7LAsyqkvvO_ z+8UsRu4RDW2d)^+GcI~JXZdv)k4RYHa$F#Du98!KH0`}N$+h~?k~+Nc16MED4m5i( zc<#-#DlJ%)n~zj(b47#sEG6Q|%p2E(uDuf(_?dCVV~hkYR$+?V&9#OzeFC0U)y(&P zKQ@VSlb7ViTi-@bcwY@3jaCY_S7-^FpBC#8RnEBtFZE2ExZOgUDvd(^6|!}XL`enP zfePhm%?ZYLn+?Xjk6KzDd&}S0f;atK#eY#X_I`jf z^cWNa|1P_8I}1Hn5~vv?g?gALW6d+xX=0AwPw+@ScD{voXIWN{8f9cDcsIsO!5Y8}U|#4-Y_JsR*!n8p z8IN{N9EixWDU0(s?dncrBj>ec270F$67n?If~#Di@DEkZ^^M=^C28l_BlV!FHmH}q z8!(WaY5<>5+omUXQz`3U%G*EY$J;C{GC>(dw_-DzlC`vN^zZC)A#v#H;*;Xqg6t4g z3jj_BkbmUH17-&6jeaZtjWV+}0Mem1Lpdc*p4}8~p zFr{crjusr`^q1gQO8#;vQa@f8MO!p>#1KdgnTBot{wt zby*}T2ib}+z+4F6J^GPyTq6nV*d0t?ysCih58|~-YBj3E{MDSjLXc|zP}C)~vPxj< zgMEFAjhV(BB!A?TVbjoi?+XM$=_|)9wDL3VV%M`G72{1-h`M|%sXIyWT(mX=8- zMvp0gJ`I8XXaoW@_DvG!wIxFRWvP@{xZ+SrN*BZCURnP^liuMKFji9|bxKtA93+L> ziKId_%6c8}Q1HZwujytl*CB_im|e>W{x$20RO0;r%y;xvD3V@T;;(?RkeCnZ)y1qi zBr`Y%8CAWV<~8RfG&{4+?d0n>CJeZh8Iclcsj$p zvD4@Oi&q99tD)3Au^LP};-2da(c5hhW2denlfWo*{$SOaT!K3eUo&#zu`8#ji)>$d-@_z97rjLT336N?M0XTG^hB z%zsc>flEZZiZrr9?fS|U0;xy`0x=dJ@j*u*3I(DJ!p3lY8d*6UDG^11Bk&ct7s1pb z$1s-yr-NtTglZE}wZgCNN|51JRFW)TGdziI#)CO=qlAAtutZhCY53}^5nB!~y>CIw e_oHh9*?hPD(^;{+2b~EvxsgUy76!d_?knUhi;(LLveG8B5ueX6P2Y%-Tv_Q$C68azuz~X&-eXJ zMQX~TX~DCD0RWh0T#TpjE9iYDPvYNfNy#IAiQx2^Tsob{66Cak?{Z!kVly^xy4&vXV;Z0eUrBa_K@&4e_2n55ewCPJW@-!A>NKPZ{j8IAK(Tz;?Sgu5mQWrIG)*@lrHv zayv;ljb!OU#>&?tFUm*ebNAndyg=R?)jJHu7sXP5)7C;e>EMjG8sfi*2#Qc4s6s4N zC?zOnkm{up`jv8Q#V6nJyl-Pw%>Rs z@buQsPIgzs7Qx<{A}Jg(a=7XI`QqbPLtJl7|8R3xXI@70wfd@+Nzs=C=KD9o@4Q+* zt#T;j{<=9m{`$Mej(Vatw5*HVQo1m}T(^EOC!l`)ve1iNe%qSjW>sAKCUZgMlF*FRTA}ohHw5bopxyR~iSF9L(6cyvkrZG`;_U-;oXGQKqTBr$DB(^;TVZ zeRIvdr_hl)8Cxa zm>o9I56m6FmmY0&&(wAM6@9z)YtSdh5%f8-E__zW3dOKc@B!6qOZ19nwl$Si76ZSuv%obk@q`g0mUBB1VE- zvB8(#xa^5Zd@ml@*cuz=IT;nzus-odUqp3BQVh{KZ?vPHQo27%>2AOIa0tFqkYsA7 z+vJCX#(HW?{CoCA#F@alpdsC(550ANsG| zk@anJ+|?i(cWd5mpn7BNg*`dQorQ8GwQFxxTe`<23=KZ0Ii-8FW>VLu7k3Y@2$VFV zQ@?1G=fyX$L#(_u^dUI=Y0ajypXVOzZu>nWYj9CwLu5=_mec&?SwzZccXJ&I4D^g)p%H)OYc*SvdePTNK3xpf<40@DyG zJ%~BuRJe{jc*f0rwlZ!3?R&cK%LGS*EHHlMPvWEF(=xn z%ranLG-f0+Ni|&$t?w+E&fWWktU%fuWFv{wMNws-ggR4)=_xrH#H3GH8eD@wfkLrB z$d(DkY)Bdi!=YROUw}fPLLo=Sh9%Nj8*_5GFi*-C3j`8A4~C_oLY|C=N;xn$5EaX~ z5IW11>y4C3uf}F|ajWhOH}Jh&L_%UJiXfE)kulo=k!b`)7}E#?Ac^4vtK)FJhA=xaD z*6F*}rx))o52mMY3`t*&xa59k2E(Rbj*6A$yF+n`bnhsSoxhi;4}>{|;I(2rGX9nIs{Q_)r6BpLEU2YwBs9^oL1&^~|x#~udd-9Nw{Io7JkARMwEY7qJ5 zKCyK*+ikEP^S^y%Gc+Npj)-L){41xuZ+P3LYuqIw5Cj)*zmU1p*R5!E7xc(;#S;o~ zz2KW$aWg^j(J!wLSMS-qwjo7}RF@`sng=7Aj$G1IPWCq2i#Biv%Gxr*9IJXB3i7Ly z5);$17}u+9u0C4+>Ue&)XK#s9_{xP79V2ypD?PiP^whPVIR4`0%ihYy#5GRc zlP^UZU*$hjLuX35+e$X7r|g|a#qEjDPW9hM}-1(n@>KU;_(--)@gg$2y1EC)C7sNmUa_n4#x53iA%Zb#x`rQSh z92Qm%t{E~l-x)t4(KdKG+JAa6IPct3|5T;Y^?6gF1@jT?zjh=CEv>t~rKPm4?yK*g zqs*q8$FA1bxRn0ljDY43H;OIz5QQ<<$fNGuEx&fQ-iz5 z7Da6dd*R%D&tVcMa=MxQ)1K^oy2(SzrG44Iw%YL(12qg=Mo&oWiXFpVHP%0fTpEF% I2;KbIKdaP^NdN!< literal 0 HcmV?d00001 diff --git a/web/assets/ui_icons/icon_attack_time.png b/web/assets/ui_icons/icon_attack_time.png new file mode 100644 index 0000000000000000000000000000000000000000..3dee815dc8466708f1b23001735aa28cb2794e95 GIT binary patch literal 3018 zcmbVOc{o&iA3s`bnNr!t))*})X2(p1%!1LF27@AQ!we?OOf$n+x)f7nX&+spR7$

W@8|pZE}!3d&ORo?6{2aV2><|u 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 literal 0 HcmV?d00001 diff --git a/web/assets/ui_icons/icon_damage.png b/web/assets/ui_icons/icon_damage.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca96b058f148cd111b71116b8fc26c85f854619 GIT binary patch literal 2199 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 E2i_9tqyPW_ literal 0 HcmV?d00001 diff --git a/web/assets/ui_icons/icon_movement_speed.png b/web/assets/ui_icons/icon_movement_speed.png new file mode 100644 index 0000000000000000000000000000000000000000..4c5193ec7f86cbdefb59cadee12147883011a969 GIT binary patch literal 2332 zcmbVOX;c$g77i#&XcQDdXc5Zj-~u5V2}vX@S&$$*X+dz*NGcG>MpBp%1){XgA}S4H zzztm59zX;PJ%dITMOzVD&FFDK1rcox3JNrfGK4NvocZDDA5-U4z4z|B-~H}-PUS^} zt(jx>trY@+n8OZYM!_pE{aTvCU%5*42fR2ztXL>o!iN-G8HfnrNw$ChTf|KSqd+b% zJ+&S5K_F)F1)NwYHZ+8qAQ5?Tr*%A)A}NeUAbk9kQf@*D2mxEbM1h!wdaG+e0RkQk z6+;ZghDsS=k|0PW1EW=8oCH-$0)>b2^96jARM>zBgt&lGBoxc3N*d~;T`Ihv7GqGr zM-?cAhWg}GY-j|)kjOxQo8pNhc@c>f*>gsgHb3Ho(h7eM3#ubQ79A)7LUQ>(Xa+uo-T&CO0-zM zbjAV`lqbjpQb-^X1Jf3{TO?@^4F!ApDFl%;H1vyMvHWwQ;FMvMTqy?UiN%OS({X*& zmP1kCf8F@1ww#kL1u;>eT#_bBfa}3uIs=At_t!+zim*4-aG3xu3RlRKB&3NzF~nxl zQ1Fc>Pr##Mab!G^O!2}I0*Ndl4oAll7!*7nmeJ{61T1mJ#@Da`Oa_U>AY#c3k{1rg zqSFJ(*Z@MHw-=U3VBmNA%2|Ktnz0$~=yW(IJSosVNuLgGP3W2ZcBMN`3{{Rd3lpHytzqy7Z2w+-SIf+l$xi>&5LC0sAGE>OLxckU*wu+r;1Xc-gV}u_iqe~-`?G;cvqme zie^01_)uuuU0;tPTFBQgjZ{?l6d7Fja}5>@v4qJ8xF*@d=mK{U>W` zM7;w=LAjh&FKQdFcXa5j%>0qLvxFyF?Dt!?+nKP{N_Bqz&>O$lQ(xuVE(d5{4W6DzC1i8A~xyTSDC)uQ=}V4U)?VCPI6j&AY($ltveJLMub*MD zvup}7Zn)oX>A&`ES>=c_CH zH)gz&_T~OIApUT&Cx-0XxYDEnnt!LjO}k*gZ0v{CS&GPcD@7^Mb=D_rnddc{_Og2i zTzjs)MZDxQ)a~}S-e0I{d?31@{^c9?Z50!+U8dEBJsVUl867BLY23Z|+STSN%xJ5A zks8oNzOJ;Wy9-^h+5g7&+C4k<#3c z2i?5wR$RlWmJ>n)1!Nmb)1hHSF(L6*vg1ky@PWr2G^ZH*^l~ZBp*=o6KHIvD>7agY z88_k3+VyieE$U=J-ujy15xV+sj+Z*e-UH4{@cmGcqR#m? zTzIr)mOOI9SkLb)`gR|EU$n#1@NUzCh*z0Hf0xLp?miQ4vD*=ox=oUxj6WXeHK9pm z>Xya3WKQkZZ~hp*Nk`pzAiiduuVK7Vo25Vdy7K4e! za;m>uHRo=lGoPw;$Hbi7cw^9ooe|NSWU%Q!ahL1EDJcvZ%d)#oF zs+B@$OX36@ccj=Qi!PYSFi%Ik!}fLt=EQHF@DY)M`XoZ3b^ao!qBRQ;>q66@F=_hx z)#G!QXBrk@8eE!W8JRoox>WbiR%EdcbztMJhm%$`NvB*5x{BGU_cN~S<2mjLjQ#;f zC9*I*|8c%6UQlbiwGe4z$bOzY{;sLk)H&z!cof%Sx27=Gtyq*F>w(bv&LNbqo7LR! zR*F{el^WWy!TNb2t6cZg92p()ZCzHl&4@e)_>Tui>Rk%`*GR0NT;)Cehx#>L#;WSi o>b)Zzxs$iG9(dn!#&5PG;xV^*UPfL2i|PLrHY<#IhQ4{{pFG^ds{jB1 literal 0 HcmV?d00001 diff --git a/web/assets/ui_icons/icon_projectile_speed.png b/web/assets/ui_icons/icon_projectile_speed.png new file mode 100644 index 0000000000000000000000000000000000000000..1934fcc7d657bebdee3485cede5bdaecf33f104a GIT binary patch literal 1576 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 R;pM+n2Z{^1!e(Fce*lYKwABCr literal 0 HcmV?d00001 diff --git a/web/assets/ui_icons/icon_vision.png b/web/assets/ui_icons/icon_vision.png new file mode 100644 index 0000000000000000000000000000000000000000..3752c22ee15c9b54be932b29b16fbf67c6f4193a GIT binary patch literal 2450 zcmbVOc~nzp77t(;#RX>2R-|BfP+Tzk0xuAdEJ&090t&Kbdtij*g)BmV$|6-1SqhA7 z78MkxsGvo$7DQHs+JVXvDIl0CSc-yF6gPApD$e}j=^yjXdGGta``zDNe)ryU-XU+# zEz6D87@<(8yxfGsY>Fju?C71uGy74M)I=2m}(w9>fu86f%{x2}2@~2zUY!PbOfAR63PFCp%z1 zJOJWNAr7beu-!iRLQV`|w@M|a*N)~Dk!a{%3JFpaK@=Mnsilxi#bq-9 zp|q5wze}g82P_CH^Ir&Qd@E6|C@s;i!cU@ZJrpVC8OD zj$!%IFzfv#GBF&Z+%R9B_t~-`g=8-zc2mGo%sYPRl27<&QfWjojfLdMB5`O$qN^L| z%At`+_Mkn;ap16l4_NX4!x!MxZ0%C7 zKSvun!I4>)&cv>1sAktwT5;Ib(Pvup^U#{CUCK@w2)fL}J&kcUZl6QQ)o1pLJCUt89+k8R+jXXnQ*AVN1@6R7FNk*e3+J<*WGy zY3V=YEy=}a-82wF-00|ON0Y~T?aq1{gGp9#aj~996^8v{ZXcS2#r$*Y_EhHaO;yop zg)L8M4PLszMPp^z`9OG2>W%z^PN^MstdqZN_jrb$9F$`XE|m67OiXP2EPVq!ch^46 zPbUvZG!+oDT@3aun!FpbG9(Oj=DK<2r7x)9`7`zPugM!*FJ8uIVOlMg&hKEb&#Yy;Wy+m6ZbSUbB3{b_$WY|x?A zYAGAe?U6{fhlGR}fhU1)(&+NLN&C8-6YOcU@~V)!@{uodx!A+{R;Tn+GMly$6J9k1 zR#u^GgNZ}KNA$vkd;_em^;ql0&Q4GDZ>@)pLHmPm-JW|j3&(p|%~(D`-w)n?0~M!R z*O7cGlF@D~EQRH7ojb;5wuRq5nw=U+gxca}e!o@wpr^v?OlIZ^ixo?J!AGcJG_dTl zb#%qVwFBK=I{HDP)4ta~&{e-@A9gE?BMf$T=Tuf#`)6cifcGyx`*ZZ>9tdjt>UK|0 z8HX=ZPYqv9%=!0}aT&ga(Q?`>TgY0wZxL|t0>ZCvh3B{3KW%Yy8(OmAVe{8Z15K1o z7u(u6>hbO`pFY;LNJ|UIQOnGo&Q=9qY0h4$+hOjFcB1O16i}FUo+VD-85$Z6dAKAz zKmTL=Y?b4N7Ui4Q8Y-P$dDS&NB0PL1zV24MTCJW`#>89>ZSb!S9TlzSJ2cDsSP3K^ zkB7S=VX;`1Cr?`47&W$Z^1dv)<6~-S3f@OLagSV1i12{n>VdkL>f$=e&|F=z%izF3 z5jic?A!hPesG_>Zo7G!Lj!+Ff8RhbL8Jb=f%C%j&X;Vh$gezi$xZFc~;}a9Fh59&B zaXk~as|R<=tdAELlkK3JE3E^=mJ7>0EgRA+ zx8O2gQVr#Q_b=0*)V+-G%sYDY4PJq}mHs*Yk2v6qYx=ppZC#lW#jEl?vFqt|;Smw) z?q037fk+fNQ>mxZO_kf8p?VtejmH~BTD^5${RI#CmB(EYL!CizJSZfjkNNz}rEN-Q zpV}8MH0mc~JoUZn%bz|cYI@f^{+rx9ZOz1s$Y+{22kLCr{*k=*uIPkWVc5{d^?5egS z@z=KREjxM}wbK8$Lz}k-&v|S*HT7WhMPWd_O|#Z!BxmoA3!Hv^nwWpOcgn}gxFfMO zs*Bw1f_v4F54`z0*gw@F{80|M!J_tlriW!tkwvZdrm6I^Pm+H<2V7?JI_0+ZbqSQ{M_cNJ#`wIFy)fZv^b;zSrt(APcsS_Pg;* uo^Fm7BMFww#FFXZ8o_no=Xl!dpd>vM4Hz^(sA*-)|0Qubp6n_nKjl9+75Enb literal 0 HcmV?d00001 diff --git a/web/export_relations_site.py b/web/export_relations_site.py index 844c4e7..4177063 100644 --- a/web/export_relations_site.py +++ b/web/export_relations_site.py @@ -41,6 +41,7 @@ from shared.paths import ( ITEM_CAT_ICONS, ITEM_ICONS, RANK_ICONS, + ROLE_ICONS, ROOT, STREAMER_AVATARS, STREAMER_VIDEOS, @@ -51,7 +52,7 @@ from shared.paths import ( from serve_relations import WEB_DIR, build_payload -SITE_VERSION = "0.5.87" +SITE_VERSION = "0.5.109" DEFAULT_OSS_BASE = "https://climperor.oss-cn-shanghai.aliyuncs.com" @@ -71,6 +72,7 @@ def populate_static_assets(out: Path, payload: dict) -> dict[str, int]: """Copy web site image dirs into ``out`` (portrait/item/ability/...).""" counts = { "attr": copy_glob(ATTR_ICONS, out / "attr"), + "role-icon": copy_glob(ROLE_ICONS, out / "role-icon"), "rank": copy_glob(RANK_ICONS, out / "rank", "rank*.png"), "item": copy_glob(ITEM_ICONS, out / "item"), "item-cat": copy_glob(ITEM_CAT_ICONS, out / "item-cat", "itemcat_*.png"), @@ -260,3 +262,4 @@ def main() -> None: if __name__ == "__main__": main() + diff --git a/web/fetch_hero_portraits.py b/web/fetch_hero_portraits.py index 03347ac..8aefd53 100644 --- a/web/fetch_hero_portraits.py +++ b/web/fetch_hero_portraits.py @@ -1,7 +1,8 @@ -"""Download UI art used by https://www.dota2.com/heroes. +"""Download UI art used by https://www.dota2.com/heroes / dota2.com.cn. - Landscape hero cards: Steam CDN `heroes/{key}.png` - Attribute icons: Steam CDN `icons/hero_{strength,agility,...}.png` +- Combat stat icons: dota2.com.cn `herostatic/stats/icon_*.png` Separate from `templates/cdn/` face crops used for top-bar matching. @@ -12,6 +13,7 @@ Usage: Writes: assets/hero_portraits/{key}.png assets/attr_icons/{str,agi,int,all}.png + assets/ui_icons/icon_{damage,armor,...}.png """ from __future__ import annotations @@ -25,7 +27,7 @@ import argparse import json from shared.http_utils import fetch_hero_keys, http_bytes -from shared.paths import ATTR_ICONS, HERO_PORTRAITS, HEROES_JSON +from shared.paths import ATTR_ICONS, HERO_PORTRAITS, HEROES_JSON, UI_ICONS # wide = face headshots; crop = waist-up 3D renders (usually worse in dense grids). CARD_URL = ( @@ -43,6 +45,19 @@ ATTR_FILES = { "int": "hero_intelligence", "all": "hero_universal", } +# Same filenames as https://www.dota2.com.cn/hero/ DetailsStats panel. +COMBAT_STAT_ICON_URL = "https://www.dota2.com.cn/herostatic/stats/{name}.png" +COMBAT_STAT_ICONS = ( + "icon_damage", + "icon_attack_time", + "icon_attack_range", + "icon_projectile_speed", + "icon_armor", + "icon_magic_resist", + "icon_movement_speed", + "icon_turn_rate", + "icon_vision", +) def hero_keys() -> list[str]: @@ -71,6 +86,25 @@ def fetch_attr_icons(*, force: bool) -> tuple[int, int, int]: return ok, skip, fail +def fetch_combat_stat_icons(*, force: bool) -> tuple[int, int, int]: + UI_ICONS.mkdir(parents=True, exist_ok=True) + ok = skip = fail = 0 + for name in COMBAT_STAT_ICONS: + out = UI_ICONS / f"{name}.png" + if out.is_file() and not force: + skip += 1 + continue + try: + data = http_bytes(COMBAT_STAT_ICON_URL.format(name=name), timeout=30) + out.write_bytes(data) + ok += 1 + print(f" combat {name} ({len(data)} bytes)") + except Exception as e: # noqa: BLE001 + fail += 1 + print(f" FAIL combat {name}: {e}") + return ok, skip, fail + + def main() -> None: ap = argparse.ArgumentParser(description="Download dota2.com/heroes UI art") ap.add_argument("--force", action="store_true", help="re-download existing files") @@ -85,6 +119,9 @@ def main() -> None: print("attribute icons...") a_ok, a_skip, a_fail = fetch_attr_icons(force=args.force) + print("combat stat icons...") + c_ok, c_skip, c_fail = fetch_combat_stat_icons(force=args.force) + HERO_PORTRAITS.mkdir(parents=True, exist_ok=True) keys = hero_keys() ok = skip = fail = 0 @@ -104,6 +141,9 @@ def main() -> None: fail += 1 print(f" FAIL {key}: {e}") print(f"attrs: downloaded={a_ok} skipped={a_skip} failed={a_fail} -> {ATTR_ICONS}") + print( + f"combat: downloaded={c_ok} skipped={c_skip} failed={c_fail} -> {UI_ICONS}" + ) print(f"heroes: downloaded={ok} skipped={skip} failed={fail} -> {HERO_PORTRAITS}") diff --git a/web/frontend/app.js b/web/frontend/app.js index 3a5f6d6..77c6aeb 100644 --- a/web/frontend/app.js +++ b/web/frontend/app.js @@ -56,6 +56,21 @@ function rankIconSrc(file) { return assetUrl(`rank/${name}`); } +const ROLE_ICON_FILES = { + 核心: "Carry.png", + 辅助: "Support.png", + 推进: "Pusher.png", + 逃生: "Escape.png", + 爆发: "Nuker.png", + 先手: "Initiator.png", + 控制: "Disabler.png", + 耐久: "Durable.png", +}; + +function roleIconSrc(file) { + return assetUrl(`role-icon/${encodeURIComponent(file)}`); +} + function siteVersionLabel() { const v = typeof SITE_VERSION === "string" ? SITE_VERSION.trim() : ""; return v ? `v${v}` : ""; @@ -243,10 +258,37 @@ function renderTagbar() { if (!bar) return; const order = state.data.tag_order || []; bar.innerHTML = ""; + + const addLabel = (button, text, iconFile) => { + if (iconFile) { + const icon = document.createElement("img"); + icon.className = "tagbar-role-icon"; + icon.src = roleIconSrc(iconFile); + icon.alt = ""; + icon.setAttribute("aria-hidden", "true"); + button.appendChild(icon); + } else { + const icon = document.createElement("span"); + icon.className = "tagbar-all-icon"; + icon.setAttribute("aria-hidden", "true"); + icon.innerHTML = + ''; + button.appendChild(icon); + } + const label = document.createElement("span"); + label.textContent = text; + button.appendChild(label); + }; + + const selectedHero = state.selectedKey ? heroByKey(state.selectedKey) : null; + const heroTags = new Set(selectedHero?.tags || []); + const allBtn = document.createElement("button"); allBtn.type = "button"; - allBtn.textContent = "全部"; - allBtn.className = state.tagFilters.size ? "" : "active"; + addLabel(allBtn, "全部"); + // "全部" only when no filter and no hero selected; hero roles use `.matched`. + allBtn.className = + !state.tagFilters.size && !state.selectedKey ? "active" : ""; allBtn.addEventListener("click", () => { state.tagFilters.clear(); syncStateToUrl({ replace: true }); @@ -254,10 +296,16 @@ function renderTagbar() { }); bar.appendChild(allBtn); for (const tag of order) { + const iconFile = ROLE_ICON_FILES[tag]; + if (!iconFile) continue; const btn = document.createElement("button"); btn.type = "button"; - btn.textContent = tag; + addLabel(btn, tag, iconFile); if (state.tagFilters.has(tag)) btn.classList.add("active"); + if (heroTags.has(tag)) { + btn.classList.add("matched"); + btn.title = "该英雄定位"; + } btn.addEventListener("click", () => { if (state.tagFilters.has(tag)) state.tagFilters.delete(tag); else state.tagFilters.add(tag); @@ -1674,7 +1722,6 @@ function stratzMatchupPack() { kind: "global_aggregate", label_zh: "全局聚合(未按段位 / 分路 / 周过滤)", }, - cross_source: null, stats: {}, } ); @@ -1985,13 +2032,6 @@ function formatMatchupWr(wr) { return `${(Number(wr) * 100).toFixed(1)}%`; } -function matchupCrossLabel(cross) { - const status = cross?.status; - if (status === "agree") return "来源一致"; - if (status === "conflict") return "来源分歧"; - return null; -} - function buildMatchupColumn(title, entries, scoreKey, scoreLabel) { const col = document.createElement("div"); col.className = "matchup-col"; @@ -2040,40 +2080,20 @@ function buildMatchupColumn(title, entries, scoreKey, scoreLabel) { const meta = document.createElement("span"); meta.className = "matchup-meta detail-muted"; const wrText = formatMatchupWr(row.wr); - const crossLab = matchupCrossLabel(row.cross); - meta.textContent = [ - `胜率 ${wrText}`, - `${formatPickCount(row.games)} 场`, - crossLab, - ] - .filter(Boolean) - .join(" · "); + meta.textContent = [`胜率 ${wrText}`, `${formatPickCount(row.games)} 场`].join( + " · " + ); mid.appendChild(name); mid.appendChild(meta); const score = document.createElement("span"); score.className = "matchup-score"; score.textContent = formatAdvantage(row[scoreKey]); - const crossStatus = row.cross?.status; - if (crossStatus === "agree") score.classList.add("is-agree"); - if (crossStatus === "conflict") score.classList.add("is-conflict"); - const odotaTip = - row.cross && row.cross.opendota_games != null - ? ` · OpenDota 基线差 ${ - row.cross.opendota_adv == null - ? "—" - : formatAdvantage(row.cross.opendota_adv * 100) - }pp / ${formatPickCount(row.cross.opendota_games)} 场` - : ""; score.title = [ `STRATZ 相对优势 ${formatAdvantage(row[scoreKey])}(非胜率百分点)`, `对局胜率 ${wrText}`, `${formatPickCount(row.games)} 场`, - crossLab ? `交叉:${crossLab}` : "", - odotaTip, - ] - .filter(Boolean) - .join(" · "); + ].join(" · "); item.appendChild(img); item.appendChild(mid); @@ -2115,20 +2135,9 @@ function buildMatchupsPanel(heroKey) { const notes = document.createElement("div"); notes.className = "matchup-notes detail-muted"; - const hintBits = [ - "数字是相对表现(综合胜率与自身强弱),不是直接胜率;也不分段位、分路,和「走势」页不是同一套数据。", - ]; - const cross = pack.cross_source; - if (cross && cross.available === false) { - hintBits.push("暂无其他数据源可对照,交叉结论仅供参考。"); - } else if (cross && cross.available) { - hintBits.push( - "「来源一致 / 分歧」表示是否与 OpenDota 公开统计方向相同,仅作对照。" - ); - } const hint = document.createElement("p"); hint.className = "matchup-note-line"; - hint.textContent = hintBits.join(" "); + hint.textContent = "数据来自 STRATZ(相对优势,全局聚合)。"; notes.appendChild(hint); const stale = cell.stale === true; @@ -2369,7 +2378,7 @@ function mountItemInspect(root, meta, { onPickItem, hideIcon = false } = {}) { const nameRow = document.createElement("div"); nameRow.className = "item-detail-name-row"; - const title = document.createElement("div"); + const title = document.createElement("h3"); title.className = "item-detail-name"; title.textContent = meta.name_loc || meta.key; nameRow.appendChild(title); @@ -2590,28 +2599,6 @@ function appendVitalBar(parent, kind, label, value, regen, scale) { parent.appendChild(bar); } -/** Hero role tags (skills tab center column, above skill icons). */ -function buildHeroTagsRow(hero) { - const tags = document.createElement("div"); - tags.className = "detail-tags"; - tags.setAttribute("aria-label", "英雄定位"); - const tagList = hero.tags || []; - if (!tagList.length) { - const none = document.createElement("span"); - none.className = "detail-muted"; - none.textContent = "暂无定位"; - tags.appendChild(none); - return tags; - } - for (const t of tagList) { - const chip = document.createElement("span"); - chip.className = "tag-chip"; - chip.textContent = t; - tags.appendChild(chip); - } - return tags; -} - /** Left column: HP/Mana + STR/AGI/INT (skills tab only). */ function buildHeroVitalsAttrs(hero) { if (hero.base_str == null && hero.health == null) return null; @@ -2672,38 +2659,67 @@ function buildHeroVitalsAttrs(hero) { return strip.childNodes.length ? strip : null; } +/** Official dota2.com.cn herostatic/stats icons (served from /ui-icon/). */ +function combatStatIconSrc(name) { + return assetUrl(`ui-icon/${name}`); +} + /** Right rail: Attack / Defense / Mobility as 3 side-by-side columns (skills tab). */ function buildHeroCombatStats(hero) { + // Rows: [iconFile, labelZh, value] — icons match dota2.com.cn hero pages. const combatGroups = [ { title: "攻击", rows: [ hero.damage_min != null && hero.damage_max != null - ? ["伤害", `${hero.damage_min}–${hero.damage_max}`] + ? ["icon_damage.png", "伤害", `${hero.damage_min}–${hero.damage_max}`] + : null, + hero.attack_rate != null + ? ["icon_attack_time.png", "间隔", fmtNum(hero.attack_rate, 1)] + : null, + hero.attack_range != null + ? ["icon_attack_range.png", "距离", String(hero.attack_range)] : null, - hero.attack_rate != null ? ["间隔", fmtNum(hero.attack_rate, 1)] : null, - hero.attack_range != null ? ["距离", String(hero.attack_range)] : null, // Melee heroes often have projectile_speed 0 in OpenDota — skip those. hero.projectile_speed != null && Number(hero.projectile_speed) > 0 - ? ["弹道", String(hero.projectile_speed)] + ? [ + "icon_projectile_speed.png", + "弹道", + String(hero.projectile_speed), + ] : null, ].filter(Boolean), }, { title: "防御", rows: [ - hero.armor != null ? ["护甲", fmtNum(hero.armor, 1)] : null, - hero.magic_resist != null ? ["魔抗", `${hero.magic_resist}%`] : null, + hero.armor != null + ? ["icon_armor.png", "护甲", fmtNum(hero.armor, 1)] + : null, + hero.magic_resist != null + ? ["icon_magic_resist.png", "魔抗", `${hero.magic_resist}%`] + : null, ].filter(Boolean), }, { title: "机动性", rows: [ - hero.move_speed != null ? ["移速", String(hero.move_speed)] : null, - hero.turn_rate != null ? ["转身", fmtNum(hero.turn_rate, 1)] : null, - hero.vision_day != null && hero.vision_night != null - ? ["视野", `白天:${hero.vision_day}\n夜晚:${hero.vision_night}`] + hero.move_speed != null + ? ["icon_movement_speed.png", "移速", String(hero.move_speed)] : null, + // OpenDota omits the engine default 0.6; show it like dota2.com.cn. + [ + "icon_turn_rate.png", + "转身", + fmtNum(hero.turn_rate != null ? hero.turn_rate : 0.6, 1), + ], + [ + "icon_vision.png", + "视野", + `${hero.vision_day != null ? hero.vision_day : 1800} / ${ + hero.vision_night != null ? hero.vision_night : 800 + }`, + ], ].filter(Boolean), }, ].filter((g) => g.rows.length); @@ -2730,30 +2746,21 @@ function buildHeroCombatStats(hero) { section.appendChild(title); const list = document.createElement("dl"); list.className = "hero-stats-combat-list"; - for (const [label, val] of g.rows) { + for (const [icon, label, val] of g.rows) { const row = document.createElement("div"); row.className = "hero-stat-row"; const dt = document.createElement("dt"); - dt.textContent = label; + const img = document.createElement("img"); + img.className = "hero-stat-icon"; + img.src = combatStatIconSrc(icon); + img.alt = label; + img.title = label; + img.width = 20; + img.height = 20; + img.decoding = "async"; + dt.appendChild(img); const dd = document.createElement("dd"); - if (typeof val === "string" && val.includes("\n")) { - for (const line of val.split("\n")) { - const div = document.createElement("div"); - const idx = line.indexOf(":"); - if (idx > 0) { - const sub = document.createElement("span"); - sub.className = "hero-stat-row-sub"; - sub.textContent = line.slice(0, idx + 1); - div.appendChild(sub); - div.appendChild(document.createTextNode(line.slice(idx + 1))); - } else { - div.textContent = line; - } - dd.appendChild(div); - } - } else { - dd.textContent = val; - } + dd.textContent = val; row.appendChild(dt); row.appendChild(dd); list.appendChild(row); @@ -2771,12 +2778,11 @@ function renderDetail() { root.innerHTML = ""; if (!state.selectedKey) { - root.classList.add("empty"); - root.textContent = "点击英雄查看定位、技能、核心装备、公开数据与被克装备"; + root.classList.add("empty", "is-collapsed"); return; } - root.classList.remove("empty"); + root.classList.remove("is-collapsed", "empty"); const hero = heroByKey(state.selectedKey); if (!hero) { root.classList.add("empty"); @@ -2786,7 +2792,7 @@ function renderDetail() { const head = document.createElement("div"); head.className = "detail-head"; - const title = document.createElement("div"); + const title = document.createElement("h2"); title.className = "detail-name"; title.textContent = hero.name_loc || hero.key; head.appendChild(title); @@ -2887,7 +2893,6 @@ function renderDetail() { const center = document.createElement("div"); center.className = "detail-skills-main"; - center.appendChild(buildHeroTagsRow(hero)); center.appendChild(buildSkillsPanel(state.selectedKey)); center.appendChild(renderHeroInspect(state.selectedKey)); layout.appendChild(center); @@ -3739,6 +3744,7 @@ function renderMatchesPage() { const body = $("#matches-body"); const originsEl = $("#matches-origins"); const playersEl = $("#matches-players"); + const sub = $("#matches-sub"); if (!body) return; const origin = MATCH_ORIGIN_FILTERS.some((o) => o.id === state.matchesOrigin) @@ -3845,6 +3851,7 @@ function renderMatchesPage() { const summary = total ? `共 ${total} 场 · 第 ${page}/${pageCount} 页` : ""; + if (sub) sub.textContent = summary; if (!rows.length) { body.innerHTML = state.matchesPlayerId @@ -5385,7 +5392,8 @@ function syncChrome() { btn.classList.toggle("active", btn.dataset.page === state.page); }); const heroesTb = $("#heroes-toolbar"); - const itemsTb = $("#items-toolbar"); + const heroSearch = $("#q"); + const itemSearch = $("#q-item"); const heroesView = $("#heroes-view"); const rankingsView = $("#rankings-view"); const matchesView = $("#matches-view"); @@ -5396,7 +5404,8 @@ function syncChrome() { const patchesView = $("#patches-view"); const detail = $("#detail"); if (heroesTb) heroesTb.classList.toggle("hidden", state.page !== "heroes"); - if (itemsTb) itemsTb.classList.toggle("hidden", state.page !== "items"); + if (heroSearch) heroSearch.classList.toggle("hidden", state.page !== "heroes"); + if (itemSearch) itemSearch.classList.toggle("hidden", state.page !== "items"); if (heroesView) heroesView.classList.toggle("hidden", state.page !== "heroes"); if (rankingsView) rankingsView.classList.toggle("hidden", state.page !== "rankings"); if (matchesView) matchesView.classList.toggle("hidden", state.page !== "matches"); diff --git a/web/frontend/config.js b/web/frontend/config.js index 81e744d..55b42aa 100644 --- a/web/frontend/config.js +++ b/web/frontend/config.js @@ -1,4 +1,5 @@ /* Local defaults; production export overwrites via export_relations_site.py. */ -var SITE_VERSION = "0.5.87"; +var SITE_VERSION = "0.5.109"; var ABILITY_VIDEO_BASE = ""; var STATIC_ASSET_BASE = ""; + diff --git a/web/frontend/index.html b/web/frontend/index.html index 2fffd21..80bd98a 100644 --- a/web/frontend/index.html +++ b/web/frontend/index.html @@ -5,17 +5,18 @@ DOTA2 上分帝 - - + + +

DOTA2 上分帝

@@ -110,14 +116,14 @@ @@ -128,7 +134,7 @@