68 KiB
ROK 英雄系统业务规则(1:1 迁移依据)
来源:
E:\Game\gmd\ROK服务端 Lua(snaxHero.luaproxy +HeroLogic.lualualib)+ 客户端 C# Proxy/Mediator/Config + sproto 协议用途:作为 Survivors 工程
cn.etetet.hero包实现的业务依据。所有规则均从 ROK 实代码反推,禁止凭空发挥。重写时若与本文档冲突,以 ROK 源码为准。关键文件锚点:
- 服务端业务核心:
Server/server/game_server/logic/lualib/HeroLogic.lua(923 行)- 服务端 RPC Handler:
Server/server/game_server/logic/service/proxy/Hero.lua(521 行)- 服务端 Entity 结构:
Server/common/lualib/define/HeroDef.lua- 服务端枚举:
Server/common/lualib/enum/HeroEnum.lua- 服务端战力计算:
Server/common/lualib/cacle/HeroCacle.lua- 协议定义:
Server/common/protocol/Protocol.sproto(行 968–1107、2619–2627)+Common.sproto(行 318–349 的.SkillInfo/.HeroInfo/.TalentTrees)- 客户端 Proxy:
Client/Assets/Scripts/Hotfix/MVC/Proxy/HeroProxy.cs(983 行)- 客户端 Notification:
Client/Assets/Scripts/Hotfix/MVC/CMD/HeroCmd.cs- 客户端列表+详情:
Client/Assets/Scripts/Hotfix/MVC/View_Mediator/Captain/CaptainMediator.cs(1350 行)- 客户端配置类:
Client/Assets/Scripts/Hotfix/Config/Hero*Define.cs(10 个)- 客户端错误码:
Client/Assets/Scripts/Hotfix/MVC/CMD/ErrorCode.cs行 149–176(3000–3027)
1. 整体业务范围
ROK 中英雄称作"统帅"(commander/captain),是 SLG 卡牌养成系统的核心。一个英雄绑定到军队上作为主将/副将出战。系统涵盖以下功能模块:
| # | 模块 | 一句话描述 |
|---|---|---|
| 1 | 招募(Summon) | 用对应碎片道具 (getItem × getItemNum) 兑换成统帅 Entity。不存在直接抽卡获得"英雄本体"——所有英雄都是"碎片够数即可招募"。 |
| 2 | 经验升级(Add Exp) | 用经验书道具增加 exp,到阈值升 level,受当前星级 starLimit 封顶。 |
| 3 | 升星(Star Up) | 用升星材料增加 starExp,溢出升 star;带幸运双倍机制;解锁高阶技能槽。 |
| 4 | 技能升级(Skill Level Up) | 消耗英雄招募同 ID 道具(多余碎片可用于升技能);升级目标技能是随机选择未满级的技能之一。 |
| 5 | 觉醒(Awake) | 前 4 个技能全满级时自动解锁第 5 个技能(觉醒技能,等级固定 1)。同时提供独立 RPC 手动触发。 |
| 6 | 天赋树(Talent Tree) | 3 套独立天赋页(talentIndex 1/2/3),逐节点学习;提供专精(Mastery)的累加加成。 |
| 7 | 天赋页切换 / 重置 / 改名 | 道具或钻石付费切换/重置;天赋页支持自定义名称。 |
| 8 | 装备穿戴(Equip / Take Off) | 8 个固定部位(头盔/胸甲/武器/手套/裤子/饰品 1/饰品 2/鞋子);同件装备切换英雄时自动从旧英雄卸下。 |
| 9 | 雕像兑换(Exchange) | 用"通用雕像"兑换为某英雄的招募碎片,前提是技能未全满。 |
| 10 | 战力计算(Power) | 基础分 + 等级分 + 技能分 + 天赋分(含专精)+ 装备分;任意改动后调 cacleSyncHistoryPower。 |
| 11 | 排序与分组 | 列表按 Rare / Star / Level / Power 排序;分三段:已拥有 / 可召唤 / 未集齐。 |
| 12 | 同步到部队 | 任意英雄改动会更新 RoleArmy 的战斗属性(技能、天赋属性、等级),通知战斗服务器。 |
| 13 | 行动力减免 | 主将通过技能/天赋影响军队行动力消耗(vitalityReduction)。 |
| 14 | 限时礼包触发 | 招募新英雄、英雄升级会触发 RechargeLogic.triggerLimitPackage(与英雄无强耦合,仅作业务关联)。 |
关键约束:ROK 没有抽卡(gacha)作为英雄获取入口。Tavern(酒馆)
Build_Tavern协议开宝箱产出的是道具+英雄碎片(RewardInfo.heros: *Heros{heroId, num, isNew}),玩家攒够碎片后主动调Hero_SummonHero招募。本质是"碎片养成"而非"抽英雄"。
2. Hero 数据模型
2.1 Hero Entity 字段(持久化)
来源:HeroDef.lua defaultHeroAttr + Common.sproto .HeroInfo。全部字段持久化到 MSM.d_hero[rid],由 req.Add / req.Set / req.Get 接口读写。
| 字段名 | 类型 | 含义 | 默认值 | 持久化 |
|---|---|---|---|---|
heroId |
int | 统帅配置 ID(对应 s_Hero 表主键) |
0 | ✓ |
star |
int | 当前星级 | 0(招募时置为 sHero.initStar) |
✓ |
starExp |
int | 当前星级进度(升星经验) | 0 | ✓ |
level |
int | 当前等级 | 0(招募时置 1) | ✓ |
exp |
int | 当前等级经验 | 0 | ✓ |
summonTime |
int | 招募时间戳(os.time) | 0 | ✓ |
soldierKillNum |
int | 累计士兵击杀数 | 0 | ✓ |
savageKillNum |
int | 累计野蛮人击杀数 | 0 | ✓ |
skills |
[]SkillInfo |
已解锁技能列表 | {}(招募时按星级解锁初始技能) |
✓ |
talentPoint |
int | 天赋点(遗留字段,实际不使用,见 §4.6 说明) | 0 | ✓ |
talentTrees |
map<int,TalentTrees> |
三套天赋页数据,key = 1/2/3 | {} |
✓ |
talentIndex |
int | 当前激活的天赋页 (1/2/3) | 1 | ✓ |
head |
int | 头盔位 itemIndex(背包索引) | 0 | ✓ |
breastPlate |
int | 胸甲位 itemIndex | 0 | ✓ |
weapon |
int | 武器位 itemIndex | 0 | ✓ |
gloves |
int | 手套位 itemIndex | 0 | ✓ |
pants |
int | 裤子位 itemIndex | 0 | ✓ |
accessories1 |
int | 饰品 1 位 itemIndex | 0 | ✓ |
accessories2 |
int | 饰品 2 位 itemIndex | 0 | ✓ |
shoes |
int | 鞋子位 itemIndex | 0 | ✓ |
2.2 子结构
SkillInfo(Common.sproto 行 318):
| 字段 | 类型 | 含义 |
|---|---|---|
skillId |
int | 技能配置 ID(对应 s_HeroSkill) |
skillLevel |
int | 当前技能等级(1 起) |
运行时
getRoleAllHeroSkills会临时附加talent: true(天赋衍生技能)、deputySkill: true(副将技能)这两个不持久化的标记位。
TalentTrees(Common.sproto 行 334):
| 字段 | 类型 | 含义 |
|---|---|---|
index |
int | 分页索引 (1/2/3) |
talentTree |
[]int |
已学习的天赋节点 ID 列表(s_HeroTalentGainTree 表 ID) |
name |
string | 天赋页自定义名称(玩家可改) |
2.3 同一玩家可拥有英雄数量上限
无显式上限。HeroLogic:addHero 仅判断"是否已拥有该 heroId"——已拥有则把招募道具转为对应数量的同 ID 道具退回(碎片返还,见 §4.1)。可拥有英雄数 = 配置表 s_Hero 中 listDisplay ≠ 1 的条目数。
实际 hero.json 有 46 条(其中部分为 listDisplay=1 的怪物英雄不参与列表),玩家最多拥有的英雄数 ≈ 配置表中 listDisplay=0 的条数。
3. 配置表清单
来源:
Client/Assets/Scripts/Hotfix/Config/Hero*Define.cs(C# 反序列化 DTO,与 ROK 服务端CFG.s_Hero*一一对应)+ 实际数据gmd\Unity\Assets\Bundles\Config\hero*.json(46 / 300 / 6 / 49 / 261 / 811 / 16 条)。
3.1 HeroDefine(英雄基础,hero.json = 46 条)
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 英雄 ID(主键,4 位,如 1000/1001/2007) |
l_nameID |
int | 名称语言包 ID |
civilization |
int | 所属文明 ID(对应 s_Civilization) |
l_appellationID |
int | 称号语言包 ID |
initStar |
int | 初始星级(招募时赋值给 Hero.star) |
star |
int | 星级上限(用于 IsStarMaxLevel 判断) |
rare |
int | 稀有度 1=普通/2=优秀/3=精英/4=史诗/5=传说 |
l_desID |
int | 英雄描述语言包 |
score |
int | 基础战力 |
getItem |
int | 招募所需道具 ID("招募碎片") |
getItemNum |
int | 招募所需道具数量(如 10) |
exchange |
int | 兑换通用雕像的道具 ID;0 = 不可兑换 |
l_heroLanguage |
[]int |
英雄台词语言包列表(点角色随机说话) |
getLimit |
int | 招募解锁的王国开服天数 |
skill |
[]int |
该英雄的 5 个技能 ID(索引 1-4 为普通技能,索引 5 为觉醒技能;可能少于 5) |
talent |
[]int |
该英雄的天赋节点 ID(3 个,对应 3 棵增益树 gainTree) |
listDisplay |
int | 列表显示标志;1=不显示(怪物英雄) |
heroIcon |
string | 头像 atlas 引用 |
heroModel |
string | Spine 模型 |
heroAction |
string | 序列帧动画 |
heroScene |
string | 英雄场景 prefab 名 |
heroScenePoint |
[]float |
召唤镜头位置(x/y/z) |
voiceOpening |
string | 开场白语音 |
voiceSelect |
string | 选中语音 |
voiceMove |
string | 移动语音 |
样本(ID=1000,文明 101,5 星传说):getItem=509010001,getItemNum=10(10 个碎片可招募),star=6,skill=[100001,100002,100003,100004,100005],talent=[100001,100002,100003]。
3.2 HeroLevelDefine(等级表,hero_level.json = 300 条)
5 种稀有度(
rareGroup1-5)× 60 级 = 300 条,ID 公式 =rare * 10000 + level - 1(见HeroProxy.cs行 184)。
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 主键 = rareGroup * 10000 + lv - 1 |
rareGroup |
int | 稀有度组(1-5) |
lv |
int | 等级(1-60) |
exp |
int | 升至下一级所需经验(满级时为 0 → IsMaxLevel 即 exp==0) |
soldiers |
int | 该等级总带兵量 |
score |
int | 该等级总战力(直接读取而非累加) |
starEffectData |
int | 该等级获得天赋点 |
3.3 HeroStarDefine(星级表,hero_star.json = 6 条)
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 星级 1-6 |
starLimit |
int | 该星级允许的等级上限(1→10,2→20,3→30,4→40,5→50,6→60) |
rare5 |
int | 5星英雄升下一星所需经验 |
rare4 |
int | 4星英雄升下一星所需经验 |
rare3 |
int | 3星英雄升下一星所需经验 |
rare2 |
int | 2星英雄升下一星所需经验 |
rare1 |
int | 1星英雄升下一星所需经验 |
starEffect |
int | 星级效果类型(1=普通;2=高阶,达到 5 星才解锁额外能力) |
l_starEffectID |
int | 星级效果语言包 |
starEffectData |
int | 星级效果参数(如天赋点:5★给 1 点,6★给 2 点) |
实际数值(来自
hero_star.json):
- 1★→2★:所有稀有度 500 经验
- 2★→3★:所有稀有度 1000 经验
- 3★→4★:5/4 星 6500,3/2/1 星 5000
- 4★→5★:5 星 25000,4 星 16000,3 星 12500,2 星 10000,1 星 8000
- 5★→6★:5 星 65000,4 星 48000,3 星 30000,2 星 20000,1 星 10000
- 6★:满级(rare1-5 均为 0)
3.4 HeroStarExpDefine(升星材料表,hero_star_exp.json = 49 条)
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 主键 |
rareGroup |
int | 该材料适用的英雄稀有度(必须与英雄 rare 一致) |
itemID |
int | 材料道具 ID |
exp |
int | 每个材料给的升星经验 |
lucky |
int | 每个材料贡献的幸运值(用于双倍判定) |
sure |
int | 是否为稀有保底材料(1=是,0=否) |
3.5 HeroSkillDefine(技能表,hero_skill.json = 261 条)
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 技能 ID(4–6 位,如 100001) |
l_nameID |
int | 名称语言包 |
type |
int | 技能类型(0=被动 74 / 1=主动 40 / 2=触发 117 / 3=持续 13 / 4=觉醒 17) |
l_typeID |
int | 类型语言包 |
skillBattleID |
[]int |
战斗效果 ID 列表 |
anger |
int | 怒气需求 |
open |
int | 解锁条件(0=默认;其他参考 unlockSkill) |
l_openID |
int | 解锁条件语言包 |
awakenEnhance |
int | 觉醒后增强标志 |
icon |
string | 技能图标 |
levelUpCostItem |
int | 升级所需道具(旧字段,已被 s_Hero.getItem 替代) |
l_mesID |
int | 技能描述语言包 |
lvEffectDate1..4 + l_lvEffect1..4 |
[]string + int |
4 段升级效果描述与数值 |
armyEffect |
string | 战斗特效路径 |
sound |
string | 技能音效 |
3.6 HeroSkillEffectDefine(技能效果表,hero_skill_effect.json = 811 条)
主键查找公式:
ID = skillId * 1000 + skillLevel(见HeroLogic.lua行 158、604)。
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 主键 = skillId * 1000 + level |
group |
int | 技能 ID(去除 level 部分) |
level |
int | 技能等级 |
score |
int | 该技能等级提供的总战力 |
attrTypeNew |
[]attrType |
客户端属性类型枚举数组 |
attrType |
[]string |
服务端属性名数组 |
attrNumber |
[]int |
对应属性的数值数组(与 attrType 同长度) |
3.7 HeroSkillLevelDefine(技能升级消耗,hero_skill_level.json = 16 条)
ID 100-115,对应"全英雄的总技能等级(去除技能数)+ 100",最大支持升 16 次。
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 主键 (100-115) |
level |
int | 第几次升级 (1-16) |
costItem1 |
int | 普通(1星)英雄消耗道具数 |
costItem2 |
int | 优秀(2星)英雄消耗道具数 |
costItem3 |
int | 精英(3星)英雄消耗道具数 |
costItem4 |
int | 史诗(4星)英雄消耗道具数 |
costItem5 |
int | 传说(5星)英雄消耗道具数 |
关键:消耗的道具就是该英雄的招募碎片 (
sHero.getItem),因此多余碎片可用于技能升级。
3.8 HeroTalentDefine(天赋根节点,每英雄 3 条)
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 主键 |
l_talentID |
int | 天赋名语言包 |
icon1 |
string | 小图标 |
gainTree |
string | 所属增益树 ID(注意是字符串,需 int.Parse) |
masteryGroupID |
int | 天赋专精组 ID |
l_tipsID |
int | tips 语言包 |
type |
int | 天赋类型(用于装备专属判定 checkHeroTalent) |
3.9 HeroTalentGainTreeDefine(天赋树节点表)
每个节点对应天赋页中一个可点的格子;客户端用
HeroTalentGainTreeDefine.ID来标识talentTree数组里的元素。
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 节点 ID |
gainTree |
int | 所属树组 ID(与 HeroTalent.gainTree 对应) |
level |
int | 天赋等级 / 层数(用于前置依赖:必须先点 level - 1 同组节点) |
score |
int | 该节点战力 |
icon |
string | 图标 |
nameID |
int | 名字语言包 |
descID |
int | 描述语言包 |
battleSkillID |
int | 战斗内生效技能 ID(>0 时附加给主将) |
attrTypeNew |
[]attrType |
客户端属性枚举 |
attrType |
[]string |
服务端属性名 |
attrNumber |
[]int |
属性值 |
3.10 HeroTalentMasteryDefine(天赋专精)
同一
group+ 累计点数(needTalentPoint)达到时激活,提供额外战力 + 属性 + 技能。
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 主键 |
group |
int | 所属增益树组 |
level |
int | 专精层(按累计点数排序) |
score |
int | 激活战力 |
name |
int | 名字语言包 |
descID |
int | 描述语言包 |
needTalentPoint |
int | 激活需要的累计天赋点数 |
battleSkillID |
int | 战斗内生效技能 |
attrType / attrTypeNew / attrNumber |
同上 | 属性加成 |
3.11 HeroTalentTypeDefine(天赋类型,用于装备专属)
| 字段 | 类型 | 含义 |
|---|---|---|
ID |
int | 类型 ID |
l_talentID |
int | 类型名语言包 |
icon |
string | 图标 |
equipIcon |
string | 装备天赋图标 |
3.12 全局/辅助配置(来自 s_ZeroEmpty / s_Config)
| Key | 含义 | 引用位置 |
|---|---|---|
Enum.ZeroEmptyType.HERO_SKILL_OPEN |
[heroId][star] = skillId,星级解锁哪个技能 |
unlockSkill |
Enum.ZeroEmptyType.TALENT |
[gainTree][nodeId] = true,节点归属增益树 |
checkHeroStudyTalent |
Enum.ZeroEmptyType.HERO_LEVEL_TALENT |
[rare][level] = point,等级影响的天赋点 |
checkTalentPoint |
Enum.ZeroEmptyType.HERO_STAR_TALENT |
[star] = point,星级影响的天赋点 |
checkTalentPoint |
s_Config.talentResetItemID |
切换/重置天赋页的免费道具 ID | ChangeTalentIndex / ResetTalent |
s_Config.heroNameLimit |
天赋页名称长度 [最小, 最大] |
ModifyTalentName |
s_Config.equipTalentPromote |
装备专属属性加成系数 | getHeroEquipAttr |
4. 业务规则详细(核心)
标注:
P*=Hero.luaproxy 的 RPC handler;L*=HeroLogic.lua函数。
4.1 招募 / 兑换英雄
4.1.1 英雄获取来源(真实业务流)
ROK 中英雄获取链路如下,绝非"抽卡":
酒馆开宝箱 (Build_Tavern, type=1银箱 / 2金箱)
↓ 服务端随机算法
RewardInfo {
items: [道具] (碎片 / 升星石 / 装备 / 通用资源 ...)
heros: [Heros{heroId, num, isNew}] // 实际是给"碎片数量",但走专门通道展示
}
↓ 玩家攒够 sHero.getItemNum (默认 10) 碎片
玩家主动调 Hero_SummonHero(heroId)
↓ 服务端扣碎片 + addHero
HeroLogic:addHero → 同步 Hero_HeroInfo + 触发限时礼包 + 调整城防主将
碎片本身是个普通道具(如 509010001),存在背包里,可以通过 Tavern 宝箱、活动、商店、邮件等多渠道获得。没有任何"直接给一个英雄实体"的入口——所有英雄都必须经过 Hero_SummonHero 这一步。
recruitLimit:ROK 源码无此字段。判定逻辑仅有 sHero.getLimit(开服天数解锁)。
4.1.2 重复招募处理(碎片返还)
如果调 Hero_SummonHero 时该英雄已存在(getHero 非空且非空表):
- 服务端 RPC 层会先验失败:
Hero.luaSummonHero行 42-46 返回HERO_ALREADY_EXIST (3002)。 - 但
HeroLogic:addHero内部还有一层兜底(行 41-52):如果已存在,把传入的本应该消耗的道具转换成同 ID 道具加回背包(实际是没扣到也没退到,因为 proxy 层先拒了)。这层兜底用于其他调用方(如直接发奖、活动给英雄)时的兼容。
4.1.3 完整流程(伪代码)
function SummonHero(rid, heroId):
// 步骤 1:参数校验
if not heroId: return ERR(HERO_ARG_ERROR)
sHero = CFG.s_Hero[heroId]
if not sHero: return ERR(CFG_ERROR)
// 步骤 2:开服天数校验
if getSelfNodeOpenDays() < sHero.getLimit: return ERR(HERO_OPEN_DAYS_NOT_ENOUGH)
// 步骤 3:是否已拥有
if HeroLogic:getHero(rid, heroId) is not empty: return ERR(HERO_ALREADY_EXIST)
// 步骤 4:碎片是否足够
if BagComponent.itemCount(sHero.getItem) < sHero.getItemNum:
return ERR(HERO_SUMMON_ITEM_NOT_ENOUGH)
// 步骤 5:扣碎片
ItemLogic:delItemById(rid, sHero.getItem, sHero.getItemNum, eventType=SUMMON_HERO_COST_ITEM)
// 步骤 6:创建英雄实体
HeroLogic:addHero(rid, heroId):
defHero = HeroDef:getDefaultHeroAttr()
defHero.heroId = heroId
defHero.star = sHero.initStar
defHero.level = 1
defHero.summonTime = os.time()
HeroLogic:unlockSkill(rid, heroId, initStar, defHero) // 按初始星级解锁初始技能
MSM.d_hero[rid].req.Add(rid, heroId, defHero) // 写库
HeroLogic:syncHero(rid, heroId, defHero, true) // 推 Hero_HeroInfo
RoleLogic:cacleSyncHistoryPower(rid, ..., recalc=true)
BuildingLogic:changeDefendHero(rid) // 自动调整城防主将
RechargeLogic:triggerLimitPackage(rid, NEW_HERO, sHero.rare)
ActivityRoleMgr.setActivitySchedule(rid, RECRUIT_HERO_COUNT, 1)
unlockSkill 步骤逻辑(HeroLogic.lua 行 391-406):
function unlockSkill(rid, heroId, star, heroInfo):
heroConfig = s_ZeroEmpty[HERO_SKILL_OPEN]
if heroConfig[heroId][star] exists and star != 5:
skillId = heroConfig[heroId][star]
if not already in heroInfo.skills:
heroInfo.skills.append({skillId, skillLevel=1})
即配置表
HERO_SKILL_OPEN定义"达到某星级解锁某技能"的映射。第 5 星不通过此处解锁(觉醒走另一通道)。
4.1.4 涉及 RPC
| 协议 | ID | 用途 |
|---|---|---|
Hero_SummonHero |
601 | 客户端主动招募 |
Hero_HeroInfo |
30201 | 服务端推送新增/变化的英雄(含 noShow 标志屏蔽 UI 弹窗) |
4.1.5 涉及错误码
HERO_ARG_ERROR (3000)、CFG_ERROR、HERO_OPEN_DAYS_NOT_ENOUGH (3000)、HERO_ALREADY_EXIST (3002)、HERO_SUMMON_ITEM_NOT_ENOUGH (3001)。
4.2 升级(经验)
4.2.1 经验来源
- 唯一入口
Hero_AddHeroExp(602):使用s_Item.subType为"经验书"类的道具,按desData1× 数量加经验(Hero.lua行 160)。
4.2.2 等级计算
s_HeroLevel主键 =rareGroup * 10000 + level - 1(注意 -1 偏移,见HeroProxy.cs行 184)- 服务端按
rare * 10000 + level查s_HeroLevel:Get的exp字段,即"升到下一级所需经验" - 当
s_HeroLevel.exp == 0时为满级(HeroProxy.IsMaxLevel)
4.2.3 升级时属性变化
ROK 不在升级时单独"刷属性"——属性按需查表:
- 部队带兵量 =
s_HeroLevel.soldiers - 战力 =
s_HeroLevel.score直接读(非累加) - 技能/天赋/装备的属性独立由
getHeroAttr即时合并(见 §4.8)
升级会触发:
RechargeLogic:triggerLimitPackage(HERO_LEVEL_UP, rare, level)updateMapArmyHero同步到地图部队ArmyLogic:updateArmyInfoOnHeroInfoChange更新军队信息ActivityRoleMgr.setActivitySchedule(HERO_LEVEL_COUNT)cacleSyncHistoryPower重算战力
4.2.4 等级与星级关系
升级硬封顶 = 当前星级的 s_HeroStar.starLimit(如 1 星英雄最多升到 10 级)。addHeroExp 行 412-414:
if heroInfo.level == sHeroStar.starLimit: return false
到达上限后继续加经验:经验清零(行 434-436)而非保留。
4.2.5 完整流程(伪代码)
function AddHeroExp(rid, heroId, itemId, itemNum):
heroInfo = HeroLogic:getHero(rid, heroId)
sHeroStar = CFG.s_HeroStar[heroInfo.star]
if heroInfo.level == sHeroStar.starLimit: return ERR(HERO_LEVEL_LIMIT)
if BagComponent.itemCount(itemId) < itemNum: return ERR(ITEM_NOT_ENOUGH)
sItem = CFG.s_Item[itemId]
addExp = sItem.desData1 * itemNum
BagComponent.delItem(itemId, itemNum)
HeroLogic:addHeroExp(rid, heroId, addExp):
sHeroStar = CFG.s_HeroStar[heroInfo.star]
if heroInfo.level == sHeroStar.starLimit: return false
sHero = CFG.s_Hero[heroId]
heroLevelId = sHero.rare * 10000 + heroInfo.level
exp = CFG.s_HeroLevel[heroLevelId].exp
heroInfo.exp += addExp
while heroInfo.exp >= exp:
heroInfo.level += 1
RechargeLogic:triggerLimitPackage(HERO_LEVEL_UP, sHero.rare, level)
if heroInfo.level == sHeroStar.starLimit:
heroInfo.exp = 0
break
else:
heroInfo.exp -= exp
exp = CFG.s_HeroLevel[sHero.rare * 10000 + heroInfo.level].exp
setHero & syncHero
if uplevel:
cacleSyncHistoryPower / updateMapArmyHero / updateArmyInfo / setActivitySchedule
4.2.6 RPC + 错误码
| RPC | ID | 错误码 |
|---|---|---|
Hero_AddHeroExp |
602 | HERO_LEVEL_LIMIT (3011), ITEM_NOT_ENOUGH |
4.3 升星
4.3.1 升星材料
-
每次升星可一次性提交最多 6 个升星材料(
Hero.lua行 214)。 -
每个材料对应
s_HeroStarExp表一条:rareGroup:必须与英雄稀有度 (sHero.rare) 一致exp:每个材料贡献的升星经验lucky:每个材料贡献的幸运值
-
计算公式(行 219-229):
total_exp = Σ(material.exp × material.itemNum) total_lucky = Σ(material.lucky × material.itemNum)
4.3.2 幸运系数
Hero.lua 行 231-233:
if total_lucky >= Random.Get(1, 100): // 即概率 = min(total_lucky, 100) %
total_exp *= 2
也就是说:lucky 直接作为"双倍触发概率(%)"。理论上 lucky ≥ 100 时必触发双倍,但
Random.Get(1,100)的下限是 1,因此lucky=100 时必触发。
4.3.3 升星后属性变化
属性按需查表,参考 §4.2.3。升星仅会:
- 解锁可能的新技能(
unlockSkill(rid, heroId, newStar),见 §4.1.3) - 增加可用天赋点(通过
s_ZeroEmpty.HERO_STAR_TALENT累加)
4.3.4 升星上限
s_HeroStar总条数 = 6(从配置看 6 星即满)- 服务端:
if not CFG.s_HeroStar:Get(heroInfo.star + 1): return ERR(HERO_STAR_MAX) - 6 星时
rare1-5均为 0,即不需要经验也不能再升
4.3.5 升星前置:等级必须达到本星级上限
Hero.lua 行 185-188:
if heroInfo.level < sHeroStar.starLimit: return ERR(HERO_LEVEL_NO_ENOUGH)
4.3.6 完整流程(伪代码)
function HeroStarUp(rid, heroId, items: [{itemId, itemNum}]):
heroInfo = HeroLogic:getHero(rid, heroId)
sHeroStar = CFG.s_HeroStar[heroInfo.star]
sHero = CFG.s_Hero[heroId]
// 校验链
if not CFG.s_HeroStar[heroInfo.star + 1]: return ERR(HERO_STAR_MAX) // 3012
if heroInfo.level < sHeroStar.starLimit: return ERR(HERO_LEVEL_NO_ENOUGH) // 3013
if items is empty: return ERR(HERO_NO_ITEM) // 3014
for item in items:
if CFG.s_HeroStarExp[item.itemId].rareGroup != sHero.rare:
return ERR(HERO_ITEM_ERROR) // 3015
if BagComponent.itemCount(item.itemId) < item.itemNum:
return ERR(ITEM_NOT_ENOUGH)
if Σ(item.itemNum) > 6: return ERR(HERO_ITEM_TO_MUCH) // 3016
lucky = 0; addExp = 0
for item in items:
sHeroStarExp = CFG.s_HeroStarExp[item.itemId]
lucky += sHeroStarExp.lucky * item.itemNum
addExp += sHeroStarExp.exp * item.itemNum
BagComponent.delItem(item.itemId, item.itemNum)
if lucky >= Random(1,100): addExp *= 2 // 双倍
HeroLogic:addHeroStarExp(rid, heroId, addExp):
if not CFG.s_HeroStar[heroInfo.star + 1]: return false
heroInfo.starExp += addExp
while heroInfo.starExp >= curStarReqExp:
heroInfo.star += 1
HeroLogic:unlockSkill(rid, heroId, heroInfo.star, heroInfo)
if not next star: heroInfo.starExp = 0; break
else: heroInfo.starExp -= curStarReqExp; refresh exp threshold
cacleSyncHistoryPower / updateMapArmyHero
setActivitySchedule(HERO_STAR_LEVEL_COUNT, 1)
4.3.7 RPC + 错误码
| RPC | ID | 错误码 |
|---|---|---|
Hero_HeroStarUp |
606 | HERO_STAR_MAX (3012), HERO_LEVEL_NO_ENOUGH (3013), HERO_NO_ITEM (3014), HERO_ITEM_ERROR (3015), HERO_ITEM_TO_MUCH (3016), ITEM_NOT_ENOUGH |
4.4 技能升级
4.4.1 技能槽位含义
- 槽位 1-4:普通技能(被动/主动/触发/持续,看
s_HeroSkill.type)。这些技能通过unlockSkill按"达到某星级"解锁,技能 ID 通常为heroId * 100 + n(n=1..4)。 - 槽位 5:觉醒技能。技能 ID 为
heroId * 100 + 5。仅当 1-4 技能全部满级(5 级)时自动解锁(见 §4.5)。
4.4.2 升级消耗
-
道具:
sHero.getItem(即招募碎片)。升技能与招募共享同一道具池。 -
数量:查
s_HeroSkillLevel,主键计算:upLevelIndex = (Σ skillLevel) - skillCount + 1 // 第几次升级 costItemId = sHero.getItem cost = sHeroSkillLevel[upLevelIndex].costItemN // N = rare (1-5)- 客户端等价 (
HeroProxy.cs行 405):id = 100 + GetSkillAllLevel() - GetSkillCount()(注意客户端用 100 偏移作为 ID)。
- 客户端等价 (
-
金币:ROK 无金币消耗(仅消耗碎片)。
4.4.3 升级上限
- 单技能最大等级 = 5(
HeroProxy.IsAllSkillMax:GetSkillAllLevel() >= 20,即 4 个技能 × 5 级) - 整体上限通过
s_HeroSkillEffect表是否存在(skillId * 1000 + nextLevel)来判定(HeroLogic.lua行 158)
4.4.4 升级目标的随机性 ⚠️
这是 ROK 一个非常重要且非直观的设计——服务端不允许玩家选择升哪个技能!
function heroSkillLevelUp(rid, heroId):
skillIds = []
for skillInfo in heroInfo.skills:
if CFG.s_HeroSkillEffect[skillInfo.skillId * 1000 + skillInfo.skillLevel + 1] exists:
skillIds.append({id: skillInfo.skillId, rate: 1000})
// 扣道具
BagComponent.delItem(sHero.getItem, count)
// 随机选一个可升级的技能
skillId = Random.GetId(skillIds) // 等权重随机(rate 都是 1000)
skillLevel = ++ for that skillId
syncHero(...)
if checkHeroSkillFull(rid, heroId) AND has 5th skill:
heroAwake(rid, heroId) // 自动触发觉醒
返回 {skillId, skillLevel},客户端通过 Hero_HeroSkillLevelUp.response 收到这个结果,弹窗展示"恭喜你升级了 XX 技能"。
4.4.5 RPC + 错误码
| RPC | ID | 错误码 |
|---|---|---|
Hero_HeroSkillLevelUp |
603 | HERO_NOT_EXIST (3003), HERO_SKILL_MAX (3004), HERO_LEVEL_ITEM_NOT_ENOUGH (3005) |
4.5 觉醒
4.5.1 觉醒条件
- 该英雄存在第 5 个技能配置:
CFG.s_HeroSkill[heroId * 100 + 5]不为空 - 前 4 个技能全部满级:
HeroLogic:checkHeroSkillFull(rid, heroId)返回 true(即没有任何技能能继续升级) Hero.lua行 101 校验:if not table.size(heroInfo.skills) < 4 or not checkHeroSkillFull: return ERR(HERO_SKILL_NOT_MAX)- 注意源码这里有运算符优先级问题(
not table.size < 4实际为(not table.size) < 4→false < 4总成立)。实际生效条件为第二部分checkHeroSkillFull。重写时按"技能数 >= 4 且全满"标准实现即可。
- 注意源码这里有运算符优先级问题(
4.5.2 觉醒消耗
无任何消耗——heroAwake 直接把第 5 技能加入 heroInfo.skills,level=1。
4.5.3 觉醒效果
- 解锁第 5 个技能(觉醒技),等级固定 1。
- 技能 ID 公式:
heroId * 100 + 5 - 战力重算 + 同步部队
4.5.4 自动 vs 手动
- 自动:在
heroSkillLevelUp升级技能后会判断,如果满足条件自动调heroAwake,无需玩家主动操作。 - 手动:
Hero_HeroAwakeRPC 是冗余入口(兼容性),实际由前 4 技能升满后自动触发。
4.5.5 完整流程
function heroAwake(rid, heroId):
skillId = heroId * 100 + 5
if skillId already in heroInfo.skills: return existing
heroInfo.skills.append({skillId, skillLevel=1})
setHero & syncHero(true)
cacleSyncHistoryPower
return {skillId, skillLevel=1}
4.5.6 RPC + 错误码
| RPC | ID | 错误码 |
|---|---|---|
Hero_HeroAwake |
604 | HERO_NOT_EXIST (3003), HERO_NOT_AWAKE (3006), HERO_SKILL_NOT_MAX (3007) |
4.6 天赋树
4.6.1 三套天赋页(TalentIndex 1/2/3)
- 每个英雄有 3 套独立的天赋页,对应
talentTrees[1]、talentTrees[2]、talentTrees[3]。 - 玩家可任意切换"当前激活的天赋页" (
talentIndex),仅激活页的天赋影响战力和战斗属性。 - 切换时需要消耗道具或钻石(详见 §4.6.5)。
服务端校验
if index > 3: return ERR(HERO_TALENT_INDEX_ERROR)。索引范围 1-3。
4.6.2 天赋点来源
heroInfo.talentPoint 字段已废弃(HeroDef.lua 仍保留,但 HeroLogic.lua 中所有"消耗 talentPoint"的代码都被注释掉了,见行 62-69、428-432、488-491、618)。
实际"当前可用天赋点"通过实时计算:
function checkTalentPoint(rid, heroId, index):
heroInfo = getHero(rid, heroId)
rare = sHero.rare
heroLevelMap = s_ZeroEmpty[HERO_LEVEL_TALENT][rare] // {[level] = point}
heroStarMap = s_ZeroEmpty[HERO_STAR_TALENT] // {[star] = point}
talentPoint = 0
for (level, point) in heroLevelMap:
if heroInfo.level >= level: talentPoint += point // 累加所有已达成的等级阶段
for (star, point) in heroStarMap:
if heroInfo.star >= star: talentPoint += point // 累加所有已达成的星级阶段
// 减去当前天赋页已用点数
if heroInfo.talentTrees[index]:
for _ in heroInfo.talentTrees[index].talentTree:
talentPoint -= 1
return talentPoint > 0
关键约束:所有 3 套天赋页共用同一池天赋点(每页独立计算用了多少,但池子相同)。
客户端等价 HeroProxy.GetTotalTalentPoint(行 249-271):遍历 s_HeroLevel 与 s_HeroStar 表,累加所有满足条件的 starEffectData。
4.6.3 节点前置依赖
节点学习需满足三个条件(依次校验,见 Hero.lua 行 252-271):
- 天赋点充足 (
checkTalentPoint),否则HERO_TALENT_POINT_NOT_ENOUGH(3017)。 - 该英雄能学这个天赋 (
checkHeroStudyTalent):- 取
sHero.talent对应的根天赋(3 个)→ 它们的gainTree - 看
s_ZeroEmpty.TALENT[gainTree][id]是否为真 - 否则
HERO_CAN_NOT_STUDY_TALENT(3018)
- 取
- 前置节点已学 (
checkHeroStudyTalentPre):- 取目标节点
level - 若
level == 1:允许(无前置) - 否则:检查同一页
talentTree中是否存在 level =level - 1的节点 - 否则
HERO_TALENT_PRE_NOT_STUDY(3019)
- 取目标节点
- 同级互斥(同一 level 同一页只能选一个,见
checkHeroStudyTalentSame):- 若同一页已有同
level的节点 →HERO_STUDY_SAME_LEVEL_TALENT(3020)
- 若同一页已有同
4.6.4 学习消耗
学习单个节点无任何道具/资源消耗——仅消耗"天赋点"(隐式记账)。
4.6.5 切换页 vs 重置页
两者都用 Hero_ChangeTalentIndex (608) 和 Hero_ResetTalent (609) 两个 RPC。关键区别:
-
Hero_ChangeTalentIndex:切换激活页(talentIndex)- 若
index == heroInfo.talentIndex:返回HERO_TALENT_INDEX_SAME(3022) - 消耗:
useDenar=false时扣 1 个s_Config.talentResetItemID道具;useDenar=true时扣s_Item[id].shortcutPrice钻石 - 失败:
ITEM_NOT_ENOUGH/ROLE_DENAR_NOT_ENOUGH
- 若
-
Hero_ResetTalent:重置某页的所有已学天赋- 若
index == heroInfo.talentIndex(重置当前页):才需要消耗道具/钻石(同上) - 若重置非当前页:免费
- 操作:
heroInfo.talentTrees[index].talentTree = {}(清空)
- 若
4.6.6 天赋页改名
Hero_ModifyTalentName (610):
- 名称长度校验:
s_Config.heroNameLimit[1] <= utf8.len(name) <= heroNameLimit[2] - 失败:
HERO_TALENT_NAME_LENGTH_ERROR(3023) - 写入:
heroInfo.talentTrees[index].name = name
4.6.7 天赋专精(Mastery)
每个 gainTree 有多个 s_HeroTalentMastery 阶段。属性/技能加成在 getHeroTalentAttr 中根据已学节点数 (count) 计算:
function getHeroTalentAttr(rid, heroId):
talentTree = heroInfo.talentTrees[heroInfo.talentIndex].talentTree
battleSkill = {}
battleAttr = {}
talentCount = {} // {gainTree -> count}
// 节点本身的加成
for id in talentTree:
node = s_HeroTalentGainTree[id]
if node.battleSkillID > 0: battleSkill[node.battleSkillID] = node.battleSkillID
for i, attrName in node.attrType: battleAttr[attrName] += node.attrNumber[i]
talentCount[node.gainTree] += 1
// 专精的加成
sHeroTalentMastery = CFG.s_HeroTalentMastery:Get() // 整张表
for (gainTree, count) in talentCount:
for i from count downto 1:
mastery = sHeroTalentMastery[gainTree][i]
if mastery exists:
// 触发该层专精
if mastery.battleSkillID > 0: battleSkill[battleSkillID] = battleSkillID
for j, attrName in mastery.attrType: battleAttr[attrName] += mastery.attrNumber[j]
return {battleSkill, battleAttr}
⚠ ROK 此处 mastery 是按
[gainTree][index]二级索引取,index 含义是"按已学点数顺序排",并非客户端HeroProxy.talentScore用的needTalentPoint阈值——两者实现略有差异,重写时应以服务端 Lua 为准。
4.6.8 完整流程(伪代码)
function TalentUp(rid, heroId, id, index):
heroInfo = HeroLogic:getHero(rid, heroId)
if not heroInfo: return ERR(HERO_NOT_EXIST)
if not checkTalentPoint(rid, heroId, index): return ERR(HERO_TALENT_POINT_NOT_ENOUGH)
if not checkHeroStudyTalent(heroId, id): return ERR(HERO_CAN_NOT_STUDY_TALENT)
if not checkHeroStudyTalentPre(rid, heroId, index, id): return ERR(HERO_TALENT_PRE_NOT_STUDY)
if not checkHeroStudyTalentSame(rid, heroId, index, id): return ERR(HERO_STUDY_SAME_LEVEL_TALENT)
// 学习
if not heroInfo.talentTrees[index]:
heroInfo.talentTrees[index] = {index, talentTree=[], name=""}
heroInfo.talentTrees[index].talentTree.append(id)
setHero & syncHero(true)
TaskLogic:addTaskStatisticsSum(HERO_TALENT_NUM, 1)
cacleSyncHistoryPower
updateMapArmyHero(rid, heroId)
return {result: true}
4.6.9 RPC + 错误码汇总
| RPC | ID | 错误码 |
|---|---|---|
Hero_TalentUp |
607 | HERO_NOT_EXIST, HERO_TALENT_POINT_NOT_ENOUGH (3017), HERO_CAN_NOT_STUDY_TALENT (3018), HERO_TALENT_PRE_NOT_STUDY (3019), HERO_STUDY_SAME_LEVEL_TALENT (3020) |
Hero_ChangeTalentIndex |
608 | HERO_NOT_EXIST, HERO_TALENT_INDEX_ERROR (3021), HERO_TALENT_INDEX_SAME (3022), ITEM_NOT_ENOUGH, ROLE_DENAR_NOT_ENOUGH |
Hero_ResetTalent |
609 | HERO_NOT_EXIST, HERO_TALENT_INDEX_ERROR, ITEM_NOT_ENOUGH, ROLE_DENAR_NOT_ENOUGH |
Hero_ModifyTalentName |
610 | HERO_TALENT_NAME_LENGTH_ERROR (3023), HERO_NOT_EXIST, HERO_TALENT_INDEX_ERROR |
4.7 装备穿戴
4.7.1 装备槽位(8 个)
来源:Hero.lua 行 430-437 + BagProxy.cs EquipSubType 枚举。
| equipIndex | 字段名 | EquipSubType | s_Item.subType |
|---|---|---|---|
| 1 | head |
Head | HELMET |
| 2 | breastPlate |
BreastPlate | BREASTPLATE |
| 3 | weapon |
Weapon | ARMS |
| 4 | gloves |
Gloves | GLOVES |
| 5 | pants |
Pants | PANTS |
| 6 | accessories1 |
accessories1 | ACCESSORIES |
| 7 | accessories2 |
accessories2 | ACCESSORIES |
| 8 | shoes |
shoes | SHOES |
字段值是背包道具的 itemIndex(不是 itemId),0 = 未装备。
4.7.2 穿戴规则
- 道具必须存在 (
getItem(rid, itemIndex)非空) s_Item.subType必须与目标槽位的subType一致;否则HERO_EQUIP_SUBTYPE_ERROR(3025)- 英雄等级必须 ≥
s_Equip.useLevel;否则HERO_EQUIP_LV_NO_ENOUGH(3024) - 英雄必须处于待命状态(不在出征/守城军队中);否则
HERO_EQUIP_NOT_IN_CITY(3026)- 通过
checkHeroIdle遍历ArmyLogic:getArmy(rid),检查mainHeroId/deputyHeroId
- 通过
4.7.3 同一装备切换英雄(重要)
Hero_HeroWearEquip 中(Hero.lua 行 459-467):
- 若该 itemInfo 已被其他英雄装备 (
itemInfo.heroId > 0):从旧英雄上清空对应槽位 (beforeHeroInfo[attr] = 0) - 若目标英雄的目标槽位已有装备:把旧装备的
heroId清 0 - 写入:
heroInfo[attr] = itemIndex,itemInfo.heroId = heroId
4.7.4 卸下装备
Hero_TakeOffEquip (612):
- 校验英雄待命
itemInfo.heroId = 0;heroInfo[attr] = 0- 同步 Hero + Item
4.7.5 与背包的联动(字段需要在 Item Entity 加 heroId 反向索引)
s_Item是道具基础,s_Equip是装备扩展(compose套装组 ID、useLevel、attAddEx等)s_EquipAtt定义属性枚举s_EquipCompose定义套装:compose2/4/6/8件数效果,分别提供compose2/4/6/8AddEx属性加成- 专属装备加成:
itemInfo.exclusive > 0且checkHeroTalent(heroId, exclusive)时,属性乘s_Config.equipTalentPromote × 2并向上 0.5 取整(HeroLogic.lua行 822-825)
4.7.6 RPC + 错误码
| RPC | ID | 错误码 |
|---|---|---|
Hero_HeroWearEquip |
611 | ITEM_NOT_EXIST, HERO_EQUIP_SUBTYPE_ERROR (3025), HERO_EQUIP_LV_NO_ENOUGH (3024), HERO_EQUIP_NOT_IN_CITY (3026), HERO_EQUIP_ALREADY_WEAR (3027) |
Hero_TakeOffEquip |
612 | HERO_EQUIP_NOT_IN_CITY (3026) |
4.8 战力计算
4.8.1 战力公式
来源:HeroCacle.lua 全文 + HeroProxy.cs.skillScore/talentScore。
power(hero) =
sHero.score // ① 基础战力
+ s_HeroLevel[rare*10000 + level].score // ② 等级战力(直接读,非累加)
+ Σ s_HeroSkillEffect[skillId*1000 + lv].score // ③ 技能战力(所有已学技能)
+ Σ s_HeroTalentGainTree[id].score // ④ 天赋节点战力(仅当前激活页)
+ Σ s_HeroTalentMastery[group][i].score // ⑤ 天赋专精战力(按已学节点数触发的所有阶段)
+ [装备分由 getHeroEquipAttr 间接生效,但 HeroCacle 中并未独立计算"装备战力分"]
⚠ ROK 服务端
HeroCacle:caclePower没有显式"装备分"参与战力——装备只影响战斗时的属性,不直接进总战力。客户端HeroProxy.UpdatePower同样只算base + level + skill + talent。这一点重写时需要决策:要不要把装备纳入战力。ROK 原版不纳入。
4.8.2 属性查询:getHeroAttr
战斗时按属性名查询英雄某项属性:
function getHeroAttr(rid, heroId, attrName, notCancleTalent=false):
num = 0
if not notCancleTalent:
num += getHeroTalentAttr(rid, heroId).battleAttr[attrName] // 天赋
num += getHeroEquipAttr(rid, heroId).battleAttr[attrName] // 装备
num += cancleskillAttr(rid, heroId)[attrName] // 技能
return num
副将(
deputyHero)的天赋属性不参与计算(notCancleTalent=true,见行 917)。仅主将享受天赋加成。
4.8.3 战力变化触发的事件
任意会影响战力的操作完成后会调:
RoleLogic:cacleSyncHistoryPower(rid, nil, nil, recalc=true) // 重算并同步
并对涉及到的部队同步:
updateMapArmyHero(rid, heroId, newLevel?):
遍历角色所有部队
如果该 heroId 是 main/deputy
重新算 skills + talentAttr + levels
通过 BattleAttrLogic:syncObjectHeroChange 通知战斗服务器
通过 ResourceLogic:roleArmyCollectSpeedChange 更新采集速度
4.9 其他次要功能
4.9.1 英雄列表排序
4 种排序(HeroProxy.SortType):Rare / Star / Level / Power(还有 Recomend 未实现)。
所有比较器(CompareHeroByXxx,共 4 个)都遵循通用规则:
xScore = (x.data != null) ? 1 : (x.itemCount < x.config.getItemNum ? 0 : 2)
yScore = (同上)
// 排序优先级:未集齐(0) < 已拥有(1) < 可召唤(2)
if xScore != yScore: 按 xScore 排
// 同分组内:按主排序字段(rare/star/level/power),同分用 (rare → star → level → power → summonTime) 依次比
最后 heros.Reverse()(默认降序)。
4.9.2 三段分组
GetHerosBySort 返回三个 List:
own:hero.data != null(已拥有)summon:hero.itemCount >= getItemNum(碎片够,可召唤)nosummon:itemCount < getItemNum(碎片不够)
⚠ hero.config.listDisplay == 1 的英雄不进任何分组(怪物英雄,列表不显示)。
4.9.3 列表筛选
ROK 客户端没有显式的"按文明/职业筛选"功能。仅有上述排序 + 分组。
4.9.4 雕像兑换
Hero_ExchangeHeroItem (605):用 sHero.exchange 道具(通用雕像)兑换为 sHero.getItem 道具(具体英雄碎片)。
校验链:
sHero.exchange == Enum.Exchange.NO (0)→HERO_NOT_EXCHANGE(3008)- 该英雄已存在且技能数 ≥4 且全满级 →
HERO_EXCHANGE_SKILL_MAX(3009)(技能满了说明不需要再兑换碎片) - 雕像道具不足 →
HERO_EXCHANGE_ITEM_NOT_ENOUGH(3010)
操作:扣 sHero.exchange × itemNum → 加 sHero.getItem × itemNum(1:1 转换)。
4.9.5 红点统计
HeroProxy.GetCanSummonerHeroCount 每个英雄遍历,满足任一条件 +1:
- 已拥有 +
IsCanUpSkill(可升技能:未全满 + 碎片够) - 已拥有 +
level == starLimit且!IsStarMaxLevel(可升星) - 已拥有 +
GetCurPageRemainPoint(talentIndex) > 0(有天赋点未用) - 未拥有 + 碎片够(可召唤)
5. 协议清单(C2G / G2C / 推送)
模块号:Hero Module 601-700(请求),Hero Module 30201-30250(推送)。
| 协议名 | 协议号 | 方向 | request | response | 用途 |
|---|---|---|---|---|---|
Hero_SummonHero |
601 | C2G | heroId |
无 | 招募英雄 |
Hero_AddHeroExp |
602 | C2G | heroId, itemId, itemNum |
result, itemId, itemNum |
使用经验书升级 |
Hero_HeroSkillLevelUp |
603 | C2G | heroId |
skillId, skillLevel |
升技能(服务端随机选目标) |
Hero_HeroAwake |
604 | C2G | heroId |
skillId, skillLevel |
手动觉醒(一般由技能升级自动触发) |
Hero_ExchangeHeroItem |
605 | C2G | heroId, itemNum |
result |
雕像兑换碎片 |
Hero_HeroStarUp |
606 | C2G | heroId, items: [{itemId, itemNum}] |
result |
升星(最多 6 个材料) |
Hero_TalentUp |
607 | C2G | heroId, id, index |
result |
学习单个天赋节点 |
Hero_ChangeTalentIndex |
608 | C2G | heroId, index, useDenar |
result |
切换激活的天赋页 |
Hero_ResetTalent |
609 | C2G | heroId, index, useDenar |
result |
重置某天赋页 |
Hero_ModifyTalentName |
610 | C2G | heroId, index, name |
result |
改天赋页名 |
Hero_HeroWearEquip |
611 | C2G | heroId, itemIndex, equipIndex |
result |
穿戴装备 |
Hero_TakeOffEquip |
612 | C2G | heroId, equipIndex |
result |
卸下装备 |
Hero_HeroInfo |
30201 | G2C 推送 | heroInfo: *HeroInfo(heroId), noShow: bool |
无 | 推送英雄完整数据(新增/变化);noShow=true 表示酒馆场景不要弹"获得英雄"展示 |
关联协议(其他模块发布的 hero 相关):
| 协议名 | 协议号 | 方向 | 用途 |
|---|---|---|---|
Build_Tavern |
407 | C2G | 酒馆开宝箱(间接获取英雄碎片) |
Build_DefendHero |
410 | C2G | 设置驻防主/副将 |
Shop_GetLimitHeroInfo |
907 | C2G | 限时英雄商店(碎片售卖) |
子结构定义(Common.sproto):
.SkillInfo { skillId, skillLevel }.TalentTrees { index, talentTree: *integer, name }.HeroInfo { heroId, star, starExp, level, exp, summonTime, soldierKillNum, savageKillNum, skills, talentTrees, talentIndex, head, breastPlate, weapon, gloves, pants, accessories1, accessories2, shoes }.Heros { heroId, num, isNew }(Tavern 宝箱用,给的是碎片数 num)
6. 错误码清单
来源:ErrorCode.cs 行 24–26(Role)+ 149–176(Hero)+ 224(Building)+ 473(Expedition)。
| 错误码 | 数值 | 含义 |
|---|---|---|
ROLE_HERO_NOT_EXIST |
1004 | 角色场景下英雄不存在 |
ROLE_HERO_NOT_WAIT_STATUS |
1005 | 英雄未处于待命状态 |
ROLE_HERO_STAR_NOT_ENOUGH |
1006 | 英雄星级不足 |
HERO_OPEN_DAYS_NOT_ENOUGH |
3000 | 招募时所在王国未到该英雄解锁天数 |
HERO_SUMMON_ITEM_NOT_ENOUGH |
3001 | 招募碎片不足 |
HERO_ALREADY_EXIST |
3002 | 英雄已存在 |
HERO_NOT_EXIST |
3003 | 英雄不存在 |
HERO_SKILL_MAX |
3004 | 技能已满级,无法升级 |
HERO_LEVEL_ITEM_NOT_ENOUGH |
3005 | 升技能道具不足 |
HERO_NOT_AWAKE |
3006 | 该英雄不存在觉醒技能 |
HERO_SKILL_NOT_MAX |
3007 | 技能未满,无法觉醒 |
HERO_NOT_EXCHANGE |
3008 | 该英雄无法使用通用雕像兑换 |
HERO_EXCHANGE_SKILL_MAX |
3009 | 技能已满,无需兑换碎片 |
HERO_EXCHANGE_ITEM_NOT_ENOUGH |
3010 | 通用雕像不足 |
HERO_LEVEL_LIMIT |
3011 | 等级达到当前星级上限,无法升级 |
HERO_STAR_MAX |
3012 | 星级已满 |
HERO_LEVEL_NO_ENOUGH |
3013 | 等级未达到 starLimit,无法升星 |
HERO_NO_ITEM |
3014 | 升星材料为空 |
HERO_ITEM_ERROR |
3015 | 升星材料的 rareGroup 与英雄稀有度不符 |
HERO_ITEM_TO_MUCH |
3016 | 单次升星材料数 > 6 |
HERO_TALENT_POINT_NOT_ENOUGH |
3017 | 天赋点不足 |
HERO_CAN_NOT_STUDY_TALENT |
3018 | 该英雄无法学习此天赋节点 |
HERO_TALENT_PRE_NOT_STUDY |
3019 | 前置天赋未学 |
HERO_STUDY_SAME_LEVEL_TALENT |
3020 | 同 level 天赋已学过 |
HERO_TALENT_INDEX_ERROR |
3021 | 天赋页索引非法(>3) |
HERO_TALENT_INDEX_SAME |
3022 | 切换的天赋页与当前页相同 |
HERO_TALENT_NAME_LENGTH_ERROR |
3023 | 天赋页名称长度违规 |
HERO_EQUIP_LV_NO_ENOUGH |
3024 | 英雄等级低于装备 useLevel |
HERO_EQUIP_SUBTYPE_ERROR |
3025 | 装备 subType 与槽位不匹配 |
HERO_EQUIP_NOT_IN_CITY |
3026 | 英雄不在城内(出征中) |
HERO_EQUIP_ALREADY_WEAR |
3027 | 该装备已被装备(实际代码中走"自动卸旧再穿新",此码可能未触发) |
BUILDING_WALL_HERO_SAME |
6019 | 驻防主副将相同 |
EXPEDITION_HERO_NOT_FREE |
17004 | 远征英雄非空闲 |
通用错误码(非 Hero 模块但 Hero 流程会复用):
| 错误码 | 含义 |
|---|---|
HERO_ARG_ERROR |
参数错误(在 SummonHero 中作为 ErrorCode 引用,源码未列出具体数值,按通用 5001 范围) |
CFG_ERROR |
配置缺失 |
ITEM_NOT_EXIST |
道具不存在 |
ITEM_NOT_ENOUGH |
道具数量不足 |
ROLE_DENAR_NOT_ENOUGH |
钻石不足 |
7. UI 流程
ROK 客户端 PureMVC 架构,每个 Mediator 对应一个 Panel;按业务流程列出。
7.1 入口:CaptainMediator(统帅总览)
Panel:UI.s_captain(CaptainMediator + CaptainView)
注册 Notification:
CmdConstant.GetNewHero- 拿到新英雄Hero_HeroStarUp.TagName- 升星响应CmdConstant.UpdateHero- 英雄数据更新Hero_HeroSkillLevelUp.TagName- 技能升级响应CmdConstant.ItemInfoChange- 道具变化(用于刷新升技能道具显示)CmdConstant.HeroListVisible- 显示/隐藏列表CmdConstant.RefreshEquipRedPoint- 装备红点CmdConstant.HeroSceneVisible- 3D 场景显示
关键流程:
- 进入面板 →
InitData加载 6 个 prefab:UI_LC_Captain、UI_Item_CaptainHead、UI_Item_CaptainSummon、UI_Item_CaptainPartline、UI_Item_CaptainSkill、UI_LC_CaptainUnSum - 从
PlayerPrefs("Hero_SortType_{rid}", default Rare)读上次排序方式 HeroProxy.GetHerosBySort→ 三段分组(已拥有 / 可召唤 / 未集齐)- 列表用
ListView渲染:UI_LC_Captain行:每行 2 个已拥有/可召唤英雄UI_Item_CaptainPartline分隔行UI_LC_CaptainUnSum行:每行 2 个未集齐英雄
- 选中某英雄后:
- 加载
m_hero.config.heroModelSpine 动画 - 加载
m_hero.config.heroScene场景 prefab - 播放
voiceSelect语音
- 加载
右侧详情面板切换(view.m_UI_Item_CaptainData 总览态):
- 点击
m_btn_starUp_GameButton→ShowStarUp→ 检查level >= starLimit→ 打开升星面板 - 点击
StandardButton_skillup→ShowSkillUp→ 打开技能升级面板 - 点击
StandardButton_gift→ShowTalent→ 打开天赋树面板 - 点击
m_btn_changeEquip_GameButton→ 切换到装备界面(条件:英雄已拥有 + 城堡等级 ≥s_SystemOpen[11000].openLv)
7.2 招募流程
入口:
- 玩家在主界面/任务引导/酒馆 → 跳转
UI.s_captain - 选中"未集齐"英雄 → 显示碎片进度,碎片不够时灰显
- 选中"可召唤"英雄 → 显示"招募"按钮
- 点击招募 →
HeroProxy.SummonHero(id)→ 发Hero_SummonHero协议 - 服务端推
Hero_HeroInfo(首个英雄会推noShow=true屏蔽展示) HeroProxy.UpdateHeroInfo收到后:若是新英雄(bNewHero=true && !firstGetData && !noShow)→ 发CmdConstant.GetNewHero通知HeroCmd.Execute处理GetNewHero:- 关闭
UI.s_gameTool - 若该英雄是文明初始英雄(
heroId == civilization.initialHero)→ 触发首次获得引导 - 否则打开
UI.s_captainSummon展示新英雄(3D 镜头 + 名字/称号/品质/3 个天赋图标 + 文明标识)
- 关闭
- 关闭召唤展示后回到主流程
7.3 经验升级流程
Panel:UI_Win_CaptainLevelUp (UI_Win_CaptainLevelUpMediator)
- 在详情面板 → 经验条上点 "+" → 打开经验升级窗
- 列出背包中所有
subType == 经验书的道具 - 玩家选数量 + 确认 → 发
Hero_AddHeroExp(heroId, itemId, itemNum) - 服务端响应后
HeroProxy.UpdateHeroInfo推送,UI 自动刷新经验/等级
7.4 升星流程
Panel:UI_Item_CaptainStarUp(嵌入 CaptainView 内)
入口:m_btn_starUp_GameButton
- 校验
level >= sHeroStar.starLimit(不够提示Tip文本 166056,说明"先升到 X 级才能升星") - 显示当前 star/starExp 进度条 + 可选材料列表(来自
s_HeroStarExp按rareGroup == hero.rare过滤) - 玩家选若干个(≤6) → "升星"按钮 → 发
Hero_HeroStarUp(heroId, items) - 收到
Hero_HeroStarUp.response.result == true后调m_UI_Item_CaptainStarUp.OnAddStarExpSuccess()播放表现 - 若产生升星:会单独弹
UI_IF_CaptainStarUpSuccess升星成功界面
7.5 技能升级流程
Panel:UI_Item_CaptainSkillUp(嵌入 CaptainView 内)
入口:m_UI_Item_CaptainData.AddSkillUpButtonClickedEvent → ShowSkillUp
- 显示该英雄的 5 个技能 + 当前等级
- 显示升级所需碎片数 (
HeroProxy.Hero.GetSkillLevelUpCostItemNum) + 背包余量 - 点击"升级技能"按钮 → 发
Hero_HeroSkillLevelUp(heroId) - 服务端响应
{skillId, skillLevel}→HeroSkillLevelUpSuceess高亮"被升级"的那个技能(玩家不知道会升哪个!) - 若升级后达到觉醒条件:服务端自动触发
heroAwake,再推一次Hero_HeroInfo - 升级成功也会弹
UI_IF_CaptainSkillUpSuccess显示效果
7.6 天赋树流程
Panel:UI_Item_CaptainTalent(嵌入 CaptainView 内)+ 子 SubView UI_Item_CaptainTalent_SubView
入口:m_UI_Item_CaptainData.AddTalentButtonClickedEvent → ShowTalent
- 显示 3 个 tab(对应 talentIndex 1/2/3,可改名
UI_Win_TalentChangeName) - 当前 tab 显示一棵天赋树(3 棵 gainTree 横排,每棵有 N 层节点)
- 显示剩余天赋点 (
HeroProxy.Hero.GetCurPageRemainPoint) - 点击节点 → 校验 → 发
Hero_TalentUp(heroId, id, index) - 切换 tab → 弹
UI_Win_TalentChangeAlert询问 → 确认后发Hero_ChangeTalentIndex(默认用道具,可选钻石) - 重置当前 tab → 同样的 Alert 流程 → 发
Hero_ResetTalent
7.7 装备穿戴流程
Panel:UI_Item_CaptainEquip(嵌入 CaptainView 内)+ UI_Item_CaptainEquipUse + UI_Item_CaptainEquipList
入口:m_btn_changeEquip_GameButton(条件:英雄已拥有 + 城堡等级 ≥ 11000.openLv)
- 8 个槽位(按 §4.7.1 顺序排列),已穿戴的显示道具图标,空位显示槽位占位图
- 点击某槽位 → 弹"选择装备"列表,从背包筛
subType == 槽位 subType的道具 - 选中道具 → 发
Hero_HeroWearEquip(heroId, itemIndex, equipIndex) - 长按已穿戴的 → 弹"卸下" → 发
Hero_TakeOffEquip(heroId, equipIndex)
7.8 雕像兑换流程
Panel:UI_Win_CaptainItemSource(兑换 + 道具来源面板)
- 玩家进入英雄详情,看到该英雄的招募碎片 → 点击 "+" → 显示获取来源
- 若
sHero.exchange > 0(可雕像兑换)→ 显示"用通用雕像兑换"入口 - 玩家选数量 → 发
Hero_ExchangeHeroItem(heroId, itemNum) - 服务端 1:1 转换 → 同步背包道具
7.9 酒馆开宝箱(碎片来源)
Panel:UI.s_tavernSummon(TavernSummonMediator + TavernSummonView)
两种宝箱:
- 白银宝箱(type=1):CD 时间到给免费次数,或扣
silverBoxOpenItem×silverBoxOpenItemNum数量;不足时可用钻石(shopPrice) - 黄金宝箱(type=2):同理,CD 长(
s_BuildingTavern.goldBoxCD)
操作:
- 免费开 / 单开 / 全开(≥10 个时显示)
- 协议:
Build_Tavern(type, free, count, useDenar) - 响应:
RewardInfo { items, heros, ... }+count+type
结果展示:
- 单开(count < 10):4 格抽奖动画,热英雄碎片插入随机位置
- 多开(count ≥ 10):跳转
UI.s_tavernReward滚动展示完整列表
酒馆只产出"碎片+道具",不直接给英雄实体! 英雄实体一律通过
Hero_SummonHero流程。
8. 与 Survivors 实现的对照点
8.1 架构对照
| 维度 | ROK | Survivors(迁移后) |
|---|---|---|
| 客户端框架 | PureMVC + ILRuntime/IFix | ET + YIUI + HybridCLR |
| 协议 | sproto | protobuf3 |
| 服务端 | C++ + Lua(snax proxy) | ET Server(Scene-Entity 模型) |
| 配置 | Lua tabtoy → Configs.data |
Luban → JSON + 生成 C# |
| 持久化 | 自研 MSM.d_hero[rid] 内存 + 落库 |
MongoDB(cn.etetet.db 封装) |
8.2 PureMVC Proxy → ET Component 映射
| ROK | Survivors |
|---|---|
HeroProxy (PureMVC) |
HeroComponent 挂在 Player Entity 上 |
HeroProxy.Hero 包装类 |
Hero 子 Entity(继承 ET Entity) |
HeroProxy.UpdateHeroInfo(notification) |
Hero_HeroInfoHandler 服务端推送消息处理器 |
HeroProxy.AnalysisHero 初始化 |
HeroComponent.Awake 时根据所有 HeroConfig 创建占位 |
HeroProxy.GetHerosBySort |
HeroComponent.GetHerosBySort(保留 4 种排序) |
HeroCmd Notification 分发 |
ET 事件系统 EventSystem.Publish<HeroLevelUpEvent> |
8.3 sproto → protobuf 字段映射
| sproto type | protobuf 等价 |
|---|---|
integer |
int64(默认)或 int32(明确小字段) |
boolean |
bool |
string |
string |
*Type (list) |
repeated Type |
*Type(key) (map by key) |
map<int64, Type>,注意:sproto 用对象字段当 key,protobuf map 是显式 KV |
重要差异:
- sproto
*HeroInfo(heroId)是"以 heroId 字段聚类的数组" → protobuf 应映射为map<int64, HeroInfo>- sproto
*TalentTrees(index)→map<int32, TalentTrees>- sproto 的 tag 不需要在 protobuf 中保留,按字段顺序新分配即可,但建议保留语义化分组
8.4 配置表对照
| ROK Define | Survivors Luban Bean | 备注 |
|---|---|---|
HeroDefine |
HeroConfig |
直接对齐,getItem/getItemNum 保留 |
HeroLevelDefine |
HeroLevelConfig |
主键 (rareGroup, lv) 双键 |
HeroStarDefine |
HeroStarConfig |
rare1-5 字段保留,或重构成 map<rareGroup, int> reqExp |
HeroStarExpDefine |
HeroStarExpConfig |
1:1 |
HeroSkillDefine |
HeroSkillConfig |
1:1 |
HeroSkillEffectDefine |
HeroSkillEffectConfig |
主键 skillId * 1000 + level 公式保留 |
HeroSkillLevelDefine |
HeroSkillLevelConfig |
1:1 |
HeroTalentDefine |
HeroTalentConfig |
gainTree 由 string 改 int(Luban 类型严格) |
HeroTalentGainTreeDefine |
HeroTalentNodeConfig |
建议改名避免单词冗长 |
HeroTalentMasteryDefine |
HeroTalentMasteryConfig |
1:1 |
8.5 RPC 命名映射
| ROK sproto | Survivors protobuf | ResponseType |
|---|---|---|
Hero_SummonHero |
C2G_SummonHero |
G2C_SummonHero |
Hero_AddHeroExp |
C2G_HeroAddExp |
G2C_HeroAddExp |
Hero_HeroSkillLevelUp |
C2G_HeroSkillUp |
G2C_HeroSkillUp |
Hero_HeroAwake |
C2G_HeroAwake |
G2C_HeroAwake |
Hero_ExchangeHeroItem |
C2G_ExchangeHeroItem |
G2C_ExchangeHeroItem |
Hero_HeroStarUp |
C2G_HeroStarUp |
G2C_HeroStarUp |
Hero_TalentUp |
C2G_TalentUp |
G2C_TalentUp |
Hero_ChangeTalentIndex |
C2G_ChangeTalentIndex |
G2C_ChangeTalentIndex |
Hero_ResetTalent |
C2G_ResetTalent |
G2C_ResetTalent |
Hero_ModifyTalentName |
C2G_ModifyTalentName |
G2C_ModifyTalentName |
Hero_HeroWearEquip |
C2G_HeroWearEquip |
G2C_HeroWearEquip |
Hero_TakeOffEquip |
C2G_TakeOffEquip |
G2C_TakeOffEquip |
Hero_HeroInfo(推送) |
G2C_HeroInfo(推送) |
- |
8.6 数据存储映射
| ROK | Survivors |
|---|---|
MSM.d_hero[rid] |
MongoDB collection Hero(按 playerId + heroId 复合主键) |
req.Get / req.Set / req.Add |
DBComponent.Query<Hero> / .Save<Hero> |
syncHero 推送 |
MessageHelper.SendToClient(playerId, new G2C_HeroInfo {...}) |
cacleSyncHistoryPower |
PlayerComponent.RecalcPower() + 推送 |
9. 实现优先级建议
按 ROK 业务复杂度 + Survivors 现有能力评估,建议拆 4 期实现。
P1 - 必做基础(招募 + 升级)⭐⭐⭐
目标:跑通"获得碎片 → 招募 → 升级到等级上限"完整链路。
HeroConfig+HeroLevelConfig+HeroStarConfig三张表 Luban 化HeroEntity 设计(仅持久化heroId/star/starExp/level/exp/summonTime/skills)HeroComponent挂在 Player Entity 上- RPC:
C2G_SummonHero+C2G_HeroAddExp - 服务端业务:
SummonHero校验链 +addHeroExp循环升级 + 等级封顶 - 推送:
G2C_HeroInfo - GM 命令:
gm.hero.add(heroId)/gm.hero.addExp(heroId, amount)/gm.hero.list - 战力计算:
base + level(先简化,跳过技能/天赋/装备)
P2 - 战斗相关(升星 + 技能 + 装备)⭐⭐
目标:英雄养成的核心数值闭环。
HeroStarExpConfig+HeroSkillConfig+HeroSkillEffectConfig+HeroSkillLevelConfig- RPC:
C2G_HeroStarUp+C2G_HeroSkillUp+C2G_HeroAwake - 升星:6 个材料 + 幸运双倍机制(
lucky >= Random(1,100)) - 升星:解锁高阶技能(
unlockSkill) - 技能升级:服务端随机选目标技能 + 5 级满 + 触发觉醒
- 觉醒:第 5 技能 ID =
heroId * 100 + 5 - 8 槽位装备:
EquipConfig+EquipAttConfig+EquipComposeConfig - RPC:
C2G_HeroWearEquip+C2G_TakeOffEquip - 装备切换英雄时自动从旧英雄卸下
- 套装属性(2/4/6/8 件)
- 战力计算:加
skill分
P3 - 天赋系统 ⭐⭐
目标:高阶差异化养成。
HeroTalentConfig+HeroTalentGainTreeConfig+HeroTalentMasteryConfigs_ZeroEmpty.TALENT / HERO_LEVEL_TALENT / HERO_STAR_TALENT三张全局表(Survivors 可改为HeroTalentGlobalConfig单表)- RPC:
C2G_TalentUp+C2G_ChangeTalentIndex+C2G_ResetTalent - 学习单节点:5 个校验链(点足 / 能学 / 前置 / 同级互斥)
- 切换页:当前页切换才收费
- 重置页:仅当前页收费
- 战力计算:加
talent节点分 + mastery 分
P4 - UI 一次性补齐 + 次要功能 ⭐
目标:把 P1-P3 服务端业务用 YIUI 包成可玩界面,集中做不返工。
HeroListPanel:排序(4 种)+ 三段分组 + 选中详情HeroDetailPanel:信息展示 + 升级/升星/技能/天赋/装备入口HeroSummonResultPanel:获得新英雄 3D 展示HeroStarUpPanel:材料选择 + 幸运进度HeroSkillUpPanel:5 个技能 + 升级按钮(随机机制要 tooltip 说明)HeroTalentPanel:3 个 tab + 天赋树 + 切换/重置/改名HeroEquipPanel:8 个槽位 + 选装备子面板- 可推迟:雕像兑换 (
C2G_ExchangeHeroItem) / 天赋页改名 (C2G_ModifyTalentName) / 红点统计
可推迟(不阻塞)
- 副将系统:ROK 中副将影响军队属性(不享受天赋加成),Survivors 单机肉鸽不需要"军队",可砍掉
- 行动力减免:与 Survivors 战斗模式不兼容
- 限时礼包触发:与付费系统耦合
- 怪物英雄(
s_MonsterTroops):仅用于 SLG 野怪,Survivors 不需要 - 部队属性同步到战斗服务器:Survivors 是单机肉鸽,没有战斗服务器
- 城防主将自动调整(
BuildingLogic:changeDefendHero):与城建系统耦合
附录 A:实际数据样本
HeroDefine 样本(hero.json ID=1000):
{
"ID": 1000, "civilization": 101, "initStar": 1, "star": 6, "rare": 5,
"score": 1100, "getItem": 509010001, "getItemNum": 10,
"exchange": 503050004, "getLimit": 0,
"skill": [100001, 100002, 100003, 100004, 100005],
"talent": [100001, 100002, 100003],
"heroIcon": "hero_head[hero_head_1000]", "heroModel": "Hero_1000_SD"
}
HeroStarDefine 样本(hero_star.json 5★):
{
"ID": 5, "starLimit": 50,
"rare5": 65000, "rare4": 48000, "rare3": 30000, "rare2": 20000, "rare1": 10000,
"starEffect": 2, "starEffectData": 1
}
HeroSkillLevelDefine 样本(hero_skill_level.json 第 6 次升级):
{
"ID": 105, "level": 6,
"costItem5": 30, "costItem4": 20, "costItem3": 20, "costItem2": 10, "costItem1": 10
}
HeroStarExpDefine 样本(hero_star_exp.json 2 星材料):
{
"ID": 1001, "rareGroup": 2, "itemID": 503010002,
"exp": 400, "lucky": 20, "sure": 0
}
附录 B:调研中发现的关键设计/坑
talentPoint字段已废弃——HeroDef.lua仍保留但HeroLogic.lua中所有相关代码已注释掉;实际天赋点通过checkTalentPoint实时计算。重写时不要持久化这个字段,避免逻辑混乱。- 技能升级随机性——服务端不允许玩家选升哪个技能(
Random.GetId(skillIds)等权重)。前端 UI 需要清楚地告知玩家"随机升级一个未满技能"。 - 同稀有度共享 cost——
HeroSkillLevelDefine用costItem1-5区分稀有度的消耗量,5 星比 1 星贵 4-8 倍。 - 升星与等级互锁——必须先升满当前星级的等级上限才能升星,升星后等级上限提升。
- 碎片是通用货币——招募 + 升技能 + 雕像兑换都用同一个
sHero.getItem道具,玩家不会有"碎片够多但不知道干嘛"的窘境。 - 战力不含装备——
HeroCacle:caclePower不算装备分;装备只影响战斗时实际属性(getHeroAttr)。 - 副将不享受天赋加成——
getHeroAttr对副将传notCancleTalent=true。 - 觉醒技能 ID 公式固定——
heroId * 100 + 5;前 4 技能 ID 通常为heroId * 100 + 1..4,但实际由s_Hero.skill数组定义,不要假设。 skillEffect主键公式——skillId * 1000 + level;查询不到表示已满级。hero_level主键有 -1 偏移——客户端HeroProxy.cs:184用(int)level + config.rare * 10000 - 1,服务端 Lua 直接用rare * 10000 + level;两者实际相差 1,怀疑客户端有 bug 或者表 ID 设计就是从 0 开始。重写时应统一使用一种公式,建议沿用服务端逻辑 (rare * 10000 + level)。- 3 套天赋页共享同一池天赋点——而非"每页一个独立池"。重置或切换页只是清空已学列表,可用点数总数不变。
- 第 5 技能不通过
unlockSkill——HeroLogic.lua:394明确判断_star ~= 5,第 5 技能(觉醒)由heroAwake单独负责。 Build_Tavern是英雄碎片主入口——但 ROK 工程同时还有Shop_GetLimitHeroInfo(限时商店)和活动奖励等其他来源;碎片本质就是普通道具。
附录 C:未在 ROK 中找到 / 不存在的内容
- 抽卡协议:ROK 完全没有"抽英雄实体"的协议。
Build_Tavern是抽宝箱(产出碎片+道具)。 - 保底机制:宝箱奖励是配置表
s_TavernRank按权重随机,无显式保底。 - 英雄拥有数量上限:源码无任何限制。
HeroTalentGainTreeConfig/HeroTalentConfig/HeroTalentMasteryConfig的 JSON 数据:gmd\Unity\Assets\Bundles\Config\下不存在这 3 张表的 json(只有 hero/hero_level/hero_skill 等基础表)。ROK 配置只在Configs.data加密 SQLite + 服务端 Lua 中,需要从那里导出。- 升星动画/特效配置:未在 hero 表中找到,可能在通用
s_Effect表里。