加载中…
个人资料
替别人活着
替别人活着
  • 博客等级:
  • 博客积分:0
  • 博客访问:53,517
  • 关注人气:12
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

暗黑非资料片8pp杀地狱Diablo只获得1点经验值的代码分析

(2008-05-15 23:09:42)
标签:

游戏

分类: 暗黑世界
 


    当你在非资料片模式下,在地狱难度最后杀Diablo的时候,如果是8个玩家结盟在Diablo的场景,有人就会要求等级最低的离队。
    这看似是一个奇怪的要求,但是如果没人离队,就是说如果是8pp杀Diablo的话,那么每个玩家只能获得1点经验,而不是预期的大量经验。
    同样的情形发生在资料片模式地狱难度杀Baal五小队的第二小队的时候,如果没人离队而且8个玩家都在场,那么每个玩家只能获得1点经验。BN上有一些老玩家习惯在杀第二小队的时候,等级最高的离队,杀完第二小队后,再重新组队。

    这个问题解决方法只有一个:杀Diablo或者第二小队的时候,最多保持7个玩家在场。

    如果你想深究这个原因,你回去查询IMPK的经验计算过程,“Experience计算的详细流程”http://impk.blizzard.cn/ShowTopic-546719-34.html
    你会发现其中并没有说明为何出现这种情况。因此我决定研究一下程序代码,以找到真正的原因。

    很容易,可以发现游戏中经验值的计算代码,而问题出在结盟时候经验分配的计算上,代码如下(1.11b的代码,1.10的算法一样)
.text:6FC9E04D loc_6FC9E04D:                           ; CODE XREF: PersonExpGain2+50j
.text:6FC9E04D                 mov     esi, [esp+70h]  ; 引入结盟因素,基础经验值=0x0039FD64
.text:6FC9E051                 lea     eax, [ecx-1]    ; 同一区域内在exp分配范围内结盟玩家数
.text:6FC9E054                 imul    eax, esi        ; 0x0195EDBC=exp*(player-1)
.text:6FC9E057                 imul    eax, 59h        ; 0x8D1FA65C=exp*(player-1)*89
.text:6FC9E05A                 cdq                     ; EDX=FFFFFFFF EAX=8D1FA65C
.text:6FC9E05B                 and     edx, 0FFh       ; EDX=FF  EAX=8D1FA65C
.text:6FC9E061                 add     eax, edx        ; EAX=8D1FA75B=8D1FA65C+FF
.text:6FC9E063                 sar     eax, 8          ; EAX=EAX/256=FF8D1FA7
.text:6FC9E066                 add     eax, esi        ; EAX=FFC71D0B=FF8D1FA7+0039FD64
.text:6FC9E068                 mov     [esp+5Ch+arg_8], eax
.text:6FC9E06C                 xor     esi, esi
.text:6FC9E06E                 test    ecx, ecx        ; 游戏内玩家数=8
.text:6FC9E070                 fild    [esp+5Ch+arg_8] ; FFC71D0B
.text:6FC9E074                 pushf
.text:6FC9E075                 cmp     dword_6FD39F7C, 0
.text:6FC9E07C                 jnz     short loc_6FC9E084
.text:6FC9E07E                 fidiv   dword ptr [esp+5Ch] ; 除以0x277==同一场景内所有玩家级别之和
.text:6FC9E082                 jmp     short loc_6FC9E08D
.text:6FC9E084 ; ---------------------------------------------------------------------------
.text:6FC9E084
.text:6FC9E084 loc_6FC9E084:                           ; CODE XREF: PersonExpGain2+ACj
.text:6FC9E084                 push    dword ptr [esp+5Ch]
.text:6FC9E088                 call    unknown_libname_13 ; Microsoft VisualC 2-8/net runtime
.text:6FC9E08D
.text:6FC9E08D loc_6FC9E08D:                           ; CODE XREF: PersonExpGain2+B2j
.text:6FC9E08D                 popf
.text:6FC9E08E                 fstp    dword ptr [esp+70h] ; C5B8A225
.text:6FC9E092                 jle     short loc_6FC9E0CB
.text:6FC9E094
.text:6FC9E094 loc_6FC9E094:                           ; CODE XREF: PersonExpGain2+F9j
.text:6FC9E094                 mov     edi, [esp+esi*4+34h] ; 4B=玩家级别
.text:6FC9E098                 mov     ebx, [esp+esi*4+14h] ;
.text:6FC9E09C                 mov     [esp+5Ch+arg_8], edi
.text:6FC9E0A0                 fild    [esp+5Ch+arg_8] ; 4B
.text:6FC9E0A4                 fmul    dword ptr [esp+70h]
.text:6FC9E0A8                 call    __ftol2
.text:6FC9E0AD                 mov     ecx, [esp+6Ch]  ; exp=5A
.text:6FC9E0B1                 push    ecx
.text:6FC9E0B2                 push    ebp             ; ptGame
.text:6FC9E0B3                 call    PersonExpGain
.text:6FC9E0B8                 push    eax
.text:6FC9E0B9                 push    edi
.text:6FC9E0BA                 push    ebp
.text:6FC9E0BB                 mov     eax, ebx
.text:6FC9E0BD                 call    sub_6FC9DDB0
.text:6FC9E0C2                 mov     eax, [esp+54h]
.text:6FC9E0C6                 inc     esi
.text:6FC9E0C7                 cmp     esi, eax
.text:6FC9E0C9                 jl      short loc_6FC9E094

    关键的代码段是从6FC9E04D到6FC9E092这一段。代码边上的注释,其游戏场景就是:非资料片,地狱难度,8pp在场KD。

    首先,地狱难度的Diablo的基础经验值是exp1=0x0039FD64,计算算法如下:

1.exp1=0x0039FD64
2.exp2=exp1*(8-1)=0x0195EDBC
3.exp3=exp2*89=0x8D1FA65C
4.将32位的exp3符号扩展成64位的值。由于exp3的最高位为1,所以扩展后EDX=0xFFFFFFFF。注意,EDX:EAX已经变成负数了....
5.EDX&0xFF,就是限制EDX不能太大,由于是负数,所以EDX=0x000000FF
6.exp4=exp3+EDX,很奇怪,为什么要加上高半部分呢?!
7.exp5=exp4/256=FF8D1FA7,注意这里用的是算数移位,由于最高位是1,所以补入1
8.使用fild指令,将exp5装入浮点寄存器。注意fild指令认为这是一个32位的有符号数,所以到了浮点寄存器里面,变成负数浮点数了
9.exp6=exp5/同一场景内所有玩家级别之和

下面开始循环了
10.exp7=exp6*当前玩家的等级
11.将exp7放入eax,调用PersonExpGain函数,进一步修正玩家最终获得的经验
12.对获得经验的每个玩家,重复10、11步

PersonExpGain函数执行经验修正,具体算法就如IMPK的资料所示,值得注意的是,一开始有一个判断
.text:6FC9B3E0                 push    esi
.text:6FC9B3E1                 mov     esi, eax        ; 经验总数
.text:6FC9B3E3                 cmp     esi, 7FFFFFh
.text:6FC9B3E9                 jle     short loc_6FC9B3FE
.text:6FC9B3EB                 mov     esi, 7FFFFFh
.text:6FC9B3F0
.text:6FC9B3F0 loc_6FC9B3F0:                           ; CODE XREF: PersonExpGain+20j
.text:6FC9B3F0                 test    ebx, ebx        ; ebx=ptPlayer
.text:6FC9B3F2                 jz      short loc_6FC9B40B
.text:6FC9B3F4                 cmp     dword ptr [ebx], 0
.text:6FC9B3F7                 jnz     short loc_6FC9B40B
.text:6FC9B3F9                 mov     eax, [ebx+4]    ; ptPlayer->Type PAL=3
.text:6FC9B3FC                 jmp     short loc_6FC9B40D
.text:6FC9B3FE ; ---------------------------------------------------------------------------
.text:6FC9B3FE
.text:6FC9B3FE loc_6FC9B3FE:                           ; CODE XREF: PersonExpGain+9j
.text:6FC9B3FE                 test    esi, esi
.text:6FC9B400                 jg      short loc_6FC9B3F0 ; 经验小于等于0,则强制为1
.text:6FC9B402                 mov     eax, 1
.text:6FC9B407                 pop     esi
.text:6FC9B408                 retn    8

    Blizzard的程序员有先见之明,一开始拿获得的经验值与0x7FFFFF比较,取较小者。但是此时的EAX是负数,所以在
6FC9B3E9                 jle     short loc_6FC9B3FE
处的比较会成功,转到
6FC9B3FE                 test    esi, esi
然而这个test会失败,跳过了所有后续复杂的经验值修正,所以最后获得的经验值就是1,然后直接返回。

    从上面的代码可以看出,暴雪的程序员应当试图在按照IMPK给出的公式来进行编程,但是由于没有注意到数值的有效位数,导致很容易就溢出变成负数,从而导致8pp KD奇怪的经验值获得。


    如果是7pp KD,那么计算如下:
1.exp1=0x0039FD64
2.exp2=exp1*(7-1)=0x015BF058
3.exp3=exp2*89=0x78F68E98
4.将32位的exp3符号扩展成64位的值。由于exp3的最高位为0,所以扩展后EDX=0x00000000。注意,EDX:EAX还是正数....
5.EDX&0xFF,就是限制EDX不能太大,由于是正数,所以EDX=0x00000000
6.exp4=exp3+EDX=0x78F68E98
7.exp5=exp4/256=0x0078F68E,注意这里用的是算数移位,由于最高位是0,所以补入0
8.使用fild指令,将exp5装入浮点寄存器。注意fild指令认为这是一个32位的有符号数,所以到了浮点寄存器里面,还是正数浮点数了
9.exp6=exp5/同一场景内所有玩家级别之和

下面开始循环了
10.exp7=exp6*当前玩家的等级
11.将exp7放入eax,调用PersonExpGain函数,进一步修正玩家最终获得的经验
12.对获得经验的每个玩家,重复10、11步

 

    可见,7PP KD,可以获得正常的经验值。

 

    如何修正这个经验值计算的BUG?且听下回分解!

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有