一、绪论
[collapse=1.1 防骑生存端的研究背景与研究现状]
长久以来,骑士区一直有“T6胸甲好还是牌子胸甲好”“宝石插耐力还是闪避好”等争论,但由于既缺乏数值计算,又没有具体标准,一直不能得出结论。
之前已经有不少研究做了防骑生存端的属性收益分析乃至装备评分。哈哈企鹅在[1]中正式引入了有效生命值的概念;[2]则以属性占用的物品等级对生存属性进行评分。
但是依然存在着不少问题,例如有效生命值是一个长时间内的期望值,而倒坦往往是短短几秒内发生的事情,方差较大;同时,有效生命值的计算没有考虑BOSS本身的肉搏数据这一参数,也导致了格挡值收益的计算错误。
而[3]与[4]讨论了TBC的物品等级分配,指出单纯按照占用物品等级作为标准进行评分,脱离了实战范畴。同时,其依据的原理也存在问题,缺失了宝石插槽占用的装等计算。
故本文试图以在断奶的条件下,从满血到暴毙的死亡概率为标准,来量化防骑生存端的各属性收益。
[/collapse]
二、原理概述
[collapse=2.1 量化标准与属性分类]
考虑[6]中的治疗手法,我们认为,常规语境下的倒坦现象发生,需要具备两个条件,一是只有少数治疗在看坦克,二是BOSS具备把坦克在极短时间内从满血打到暴毙的能力。
任意一者不满足而出现倒坦,则认为本次倒坦为治疗失职所致,与坦克配装无关。
通常认为,生存端属性收益影响两个方面:在一段较长连续时间内,影响治疗蓝耗;在一段较短时间内,影响坦克死亡概率。
减少治疗蓝耗的收益,其收益曲线可以看做简单的一次函数,且与坦克本身无关,不予论述。
故本文对生存端属性的量化标准为:在断奶的条件下,坦克在极短时间内被连续肉搏命中,从满血到暴毙的死亡概率。
进而可以把生存端属性分为两类:一类是血甲格挡值,决定了坦克最多能够承受几次BOSS的肉搏;
二类是招闪防等,决定了坦克短时间内连续被肉搏命中的概率。
[/collapse]
[collapse=2.2 计算公式]
坦克连续承受x次肉搏后死亡的概率计算公式如下:
N=⌈pal_hp/(damage*(11960.0/(11960.0+armor))*0.94-block_value) ⌉
damage=random(damage_min, damage_max)
P1=P(N<x)
其中,N为坦克能够承受的肉搏次数,pal_hp为面板血量,damage为原始肉搏伤害,在damage_min与damage_max间随机浮动,armor为面板护甲值,block_value为面板格挡值,x为承受的肉搏次数。
考虑到在极短时间内倒坦的前提条件,我们选择计算x=2或x=3,即承受2或3次肉搏后的死亡概率作为标准。
坦克连续被x次肉搏命中的概率计算公式如下:
P2=(1-injury_free)^x
其中,P2为连续承受x次肉搏的概率,injury_free为坦克面板硬免总和,x为肉搏次数。
则最终存活概率P=1-P1*P2。
[/collapse]
[collapse=2.3 收益曲线分析与评分标准]
通过P1分别对血量、护甲、格挡值求导,可以得到结论:血量、护甲、格挡值不仅为3种互相影响收益的属性,其收益与BOSS的肉搏数据也有关系。
显然,血甲格挡值的收益曲线存在阈值,当三类属性收益总和无限接近能承受BOSS的第x+1次肉搏时,在阈值点上会出现收益飞跃,如图1所示。
而对P2求导可得,硬免属性的收益递减,如图2所示。
对于如此复杂的收益曲线,我们无法通过简单的肉眼鉴定法来比较属性收益。
故本文选择用代码来计算各属性的边际收益,而各生存属性的收益评分标准则为:1分=最终生存概率提高0.01%。
图1:血甲格挡值收益曲线
图2:硬免收益曲线
[/collapse]
三、应用设计与实现
[collapse=3.1 前提条件]
本程序默认天赋点满5点炽热防御者,不点盾牌专精,点满坚韧这一护甲加成天赋,点满神圣使命与战斗精准这二种耐力加成天赋。
物品等级则按照[4]计算:
1耐力占用2/3物品等级,耐力加成为天赋的1.16倍乘算王者的1.1倍,共计1.276倍;
1护甲占用0.1物品等级,护甲加成为天赋的1.2倍;
1格挡值占用0.65物品等级;
1躲闪等级占用1物品等级。
从2.3可知,属性收益与BOSS的肉搏数据挂钩,因此提供不同的高压BOSS模型供选择。
BOSS的肉搏数据按照[5]的原始数据,乘算0.858的挫志系数。
魔法技能的原始伤害数值则根据WCL观察得到。
想到了,但是暂时没有写入模拟器的功能:
1、魔法伤害的部分抗性减免,参考[7]。
2、根据战斗时长和模拟结果来计算一次N分钟战斗中的坦克暴毙概率。[del]有请概率论选手入场[/del]
[/collapse]
[collapse=3.2 代码实现]
[code=python]
import tkinter as tk
import tkinter.messagebox
import random
def clear_leading_zero(s):
s1 = ''
discriminator = False
for i in range(len(s)):
if (s != '0') and (discriminator is False):
discriminator = True
if discriminator is True:
s1 = s1+s
return s1
def cal(hp, armor, block_value, injury_free, model):
model_num = int(model[0])
damage_min = [[20304, 20304, 20304], [20304, 20304, 20304], [19913, 9956, 19913]]
damage_max = [[28709, 28709, 28709], [28709, 28709, 28709], [28156, 14078, 28156]]
damage_magic = [0, 2350, 0]
run_time = 1000000 + 1
str_print = ''
count_death_proto = 0
count_death_hp = 0
count_death_armor = 0
count_death_block_value = 0
for i in range(1, run_time):
hp_proto = hp - damage_magic[model_num]
hp_hp = hp_proto + 191.4
hp_armor = hp_proto
hp_block_value = hp_proto
for j in range(0, 3):
damage = random.randint(damage_min[model_num][j], damage_max[model_num][j])
damage_proto = damage * (11960.0 / (11960.0 + armor)) * 0.94 - block_value
if hp_proto <= hp * 0.35:
hp_proto = hp_proto - damage_proto * 0.7
else:
hp_proto = hp_proto - damage_proto
damage_hp = damage_proto
if hp_hp <= (hp + 191.4) * 0.35:
hp_hp = hp_hp - damage_hp * 0.7
else:
hp_hp = hp_hp - damage_hp
damage_armor = damage * (11960.0 / (11960.0 + armor + 120)) * 0.94 - block_value
if hp_armor <= hp * 0.35:
hp_armor = hp_armor - damage_armor * 0.7
else:
hp_armor = hp_armor - damage_armor
damage_block_value = damage * (11960.0 / (11960.0 + armor)) * 0.94 - (block_value + 15.38461538)
if hp_block_value <= hp * 0.35:
hp_block_value = hp_block_value - damage_block_value * 0.7
else:
hp_block_value = hp_block_value - damage_block_value
if hp_proto <= 0:
count_death_proto = count_death_proto + 1
if hp_hp <= 0:
count_death_hp = count_death_hp + 1
if hp_armor <= 0:
count_death_armor = count_death_armor + 1
if hp_block_value <= 0:
count_death_block_value = count_death_block_value + 1
p_proto = (1 - (count_death_proto * 1.0 / (run_time - 1)) * (((100 - injury_free) / 100.0) ** 3)) * 100
str_print = str_print+'原始生存概率为'+str(p_proto)+'%\n'
p_injury_free = (1 - (count_death_proto * 1.0 / (run_time - 1)) * (
((100 - injury_free - 0.52910053) / 100.0) ** 3)) * 100
str_print = str_print+'增加10物品等级的躲闪等级后的生存概率为:\n'+str(p_injury_free)+'%\n'
p_hp = (1 - (count_death_hp * 1.0 / (run_time - 1)) * (((100 - injury_free) / 100.0) ** 3)) * 100
str_print = str_print+'增加10物品等级的耐力后的生存概率为:\n'+str(p_hp)+'%\n'
p_armor = (1 - (count_death_armor * 1.0 / (run_time - 1)) * (((100 - injury_free) / 100.0) ** 3)) * 100
str_print = str_print+'增加10物品等级的护甲后的生存概率为:\n'+str(p_armor)+'%\n'
p_block_value = (1 - (count_death_block_value * 1.0 / (run_time - 1)) * (((100 - injury_free) / 100.0) ** 3)) * 100
str_print = str_print+'增加10物品等级的格挡值后的生存概率为:\n'+str(p_block_value)+'%\n'
profit_injury_free = (p_injury_free - p_proto) / 10
profit_hp = (p_hp - p_proto) / 15.0
profit_armor = (p_armor - p_proto) / 120.0
profit_block_value = (p_block_value - p_proto) / 15.38461538
str_print = str_print+'此时的边际收益评分为:\n'
str_print = str_print+'1躲闪等级='+str(round(profit_injury_free * 100, 4))+'分\n'
str_print = str_print+'1耐力='+str(round(profit_hp * 100, 4))+'分\n'
str_print = str_print+'1护甲='+str(round(profit_armor * 100, 4))+'分\n'
str_print = str_print+'1格挡值='+str(round(profit_block_value * 100, 4))+'分\n'
if profit_injury_free != 0:
if profit_hp != 0:
str_print = str_print+'1躲闪等级='+str(round(profit_injury_free / profit_hp, 2))+'耐力\n'
if profit_armor != 0:
str_print = str_print+'1躲闪等级='+str(round(profit_injury_free / profit_armor, 1))+'护甲\n'
if profit_block_value != 0:
str_print = str_print+'1躲闪等级='+str(round(profit_injury_free / profit_block_value, 1))+'格挡值\n'
var_print.set(str_print)
def run():
str_hp = clear_leading_zero(var_hp.get())
for i in range(len(str_hp)):
if (str_hp < '0') or (str_hp > '9'):
tk.messagebox.showwarning(title='waring', message='请保证输入的血量为正整数且没有多余空格!')
return
if len(str_hp) != 5:
tk.messagebox.showwarning(title='waring', message='请保证输入的血量在1.5W-2.2W之间!')
return
hp = int(str_hp)
if (hp < 15000) or (hp > 22000):
tk.messagebox.showwarning(title='waring', message='请保证输入的血量在1.5W-2.2W之间!')
return
str_armor = clear_leading_zero(var_armor.get())
for i in range(len(str_armor)):
if (str_armor < '0') or (str_armor > '9'):
tk.messagebox.showwarning(title='waring', message='请保证输入的护甲为正整数且没有多余空格!')
return
if len(str_armor) != 5:
tk.messagebox.showwarning(title='waring', message='请保证输入的护甲在1.5W-3.5W之间!')
return
armor = int(str_armor)
if (armor < 15000) or (armor > 35000):
tk.messagebox.showwarning(title='waring', message='请保证输入的护甲在1.5W-3.5W之间!')
return
str_block_value = clear_leading_zero(var_block_value.get())
for i in range(len(str_block_value)):
if (str_block_value < '0') or (str_block_value > '9'):
tk.messagebox.showwarning(title='waring', message='请保证输入的格挡值为正整数且没有多余空格!')
return
if (len(str_block_value) <= 2) or (len(str_block_value) >= 5):
tk.messagebox.showwarning(title='waring', message='请保证输入的格挡值在200-1500之间!')
return
block_value = int(str_block_value)
if (block_value < 200) or (block_value > 1500):
tk.messagebox.showwarning(title='waring', message='请保证输入的格挡值在200-1500之间!')
return
str_injury_free = clear_leading_zero(var_injury_free.get())
count_point = 0
injury_free = 0
for i in range(len(str_injury_free)):
if ((str_injury_free < '0') or (str_injury_free > '9')) and (str_injury_free != '.'):
tk.messagebox.showwarning(title='waring', message='请保证输入的硬免值为正整数,或正整数带最多两位小数,且没有多余空格!')
return
if str_injury_free == '.':
count_point = count_point + 1
if count_point >= 2:
tk.messagebox.showwarning(title='waring', message='请保证输入的硬免值为正整数,或正整数带最多两位小数,且没有多余空格!')
return
if count_point == 0:
if (len(str_injury_free) < 1) or (len(str_injury_free) > 3):
tk.messagebox.showwarning(title='waring', message='请保证输入的硬免值在0-100之间!')
return
injury_free = int(str_injury_free) * 1.0
if (injury_free < 0) or (injury_free > 100):
tk.messagebox.showwarning(title='waring', message='请保证输入的硬免值在0-100之间!')
return
if count_point == 1:
str_int = ''
str_float = ''
discriminator = False
for i in range(len(str_injury_free)):
if str_injury_free == '.':
discriminator = True
continue
if discriminator is False:
str_int = str_int+str_injury_free
else:
str_float = str_float+str_injury_free
if len(str_int) > 3:
tk.messagebox.showwarning(title='waring', message='请保证输入的硬免值在0-100之间!')
return
injury_free = int(str_int)
if (injury_free < 0) or (injury_free > 100):
tk.messagebox.showwarning(title='waring', message='请保证输入的硬免值在0-100之间!')
return
if len(str_float) > 2:
tk.messagebox.showwarning(title='waring', message='请保证输入的硬免值的小数部分最多只有两位!')
return
injury_free = injury_free+float('0.'+str_float)
model = list_box.curselection()
if model == ():
tk.messagebox.showwarning(title='waring', message='尚未选择挨打模型!')
return
cal(hp, armor, block_value, injury_free, model)
window = tk.Tk()
window.title('骑士挨打简易模拟器 V测试版')
window.geometry('800x600')
tk.Label(window, text='请输入你的血量: ', font=('Arial', 15)).place(x=10, y=10)
tk.Label(window, text='请输入你的护甲: ', font=('Arial', 15)).place(x=10, y=40)
tk.Label(window, text='请输入你的格挡值:', font=('Arial', 15)).place(x=10, y=70)
tk.Label(window, text='请输入你的硬免(%):', font=('Arial', 15)).place(x=10, y=100)
tk.Label(window, text='请选择挨打模型: ', font=('Arial', 15)).place(x=10, y=130)
var_print = tk.StringVar()
var_print.set('我是模拟结果展示窗o(*︶*)o')
tk.Label(window, textvariable=var_print, bg='white', font=('Arial', 15),
width=37, height=24).place(x=350, y=15)
var_hp = tk.StringVar()
entry_hp = tk.Entry(window, textvariable=var_hp, width=16).place(x=200, y=15)
var_armor = tk.StringVar()
entry_armor = tk.Entry(window, textvariable=var_armor, width=16).place(x=200, y=45)
var_block_value = tk.StringVar()
entry_block_value = tk.Entry(window, textvariable=var_block_value, width=16).place(x=200, y=75)
var_injury_free = tk.StringVar()
entry_injury_free = tk.Entry(window, textvariable=var_injury_free, width=16).place(x=200, y=105)
var_list_box = tk.StringVar()
var_list_box.set(('阿克连砍三刀',
'阿克连砍三刀并向你丢了一团火',
'伊利丹主副主三连'
))
list_box = tk.Listbox(window, listvariable=var_list_box, width=43)
list_box.place(x=10, y=160)
var_button = tk.StringVar()
var_button.set('点我开始模拟挨打!')
button = tk.Button(window, textvariable=var_button,
command=run).place(x=70, y=350)
tk.Label(window, text='注:模拟挨打需要5-10秒\n 请耐心等候\n 结果会有浮动,仅供参考', font=('Arial', 15)).place(x=10, y=450)
window.mainloop()
[/code]
[/collapse]
3.3 应用一《生存端属性收益的换算:一耐力到底等于几躲闪?》
由2.2与2.3可知,不同骑士的属性收益在不同的BOSS战模型下完全不同,需要通过模拟器自行计算
PC端骑士挨打简易模拟器 V测试版
链接:https://pan.baidu.com/s/1_jw_ux3UC9-CSbgQD7xyZQ
提取码:t2cp
更新历史见21楼:https://bbs.nga.cn/read.php?pid=598728996&opt=128
[collapse=效果图:][/collapse]
3.4 应用二《不同高压BOSS战的模拟:我这身装备能不能去抗阿克/血魔/伊利丹?》
我们将BOSS战的模型分为三类:
1、攻速较快的BOSS的纯平砍模型,覆盖了BOSS战全过程。
对于该类模型,一次战斗中的暴毙概率可以用二项分布计算,例如Q1,通常要求原始生存概率高于99.9%
包括:伊利丹主副主三连。
2、不常发生,但是在一次BOSS战中一定会发生的模型。
对于该类模型,一次战斗中的发生次数通常为个位数,通常要求原始生存概率高于99%
包括:血魔连砍二刀并打出一发暗影箭。
3、不常发生,在一次BOSS战中也不一定会发生,但是发生之后的死亡概率较高;以及前两类模型中的特例。
对于该类模型,从P3打到P5也不见得会发生一次,通常要求原始生存概率高于90%[del]死了就甩锅脸黑[/del]
包括:阿克连砍三刀;阿克连砍三刀并向你丢了一团火;主母军刀接堕落加平砍。
四、参考文献
[collapse=4.1 参考文献]
[1] 哈哈企鹅,防骑生存与仇恨平衡的艺术.
https://bbs.nga.cn/read.php?tid=29566644
[2] 苍穹镇魂曲,T6防骑MT开荒装备天赋指导
https://www.bilibili.com/video/B ... arch-card.all.click
[3] whateat,再次引战+整活,基于装备等级的P3神牧治疗装评价
https://bbs.nga.cn/read.php?tid=30744716
[4] 堕落的猴子,[搬运的胜利]从A(Artifact)到A(Average),TBC里的物品平衡
https://bbs.nga.cn/read.php?tid=1550239
[5] madelf,P3团本Boss部分特殊技能伤害汇总
https://bbs.nga.cn/read.php?tid=30112806&_fp=2
[6] Qg_SAI,TBC神圣骑士属性收益、技能等级、专业附魔、装备手法推荐和实用插件宏等
https://bbs.nga.cn/read.php?tid=28024486
[7] madelf,抗性问题,简单记住几个数字就行了。
https://bbs.nga.cn/read.php?tid=30209202
[/collapse] |
1、在门户里发表的文章仅代表作者本人的观点,与本网站立场无关。
2、门户的所有内容都不保证其准确性,有效性,时间性。阅读本站内容因误导等因素而造成的损失本站不承担连带责任。
3、当政府机关依照法定程序要求披露信息时,论坛均得免责。
4、若因线路及非本站所能控制范围的故障导致暂停服务期间造成的一切不便与损失,论坛不负任何责任。
5、注册会员通过任何手段和方法针对论坛进行破坏,我们有权对其行为作出处理。并保留进步追究其责任的权利。
|