前言
取这个很大的标题只是为了给自己做的事加那么一点点严肃性,本质上也是一种形式的标题党,各位看官嘴下留情
背景
DNF做为一个网游,有着各有特色的职业,多样繁杂的技能,品类繁多的装备。我自从去年回归110版本的时候,就一直不断地在学习相关的知识,希望在自己的游戏历程中,能够找到一个相对客观的方式来给自己选择某一职业下的最优装备和技能搭配。
痛点
相比于我双修的另一个游戏wow,dnf好像的确缺少一个能够模拟实战的一个程序/脚本/软件,用来评估各种技能、装备搭配下,在不同时间轴上的伤害表现。目前唯一可用的,是DNF计算器,但是DNF计算器因为一些历史原因,删除了在DNF输出最重要的部分,自动模拟技能队列(技能数)这样一个功能,导致使用者更改装备搭配,尤其是cdr相关装备时,使用非常不方便,必须得依赖实际打桩获得技能次数,或者就干脆直接按照cd伤害比来做粗略的折算。前者在候选项比较少的时候的确可是实际手打来获得技能数,但是在候选项比较多(不同时间轴,不同符文搭配,不同cdr搭配,不同技能护石搭配),手打的成本就变得不可接受了。
正文
接下来我将介绍我这一段时间用闲暇之余(除工作和dnf之外)做的一些工作,希望能起到抛砖引玉的效果。
另外,这边贴一下我近期在C站看到的非常好,用来论述技能cd、时间轴和伤害之间关系的帖子,大家可以先阅读一下,有助于理解我后面做的事
礁石大佬的数据贴,我把这个帖子奉为圣经不为过吧
https://bbs.colg.cn/thread-8740776-1-1.html
劲足轮椅大佬,第一个考虑cd信息,也就是实际技能数的数据贴,正式由于发现他太依赖于人手打数据导致难以快速复制和扩展结论,从而催生了本帖的工作
https://bbs.colg.cn/thread-8756827-1-1.html
https://bbs.colg.cn/thread-8920282-1-1.html
夜空下的艾莉欧大佬的关于cd的帖字,篇篇都值得好好读一读
https://bbs.colg.cn/thread-8923073-1-1.html
https://bbs.colg.cn/thread-8915594-1-1.html
https://bbs.colg.cn/thread-8737718-1-1.html
目标
解决上述中的痛点问题,我希望能够通过一个模拟程序,来达到以下几个目标
1. 在固定的时间轴、固定装备搭配、固定护石搭配(技能组,例如大冰大火流强还是不动强)、固定符文搭配下,找到一个最优技能序列,使得伤害值达到最大
2. 想对比下不同时间后、装备搭配、不同护石搭配、不同符文搭配中,伤害最高的是哪个?
3. 由1和2的数据下,做一些其他分析
方法
1. 要想模拟dnf的技能队列,我想到的方法非常粗暴,设定一个时间长度,例如我们最常用的40s打桩时间,然后随机选择技能,并考虑等效cd信息,给予每个技能使用次数限制,通过大量的上述过程,我们可以得到一个逼近非常接近真实可用的技能队列,然后计算这个队列中技能的实际伤害值(依赖dnf计算器),最后我们只需要挑选出伤害值最高的那组序列,即为某一个限定条件下的最优技能队列,当然在实际模拟的时候依旧会有问题,不同技能的释放时间,进入cd的时间是不同的,柔化时间也不同,所以我基于我自己瞎子的修炼场速度模板,录屏以数帧数的方式获取了瞎子大部分常用技能的cast_time, during, force_time信息
这里一一解释下上述概念
cast_time:不是游戏中的读条时间,而是释放技能的那一刻,到该技能实际进入cd的时间,由于我是手搓玩家,所以我就以手搓光标出现的时间为开始时间,技能开始进入cd的时间为结束,来计算cast_time,以这个邪光为例,他的cast_time 大概 139ms
而大冰是起手即cd,所以他的cast_time = 0
during:是指技能进入cd后,到其他技能可用的时间
force_time:是指可以柔化的技能在进入cd后,到柔化技能可用的时间,例如小冰揉邪光、炸热揉无双揉不动
整个模拟思路用伪代码描述为:
人类操作延迟 = 0.1s (专业运动员的极限反应速度也到不了100ms以下,这里考虑到并不是所有技能都是极限反应,而是可以预按键,而人的手速的极限在50-80ms,所以这里取了100ms)
total_time = 40s
time_line = 0s # 初始化时间轴
while True:
1. random_choose_skill # 随机选择技能
2. compute_force_time_reduce # 判断该技能是否为上一个技能的柔化技能,如果是则需要在整个时间轴减去cast_time + during - force_time
3. action_skill # 模拟执行技能,并记录该技能的past_time = cast_time + during + 人类操作延迟
4. judge_is_end # 判断执行该技能后,在原始的时间轴上加上past_time后是否会超过time_line - bias(bias用来控制塞蛋问题,bias越大,越不能容忍塞蛋),如果超过,则跳出模拟循环
5. update(time_line, past_time) # 更新时间轴time_line
6. start_cooling_down(skill, past_time) # 设置该技能开始cd, 并广播所有技能同步减少past_time
然后让上述循环跑万次,甚至10w次,就可以获得一个可信程度比较高的输出序列了。人打一次要40s,而在我的电脑上,跑30w次也只需要40s,模拟效率高下立判。
2. 对于目标2,想要进行不同配装搭配、护石搭配、技能搭配、符文搭配、时间轴下的对比,那只需要写一个循环,把各种情况都遍历一下即可,当然如果想要考虑的因素特别多的话,则需要跑很久很久
例如下面的模拟采用30w次迭代(每次迭代40s左右)寻找最优技能队列,并在此基础上把各个因素考虑后,循环跑了864次,总计10小时,跑出结果的配装、符文、护石,在40s下的最优解。当然有人肯定会问,这个模拟也太蠢了,10小时才对比4套配装,但是考虑手搓、符文组、护石组等信息后总共864种组合,手打,弄死我吧。。。
详细的模拟参数如下:
1. 配装总共对比了4套,他们的cdr信息和伤害倍率分别如下,
说明: cdrr是指技能恢复速度
{"damage_info": {"global": 1},"cdr_info": {"common_cdr": [0.95],"common_cdrr": [], "lingtong_info": {}}}
{"damage_info": {"global": 0.8953},"cdr_info": {"common_cdr": [0.95],"common_cdrr": [0.25], "lingtong_info": {"40": 0.15,"75": 0.15,"95": 0.2}}}
{"damage_info": {"global": 0.8645},"cdr_info": {"common_cdr": [0.95],"common_cdrr": [0.3], "lingtong_info": {"40": 0.15,"75": 0.15,"95": 0.2}}}
{"damage_info": {"global": 0.8567},"cdr_info": {"common_cdr": [0.95],"common_cdrr": [0.42], "lingtong_info": {"40": 0.15,"75": 0.15,"95": 0.2}}}
2. 护石组:["炸热", "呀呀呀", "不动", "雷云"]
3. 符文组:基于护石组筛选3个组合后的结果,并排列红、蓝、紫后的所有组合
4. 考虑是否手搓
最终在40s下:
伤害最高的装备组合: {"damage_info": {"global": 0.8567}, "cdr_info": {"common_cdr": [0.95], "common_cdrr": [0.42], "lingtong_info": {"40": 0.15, "75": 0.15, "95": 0.2}}}
伤害最高的搭配,护石组合: ["炸热", "呀呀呀", "不动"]
伤害最高的搭配,符文组合: {"red": {"呀呀呀": 3}, "blue": {"炸热": 3}, "purple": {"不动": 3}}
伤害最高搭配,是否手搓:True
伤害最高的搭配的技能序列: ["炸热", "小冰", "邪光", "无为法", "小火", "无双", "不动", "雷云", "呀呀呀", "波爆", "小冰", "邪光", "炸热", "3觉", "波爆", "小火", "炸热", "邪光", "小冰", "2觉", "无双", "波爆", "小冰", "炸热", "邪光", "波爆", "小火", "小冰", "波爆", "邪光", "小冰", "炸热", "呀呀呀", "无双", "不动", "雷云", "波爆", "小火", "无为法", "小冰", "邪光"]
伤害最高的搭配的技能伤害: {"炸热": {"times": 5, "damage": 1291283374.38698}, "小冰": {"times": 7, "damage": 667800295.4896}, "邪光": {"times": 6, "damage": 1014279114.0378001}, "无为法": {"times": 2, "damage": 1357396832.909}, "小火": {"times": 4, "damage": 707019682.2364}, "无双": {"times": 3, "damage": 597511492.4298}, "不动": {"times": 2, "damage": 1393457887.2579236}, "雷云": {"times": 2, "damage": 1316010140.8012}, "呀呀呀": {"times": 2, "damage": 1310786026.5449784}, "波爆": {"times": 6, "damage": 723669811.2228}, "3觉": {"times": 1, "damage": 1709761787.0008001}, "2觉": {"times": 1, "damage": 925417426.7858}}
伤害最高的搭配的技能伤害(总): 13014393871.10308
这里的这个技能数和我自己真实打桩是一致的,但打桩序列不同
正文的最后,还是放上代码,希望有能力的小伙伴,可以一起来共建这个代码,
ricklovelisa/dnf_skill_sim (github.com)
Todo
1. 优化模拟技能序列部分的算法,同时接受各位阿修罗大佬的监督,看看模拟的技能数是否正确
2. 系统性的尝试模拟阿修罗在各个时间轴上,各个cdr配装,各个符文下的伤害趋势、技能分布
3. 日常配装对比
4. 其他职业信息的添加,机制开发,考虑基于时间轴的伤害倍率开发(太阳+机制)etc.
后记
最后说一点关于cdr和伤害的理解,首先我先阐述我的一个观点,即没有所谓的续航和爆发,在dnf的伤害环境下,只有时间和技能次数这两个因素,很多帖子讨论的所谓的爆发和续航,概念都没对齐,能讨论出个啥?这种连概念都没对齐的标签,除了适合打碟,没有任何积极的意义。另外原来我是一个秒伤党,但是随着打团次数的增多,尤其是困难开放后,太阳+机制都打不全的情况越来越多,我不得不重新思考一个问题,秒伤意义大吗?其实做这个模拟就已经说明,我对秒伤的理解了,即只有当你的cdr能够让你的技能次数在某个时间段内+1的时候,这个cdr对于这个时间段才是有意义的,而dnf中至少在打团攻略中,哪有真的能满打满算40s的时候呢?更不要说60s,100s了都很少见吧?20-30s机制+太阳成为我日常攻略的常态,是该放弃40s打桩的评价体系了,至少我现在已经不会看40s的伤害分布了,本次模拟为了举个例子,所以还是采用了大家所熟悉的40s体系