|  | 
 
| 本帖最后由 独孤后人 于 2021-9-23 15:45 编辑 
 骑砍战团的完整伤害计算公式一直是T社的商业机密,但是WSE的作者在11年就通过逆向骑砍程序得到了这个计算公式并在网上公布了出来。这也就是网上很多伤害模拟器使用的公式的来源了。
 WSE作者的原贴在:
 http://forums.taleworlds.com/index.php?topic=168722.0
 我在后面会把代码搬运过来方便大家直接看。
 
 一、原始伤害raw_damage
 首先,是计算原始伤害(raw_damage),这一步以武器伤害的数值为基准,通过蓄力时间、速度奖励、熟练度、技能等级、天气、角色力量、双手惩罚等因素进行数值的计算和修正:
 
 | 复制代码
if hold_time >= 1.1
        hold_bonus = 1.2
elif hold_time >= 0.6:
        hold_bonus = (1.1 - hold_time) * 0.6 + 1.2
elif hold_time >= 0.5:
        hold_bonus = 1.5
else:
        hold_bonus = hold_time + 1.0
raw_damage = weapon_damage * (clamp(hold_bonus, 1.0, 2.0) * 0.5 + 0.5)
if weapon_type == 'one_handed' or weapon_type == 'two_handed' or weapon_type == 'polearm':
        raw_damage *= math.pow(melee_damage_speed_power, speed_bonus)
elif weapon_type == 'crossbow' or weapon_type == 'bow' or weapon_type == 'throwing':
        raw_damage *= math.pow(missile_damage_speed_power, speed_bonus)
if weapon_type == 'crossbow':
        if raining:
                raw_damage *= 0.75
else:
        raw_damage *= proficiency * 0.01 * 0.15 + 0.85
        
        if weapon_type == 'bow':
                raw_damage *= min(power_draw, difficulty + 4) * 0.14 + 1
                
                if mounted:
                        raw_damage *= horse_archery * 0.019 + 0.8
                
                if raining:
                        raw_damage *= 0.9
        elif weapon_type == 'throwing':
                raw_damage *= power_throw * 0.1 + 1.0
                
                if mounted:
                        raw_damage *= horse_archery * 0.019 + 0.8
        elif weapon_type == 'one_handed' or weapon_type == 'two_handed' or weapon_type == 'polearm':
                raw_damage *= power_strike * 0.08 + 1.0
        raw_damage += strength / 5.0
        if (weapon_type == 'two_handed' or weapon_type == 'polearm') and (has_shield or mounted):
                raw_damage *= 0.85
                
                if weapon_type == 'polearm':
                        raw_damage *= 0.85
                
                if weapon_flags & itp_two_handed:
                        raw_damage *= 0.9
raw_damage = clamp(raw_damage, 0, 500)
 | 
 有几点说明一下
 1.蓄力会有一个伤害的奖励。具体来说,蓄力奖励在0-0.5秒之间逐步上升,在0.5秒至0.6秒之间达到最大,即伤害为武器数值的1.5倍。 蓄力0.6秒之后就开始下降,蓄力超过1.1秒,奖励就变成1.2倍不会再变化。
 
 2.熟练度会影响除弩之外所有武器的伤害, 至少保证加100点熟练度,才能保证发挥武器数值100%的伤害,此外,每额外100点熟练度会加15%的武器伤害。
 
 3.弩比较特殊:
 它的伤害既不受熟练度影响,也不受骑射技能影响,只在下雨天气时才会受到影响,发挥75% 的威力。这很符合真实世界中弩的特性 :一把上好弦的弩,即使交给小孩子来扣动扳机也是一样的威力。
 
 4.另外,长杆和双手武器在持盾和骑马的时候,伤害会受到负面影响:
 最惨的情况:双手长杆骑在马上(双手长杆就是无法拿盾的长杆),只能发挥原本65%的威力!
 其次就是: 普通长杆骑着马,或者普通长杆拿个盾,只能发挥72.25% 。
 再次就是:纯双手武器骑马(比如双手剑),只能发挥76.5%
 单/双手两用的武器(比如手半剑),不论骑马还是持盾,都是发挥85%威力,这点相比纯双手武器还算好一些。
 
 5.关于速度加成的奖励,
 战团有一个被诟病的BUG,就是两个速度相同,一前一后同向全速前进的骑兵(简单说就是追击战), 如果前面的骑兵向后面的骑兵射一箭或者丢一镖,那么在计算后面被打中骑兵受到的伤害时,会有一个很高的速度加成,相当于后面的骑兵正面冲向一个静止目标,被静止目标投出的道具射中时的相对速度。(楼下有网友指出骑砍公式并不违背物理规律,这一段删除)这显然是不符合真实世界中的物理规则的,否则,你在高铁上拿小球砸一下你后座的人,就会出人命了。所以,追击一个有远程武器的骑兵,在此BUG影响下是有额外的风险的。
 
 二、有效伤害effective_damage
 经过代码1计算后得出了原始的伤害raw_damage后,下一步开始在原始伤害的基础上,进行护甲对伤害的吸收及减免,最后得出最终的有效伤害值effective_damage。这一部分的计算过程是存在随机值的,也就是说,骑砍里最终的有效伤害是在一定范围内随机浮动的,不受玩家控制。
 
 | 复制代码
armor = appropriate_armor_value_for_hit_location
if hit_shield_on_back:
        armor += shield_resistance + 10
soak_factor = armor * module.ini_soak_factor_for_damage_type
reduction_factor = armor * module.ini_reduction_factor_for_damage_type
if item_flags & itp_extra_penetration:
        soak_factor *= module.ini_extra_penetration_soak_factor
        reduction_factor *= module.ini_extra_penetration_reduction_factor
randomized_soak = (random.random() * 0.55 + 0.45) * soak_factor
randomized_damage = (random.random() * 0.1 + 0.9) * raw_damage
soaked_damage = randomized_damage - randomized_soak
if (soaked_damage < 0.0):
        soaked_damage = 0.0
randomized_reduction = math.exp((random.random() * 0.55 + 0.45) * reduction_factor * 0.014)
reduced_damage = (1.0 - 1.0 / randomized_reduction) * soaked_damage
if (reduction_factor < 0.00001):
        reduced_damage  = 0.0
damage_difference = round(reduced_damage + randomized_soak)
effective_damage = randomized_damage - damage_difference
if hit_bone == head:
        effective_damage *= 1.2
        if item_is_ranged:
                effective_damage *= 1.75
elif hit_bone == calf or hit_bone == thigh:
        effective_damage *= 0.9
effective_damage = clamp(effective_damage, 0.0, 500.0)
 | 
简单来说,根据武器的伤害类型,护甲会吸收及减免一部分伤害,对不同伤害类型吸收减免的系数在module.ini里有定义。比较有意思的是,在计算完护甲对伤害的吸收及减免后,还有一个对击中部位的处理,即无论近战还是远程,打击头部都是最有效的(近战120%,远程210%),打击腿、脚部是伤害最低的(90%)。所以打架时劈头盖脸的乱砍准没错的。
 
 另外,根据代码内容,背在背后的盾牌也大有用处。具体来说,如果敌人正好击中了你背后背盾牌的地方,那么在这一部分计算伤害减免的时候,你的护甲会按照被击打部位的护甲值+盾牌抗击+10 来计算,这可是很高的一个加强了,相当于皮甲直接变板甲了!
 
 在了解到这个公式后, 有时游戏中持双手武器向对方弓箭手冲锋时,如果恰巧背着一块盾牌且眼看敌箭要射中自己了,扭过去把屁股留给敌人不失为一个无奈但有效的选择。
 其实在战团初期大约还是测试版本的时候,对于背后的盾牌有一个更逆天的设定,即盾牌在背后也能起到完全挡住远程武器的作用,这就和现在在手里拿着盾牌但是不举起来的状态下也能挡住来箭是差不多的。后来因为影响平衡及游戏性,就改成现在这样的设定了。
 
 以上搬运来的代码只能算是伪代码。掌握了算法,就可以自己写出模拟器了。比如笔者之前写了一个CRPG的模拟器地址是
 http://mnbcn.0fees.us
 主要就是用的上面的公式,不过CRPG里面对伤害还有其他的附加处理,这里只把战团部分的主要代码分享出来供参考(PHP代码,非专业码农,将就看吧)。
 
 
 | 
 复制代码function random()
{
    return mt_rand()/mt_getrandmax();
}
//clamp a number just like how clamp() use in other language.
function clamp($x,$min,$max)
{
    if ($x<$min){
        return $min;
    }elseif ($x>$max){
        return $max;
    }else{
        return $x;
    }
}
function raw_damage($weapon_type,$weapon_damage,$difficulty,$hold_time,$speed_bonus,$strength,$proficiency,$power_skill,$horse_archery,$has_shield,$mounted,$raining)
{
        if(func_num_args()!=12){return -1;}
        $is_melee=($weapon_type==one_handed)||($weapon_type==two_handed)||($weapon_type==polearm);
        $is_ranged=($weapon_type==crossbow)||($weapon_type==bow)||($weapon_type==thrown);
        //$hold_time=round(random()*2, 2);//this is for test use.randomized a hold time from 0 - 2 seconds.
        if ($hold_time>=1.1){
                $hold_bonus = 1.2;
        }elseif ($hold_time >= 0.6){
                $hold_bonus = (1.1-$hold_time)*0.6+1.2;
        }elseif ($hold_time >= 0.5){
                $hold_bonus = 1.5;
        }else{
                $hold_bonus = $hold_time+1.0;
        }
        $raw_damage = $weapon_damage*(clamp($hold_bonus,1.0,2.0)*0.5+0.5);
        if ($is_melee){
                $raw_damage *= pow(melee_damage_speed_power,$speed_bonus);
        }elseif ($is_ranged){
                $raw_damage *= pow(missile_damage_speed_power,$speed_bonus);
        }
        if ($weapon_type==crossbow){
                if ($raining){
                        $raw_damage *= 0.75;
                }
        }else{
                $raw_damage *= $proficiency*0.01*0.15+0.85;
        
                if ($weapon_type == bow){
                        $raw_damage *= min($power_skill, $difficulty + 4) * 0.14 + 1;
                        if ($mounted){
                                $raw_damage *= $horse_archery * 0.019 + 0.8;
                        }
                        if ($raining){
                                $raw_damage *= 0.9;
                        }
                }elseif ($weapon_type==thrown){
                        $raw_damage *= $power_skill * 0.1 + 1.0;
                        if ($mounted){
                                $raw_damage *= $horse_archery * 0.019 + 0.8;
                        }
                }elseif ($is_melee){
                        $raw_damage *= $power_skill * 0.08 + 1.0 ;
                }
                $raw_damage += $strength / 5.0;
                if ((($weapon_type==two_handed)||($weapon_type==polearm)) && ($has_shield||$mounted)){
                        $raw_damage *= 0.85;    //2s,ps,2m,22m,pm,2pm, 2sm, psm
                        if ($weapon_type==polearm){    //ps,pm,psm,2pm
                                $raw_damage *= 0.85;
                        }        
                        if ( (($weapon_type==two_handed)&&(!$has_shield)&&($mounted))||(($weapon_type==polearm)&&(!$has_shield)&&($mounted)) ){//22m,2pm
                                $raw_damage *= 0.9;
                        }
                }
        }
        $raw_damage = clamp($raw_damage, 0, 500);
        return $raw_damage;
}
function effective_damage($hit_bone,$raw_damage,$damage_type,$extra_penetration,$hit_shield_on_back,$shield_resistance,$armor,$low_high)
{
    
    if(func_num_args()!=8){return -1;}
    $is_melee=($weapon_type==one_handed)||($weapon_type==two_handed)||($weapon_type==polearm);
    $is_ranged=($weapon_type==crossbow)||($weapon_type==bow)||($weapon_type==thrown);
    if( $damage_type==cut ){
        $soak_factor_for_damage_type=armor_soak_factor_against_cut;
        $reduction_factor_for_damage_type=armor_reduction_factor_against_cut;
    }elseif ($damage_type==pierce){
        $soak_factor_for_damage_type=armor_soak_factor_against_pierce;
        $reduction_factor_for_damage_type=armor_reduction_factor_against_pierce;
    }elseif ($damage_type==blunt){
        $soak_factor_for_damage_type=armor_soak_factor_against_blunt;
        $reduction_factor_for_damage_type=armor_reduction_factor_against_blunt;
    }
    if ($hit_shield_on_back){
        $armor += $shield_resistance+10;
    }
    $soak_factor= $armor*$soak_factor_for_damage_type;
    $reduction_factor = $armor*$reduction_factor_for_damage_type;
    if ($extra_penetration){
        $soak_factor *= extra_penetration_soak_factor;
        $reduction_factor *= extra_penetration_reduction_factor;
    }
    
    if ($low_high==highest){
        $randomized_soak= (0.0 + 0.45)*$soak_factor;
        $randomized_damage= (0.1 + 0.9)*$raw_damage;
    }elseif ($low_high==lowest){
        $randomized_soak= (0.55 + 0.45)*$soak_factor;
        $randomized_damage= (0.0 + 0.9)*$raw_damage;
    }elseif ($low_high==random){
        $randomized_soak= (random()*0.55 + 0.45)*$soak_factor;
        $randomized_damage= (random()*0.1+0.9)*$raw_damage;
    }
    
    $soaked_damage=$randomized_damage - $randomized_soak;
    if ($soaked_damage < 0.0){
        $soaked_damage=0;
    }
    
    if ($low_high==highest){
        $randomized_reduction = exp((0.0 + 0.45)*$reduction_factor*0.014);
    }elseif ($low_high==lowest){
        $randomized_reduction = exp((0.55 + 0.45)*$reduction_factor*0.014);
    }elseif ($low_high==random){    
        $randomized_reduction = exp((random()*0.55+0.45)*$reduction_factor*0.014);
    }
    
    $reduced_damage = (1.0-1.0/$randomized_reduction)*$soaked_damage;
        
    if ($reduction_factor < 0.00001){
        $reduced_damage = 0.0;
    }
    $damage_difference = round($reduced_damage + $randomized_soak);
    $effective_damage = $randomized_damage - $damage_difference;
    if ($hit_bone==head){
        $effective_damage*=1.2;
        if ($is_ranged){
            $effective_damage *= 1.75;
        }
    }elseif (($hit_bone==calf)||($hit_bone==thigh)){
        $effective_damage *= 0.9;
    }
    $effective_damage=clamp($effective_damage,0.0,500.0);
    return $effective_damage;
}
 | 
 另外,骑砍中远程飞行道具,随着飞行时间的增长,伤害就会下降,在击中目标后,会根据飞行时间计算伤害。 但具体计算公式还没有,希望有知道的骑友能补充一下。
 
 题外话:国内的MOD似乎侧重于单机剧情,建模、功能设计、历史、文化展示方向的多一些,而涉及联机或竞技的较少。上文公式引用的帖子是WSE作者在11年就发布过的,在国内未见讨论。  CRPG MOD应该说是在这方面钻研是比较深的了,WSE的作者就在他们团队中。 因此,在他们的联机MOD中,如何重新设计各种数值来提升游戏体验及平衡性,他们是能从数学建模上来进行分析和设计的。  如果说对于单机MOD来说,平衡还能靠参考原版数值、或通过主观感觉和不断手动调试来进行而不会引起平衡性问题,或者平衡性问题根本不是一个需要考量的因素的话,那么想要做联机MOD,尤其是竞技性或对抗性比较高的联机MOD的话,这些底层的公式就不能不考虑了。
 
 
 
 
 | 
 评分
查看全部评分
 鲜花鸡蛋LtDoyle  在2021-4-26 19:24  送朵鲜花   并说:我非常同意你的观点,送朵鲜花鼓励一下诺德精锐海寇  在2019-11-25 15:18  送朵鲜花   并说:我非常同意你的观点,送朵鲜花鼓励一下0092065  在2018-10-16 22:09  送朵鲜花   并说:我非常同意你的观点,送朵鲜花鼓励一下左左左  在2018-4-7 09:35  送朵鲜花   并说:良心icemirror  在2017-6-26 00:13  送朵鲜花   并说:我非常同意你的观点,送朵鲜花鼓励一下生锈的风干肉  在2017-4-6 23:43  送朵鲜花   并说:我非常同意你的观点,送朵鲜花鼓励一下iamthetopone  在2017-4-5 13:13  送朵鲜花   并说:楼主好样的,这样的转贴也是很重要的技术贴,不要因为某些人莫名其妙的恶意坏了心情南潇湘  在2017-2-23 14:08  送朵鲜花   并说:我非常同意你的观点,送朵鲜花鼓励一下 |