00003 不思议迷宫.0009.4:攻防计算



00003 不思议迷宫.0009.4:攻防计算

据说GG大玩家上有攻击和闪避的mod,有时能用,有时会出错。抱着研究的目的,我就试了试,确实如此。我先修改将伤害提高到10000倍,就报数据异常。后来将伤害增加300,重新进本,一直到30多层都没有问题。然后我暂离,再进,发现我的角色已经挂了。这说明服务器端的一些计算并没有错。后来继续用这个+300伤害,过了惑星人3层本(Boss只有800血,两刀砍死),居然拿到了奖励。退出重进后,发现奖励还在。——我能感叹下,这个游戏这是太神奇了嘛。

如果官方看到了我的这篇文章,请立即修正这个bug

欢迎大家进群(161355323)讨论游戏破解和防破解方面的话题,为提高游戏数据的安全性作出贡献。

――――――――――――――――――――――――――――――

当玩家点击一个怪物后,会发生哪些事情呢?当然是攻击怪物,然后怪物反击,然后检测是否死亡……攻击处理在哪里?

对于地牢中的格子,一切都在UIGrid.luac中。查看它的构造,很容易就找到这么一句:

    -- 攻击怪物(boss)的点击操作

   self.attackMonsterClick = nil;

查找attackMonsterClick

-- 创建怪物

function UIGrid:createMonster()

    ……

    localfunction onClicked(sender, eventType)

        ……

        ifeventType == ccui.TouchEventType.ended then

            ……

            -- 怪物攻击先在此处模拟

           DungeonActionM.go("physic_attack", self.gridData:getPos());

 

           EventMgr.fire(event.PLAYER_MOVE, self.index);

           return true;

        end

    end

    ……

   self.attackMonsterClick = onClicked;

end

看看注释,“怪物攻击先在此处模拟”,实在是不知道说什么好了,这完全就是误导。在第一眼的时候,我想到的是,“角色攻击在别处计算”。然而,事实上并不是这样。

打开DungeonActionM.go

-- 客户端执行一条action指令

-- TODO: 待所有指令都调整完毕后,需要把客户端验证的流程也整合进来

function go(cmd, pos, data, extra)

    local record= {["cmd"] = cmd, ["pos"] = pos, ["data"] = data,["extra"] = extra, };

   DungeonLogM.addRecord(record);

   DungeonDebugM.addAction({["cmd"] = cmd, ["pos"] =pos, ["data"] = data, ["extra"] = extra, });

 

    local mod =rules[cmd];

   Profiler.funcBegin("action:" .. cmd);

    local ret,added = mod.doAction({ ["pos"] = pos, ["data"] = data },extra);

    if ret ~=false and true ~= added then

        -- 部分指令已经自行添加了action,就不重复添加

        -- 执行成功了,添加到同步队列中

       DungeonM.addAction({ ["cmd"] = cmd, ["data"] = data,["pos"] = pos, });

    end

 

    if ret ~=false then

        -- 标记一下是有效的action

       record["done"] = 1;

    end

 

    -- 如果需要即时保存一下

    if notisVerifyClient() and needSave then

        needSave= false;

 

       go("save_dungeon");

    end

 

   Profiler.funcEnd("action:" .. cmd);

    return ret;

end

虽然我写代码的水平也很烂,没有实力没有立场,但还是先容我先吐槽一下这段代码。

    local record= {["cmd"] = cmd, ["pos"] = pos, ["data"] = data,["extra"] = extra, };

   DungeonLogM.addRecord(record);

   DungeonDebugM.addAction({["cmd"] = cmd, ["pos"] =pos, ["data"] = data, ["extra"] = extra, });

又是Log又是Debug的,也许真的需要这么分级?②处为啥不是DungeonDebugM.addAction(record);

    local mod =rules[cmd];

rulesrule的复数,看起来是个集合,对其的下标访问返回的应当是个元素。保存的变量名为啥是mod而不是单数形式的rulemodrules之间,必然有一个是命名错误的。

    local ret, added = mod.doAction({ ["pos"] = pos,["data"] = data }, extra);

ret是什么意思?我猜测是表示doAction这一动作是否成功,但也可能表示地球是否停转?

if ret ~= false and true ~= added then

在某些语言中,为了防止将比较误写为赋值,有人建议将常量写在前头。比如added == true可能被误写为added = true,而编译器/解释器并不会报错,就当成赋值处理。但是如果反过来写,true = added,这就错了,常量不能被赋值。这个方法不能说错,但显然违反了正常人的阅读和判断习惯。一个较好的办法是使用isSameisEqualisTrueisFalse之类的函数。如果函数很复杂,就配上单元测试。

    if ret ~=false and true ~= added then

        -- 部分指令已经自行添加了action,就不重复添加

        -- 执行成功了,添加到同步队列中

       DungeonM.addAction({ ["cmd"] = cmd, ["data"] = data,["pos"] = pos, });

    end

前面说过,良好的程序代码都应当是自解释的。再看看这个,“ret ~= false and true ~=added”这个所表达的意思,一眼是看不明白的,要细思慢想,或者,看下面的注释。将判断提取为短小的具名函数可好?

function actionHasBeenDoneSuccessfullyAndNotAdded (……)

名字有点长,这都让人不喜。中间还有个And,说明它作了两件事,还不够简单,也让人不喜。那分开?

function actionHasBeenDoneSuccessfully(……)

function actionHasBeenAdded (……)

    ifactionHasBeenDoneSuccessfully(action) and not actionHasBeenAdded(action) then

    end

原则上没有错,但在本处,显得……呃……

其实,作两件事的锅应该由mod.doAction来背。从名称上来看,这个函数只干一件事,但偏偏返回了两个值,可以考虑将它们分开:mod.doActionDungeonM.actionHasBeenAdded。也许,将action抽取出来,定义为一个显式类型/概念,会更好:action.doaction.hasBeenAdded

    if ret ~=false then

        -- 标记一下是有效的action

       record["done"] = 1;

    end

record是一个局部变量,仅在DungeonLogM.addRecord(record);中被使用。在此处来这么一句,会有啥影响吗?如果DungeonLogM.addRecord中保存的是record的引用,那就有;如果保存的是record的拷贝,那就没有。这可能从一定程度上解释了DungeonDebugM.addAction……) 的参数为啥不是record了。即便如此,我们也有更好的办法:

   DungeonDebugM.addAction(clone(record));

record["done"] = 1到底有什么用?跟踪到DungeonLogM中——里面又有一大堆可以说道说道的内容,还是先略过吧,直接看用到done的地方:

function stepPlay()

    ……

    local record= ME.user.actionRecord or {};

    local index= ME.user.replayIndex or 1;

    ……

    local action= record[index];

 

    ifaction.done then

        localprogress = ME.user.actionProgress;

       ME.user.actionProgress = progress + 1;

       print("******************* 回放进度 " ..progress + 1 .. "/" .. ME.user.actionSum .. "*******************");

    ……

    end

    ……

end

不要被函数中的局部变量的名称所迷惑;此record并非彼recordaction才是。现在,似乎明了了:被标记为done的,将被回放。是这样吗?这个我就不深究了。——我只想说,done确实有用,那么在DungeonActionM.go中就应当让它突出一点,不要像现在那样看起来没什么用处。如果notdonerecordDungeonLogM中没有用处,那么就应当在doAction成功后调用DungeonLogM.addRecord(record)。如果有用,不如弄个函数明确一下,至少也可以让“done”这个内部变量/字符串不要随便蔓延:

DungeonLogM.setRecordStatus(record, 1);

终于来到了最后一段:

    -- 如果需要即时保存一下

    if notisVerifyClient() and needSave then

        needSave= false;

 

       go("save_dungeon");

    end

莫名其妙的needSavego(cmd)中为毛要save_dungeon

needSave在下面的这个函数中被赋值:

function immediatelySave()

    needSave =true;

end

为毛不在这个函数中执行save_dungeon?如果是它是一个延时调用,为毛起名“immediatelySave”?改成saveWhenNextGo是不是好点?

吐槽就先到这里,继续说说DungeonActionM.go("physic_attack",self.gridData:getPos());具体执行了什么。go函数中最重要的一句是mod.doAction({["pos"] = pos, ["data"] = data }, extra)modrules[cmd]cmd"physic_attack"rules是什么鬼?排查后发现:

function init()

    if not _initthen

       loadCsv();

 

        -- 载入所有的规则处理子模块

        rules =LOAD_PATH("game/logic/module/dungeon_actions");

    end

end

rules就是目录game/logic/module/dungeon_actions下的全部luac文件的返回值,我们关心的"physic_attack"

return {

    doAction =function(action)

        -- 怪物攻击先在此处模拟

        localpos = action.pos;

        localgrid = DungeonM.getGridByPos(pos);

        returnSkillM.physic(ME.user, grid.monster);

    end,

};

又跑到了SkillM.physic中:

-- 物理攻击

function physic(source, target, noSync)

    ……

    -- 1. 怪物攻击

    -- 怪物攻击起始动作

    ……

 

    -- 2. 玩家攻击

    -- 玩家攻击起始动作

   initSequence(source, target, 0);

   sequenceList[source]:start(source, target, 0);

    if notFormulaM.invoke("HAPPEN_DODGE", source, target,DungeonM.getRandSeed("HAPPEN_DODGE")) then

        -- TODO:闪避不及,需要计算具体的伤害

       Profiler.funcBegin("physic1-2");

       hit(source, target, PHYSIC_ATTACK, { ["countered"] =countered, });

       Profiler.funcEnd("physic1-2");

 

        -- 触发aoe

        ……

        -- 触发溅射同排怪

        ……

    else

        -- 闪避掉了

        ……

    end

    ……

end

看看,看看,1. 怪物攻击2. 玩家攻击,这和开始时的注释“怪物攻击先在此处模拟”不同啊。玩家攻击中,值得关心的有两点,一个是FormulaM.invoke("HAPPEN_DODGE"),另一个是hit

FormulaM.invoke("HAPPEN_DODGE")的作用是调用src/game/farmula目录下的HAPPEN_DODGE.luac文件的返回值:

-- 计算是否发生闪避

return function(source, target, rand)

     -- 如果有必中属性

    local prop =PropM.combine(source, "true_strike", 1);

    ifPropM.apply(prop, 1) > 0 then

        returnfalse;

    end

 

    -- 如果有必闪属性

    prop =PropM.combine(target, "true_dodge", 1);

    ifsource:queryAttrib("attack") < PropM.apply(prop, 1) then

        returntrue;

    end

 

    -- TODO: 需要source命中 - target闪避来计算概率

    localaccuracy = source:getAccuracy();

    local dodge= target:getDodge();

 

    -- 召唤兽忽视敌人闪避

    ifsource.type == OBJECT_TYPE_SUMMON then

        -- 忽视闪避属性

        prop = PropM.combine(source,"summon_ignore_dodge", 1);

        dodge =PropM.apply(prop, dodge);

    end

 

    localhitRatio = accuracy - dodge;   -- 命中的概率

    rand = rand% 100;

    ……

    -- 是否闪避(不命中)

    return rand>= hitRatio;

end

可以看出,必中的优先级高于必闪。

在函数的开头,我们可以做个简单的判断:

    if target.type== OBJECT_TYPE_MONSTER then

        returnfalse;

    elseif target.type== OBJECT_TYPE_USER then

        returntrue;

    end

如果目标是怪,必然不闪避;如果目标是主角,必然闪避;其他的,比如召唤兽,正常计算。

hit用于伤害计算:

-- 对目标物理打击扣血

function hit(source, target, skillId, extra_data)

    ……

 

    -- 玩家扣血、触发

    local attack= source:getAttack();

    ……

    -- 伤害为攻击的倍数(暂时只能为召唤兽)

    local prop =combine(source, "multiple_damage", 1);

    if prop[3]> 0 then

        attack =trigger(source, prop[1], prop[2], attack);

    end

 

    -- 被敌方百分比削弱

    prop =combine(target, "weak_enemy", "attack");

    attack =PropM_apply(prop, attack);

 

    -- 绝对值削弱

    prop =combine(target, "weak_enemy2", "attack");

    attack =PropM_apply(prop, attack);

 

    -- 基础伤害等于攻击

    local damage= attack;

    ……

    ----------------- 计算sourceprop影响下的总伤害-------------------------------

    ----------------- 先计算总的加成,最后再相加(百分比计算时不叠加)---------------

    -- 概率额外伤害

    ……

    -- 古剑术(概率额外伤害)

    ……

    -- 醉拳(概率额外伤害)

    ……

    ----------------- 计算targetprop影响下的总伤害 ----------------------

    ----------------- 先百分比后绝对值,百分比计算时直接叠加 ---------------

    ……

    -- 物理抗性

    ……

    -- 忽视抗性

    ……

    -- 开板降低物抗

    ……

 

    -- 最终伤害,不能低于0

    damage =damage + addon;

    damage =math.max(damage, 0);

    ……

    -- 概率触发技能

    ……

    -- 亡灵契约:概率触发技能

    ……

    -- 雷神之锤:概率触发技能

    ……

end

       最终伤害,乘上1万倍好了。

――――――――――――――――――――――――――――――

       以上说这么多都是没用的,客户端修改伤害并不会影响服务器端的计算。所谓的攻击和闪避mod,只是逗人玩的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值