inkflower 发表于 2013-10-13 10:52:39

使用perl和COM技术加快zmud721逍遥行计算速度

假装是前言

相信使用逍遥行zmud7.21的玩家时有这样的体验,当正急着去某地打怪时逍遥行却缓慢的1,2,3,4,.....然后在simplying........的时候又停顿许久,等到终于开始走路并且抵达目的地了,怪已经从泉州港跑到不知道哪个山路去了,真是令人懊恼。zmud脚本在做迭代递归的计算时速度的硬伤也就被放大了。

最近论坛关于zmud中使用com的几篇贴子终于让com精髓浮出zmud的水面啦,而写这篇的目的在于引出一种在zmud721上使用非js,vbscript的思路,让使用zmud721不愿使用mush,不愿使用c等高级语言,且又不懂js,vbscript的苦手不再羡慕cmud对大部分语言的良好支持。欢迎大家拍砖,多谢大家支持,呼唤用perl的小伙伴

下面,让我们开始吧。

一、当前zmud721逍遥行的实现和使用perl重写routecreate
二、用perl编写COM组件以及在zmud调用以替代routecreat
三、现在还未想到的东东


未完待续

北大侠客行MUD,中国最好的MUD

inkflower 发表于 2013-10-13 10:52:50

本帖最后由 inkflower 于 2013-10-13 06:42 PM 编辑

一、当前zmud721逍遥行的实现和使用perl重写routecreate

相信使用zmud721逍遥行的人不少,alucar在他的贴子里对逍遥行的工作方法和实现已经说得很清楚了,这里简单总结一下,以便行文。逍遥行实现的是地图上每个城市之间的自由行走,可以从当前城市随意走到任何一个记录在案的城市,实现城市之间的寻径和行走。而这里的核心则是寻径和计算最终路径。路径的计算是在预先建立的路径表和路径连接关系表上广度优先搜索得来的,寻径成功后,按照预先记录的路径简化路径并生成最终供行走的路径。然后就是愉快的行走部分了。

整个寻径和路径简化的部分是zmud计算的瓶颈,于是就萌发了使用别的方法来取代寻径部分的想法。而这部分的实现可以使用任何语言任何工具去完成,比如zmud721内嵌的JS,VBS支持其实是最佳的,因为JS和VBS无论在计算速度和实现方法上都比ZMUD脚本更优。由于不太熟悉这两种脚本语言,因此选择了最熟悉的PERL。但zmud721不内置支持perl.因此必需提供一种方法使zmud721可以和perl脚本交换数据。考虑到zmud对COM组件良好的内置支持,因此自然而然地想到了COM,当然zmud也支持DDE,直接用DDE调用perl脚本也是可行的,不过不在这里讨论的范围。

决定了语言和方法,下面要选择工具了。perl解释器选择了activeperl,版本随意,目前我用的是5.16,基本上5.6版本以后的activeperl应该都能满足基本的寻径编程了,而且没有什么兼容方面的问题。而使用perl编写COM组件,当然少不了perlctrl,这是activestate公司的perl dev kit中的一个组件,需要安装perl dev kit。两种工具都可以在activestate官网下载到,前者是免费软件,后者则是付费软件,不过有21天的试用版。也有破解版,然而目前只有9.1.1的破解版,最高只支持activeperl5.14,不支持perl5.16。考虑到perl dev kit最低只支持perl5.8,所以activeperl最低选择是5.8,这些信息都可以在perl dev kit的release note里找到。

接下来就进入实现环节了。首先我们要做的是把逍遥行中的routecreate alias用perl完全实现,routecreate是逍遥行中的核心部分,他完成了寻径和路径简化的功能,然后把最终路径放到Route_list变量中,调用walk alias进行行走。而我们要做的就是把routecreate用perl重写啦,我是不是说了很多遍了,好吧,这是最后一遍啦,让我们开始写。

终于写完啦,让我们来运行一下单纯的perl程序,看看我们会得到什么吧,现在我们在扬州,想从扬州去福州,在逍遥行里,我们需要站在扬州中央广场,然后在zmud键入gt fz。而使用我们的gtpl.pl,我们需要这样使用: perl gtpl.pl yz fuzhou,或者perl gtpl.pl yz fz



以上就是执行的显示结果啦,路径已经存成zmud变量喜欢的格式。附件是perl的源码供感兴趣的人参考。perl作为一款优秀的文本处理脚本和广为人诟病的太灵活太随意以及没有安全保护的实现,可是它还是一款优秀的文本处理脚本语言,不过在如今各种脚本语言百花齐放的时代,任何一款脚本语言都可以胜任寻径的实现。所以在实现上就不赘述了。并且这里羞愧地说,我其实只是用perl把routecreate逐句翻译了一下,shy。另外,目前数据都直接和代码绑在一起很不好看,将就着看吧。



接下来敬请期待如何用perl编写COM组件和与zmud的交互,预计下周周末更新吧,不确定会不会推迟

inkflower 发表于 2013-10-13 10:52:56

本帖最后由 inkflower 于 2013-10-14 06:02 PM 编辑

二、用perl编写COM组件以及在zmud调用以替代routecreate


好了,现在我们已经用perl实现了寻径和路径简化,下面就到了关键的环节,怎么让zmud和perl程序通信交换数据呢?首先,zmud必须通过某种方法告诉perl我们要从哪个城市到哪个城市,同时,perl应该提供一种方法把经过计算后的最终路径传回到zmud。

怎么让zmud告诉perl程序需要完成什么呢?这时候就要借助COM组件了,关于zmud与COM组件的通信的实现,论坛上已经有玩家发过相关贴子了,这里就不重复了,具体请见:
http://pkuxkx.net/forum/thread-35918-1-2.html

好了,我们知道zmud可以与COM组件通信,但是我们的perl不是一个COM组件呀,怎么把我们的perl代码转化成一个COM组件呢?是时候让perlctrl上场啦!如上一篇所说perlctrl是activelstate公司开发的perl dev kit中的一个开发工具之一,它就是一个转化perl程序为标准的ActiveX控件的实用工具。让我们来看看它是怎么转化的。要记住,它不是编译器,也不是一个perl解释器,它就是一个小工具。

perlctrl可以把符合perlctrl格式的perl源代码转换成windows的DLL。还记得我们在上一篇中的perl源码么,它是单纯的perl代码,你可以让它在perl解释器中运行,得到你想要的结果,比如perl gtpl.pl yz bj, 运行结束后控制台上会打印最终的路径,但我们的目的不是打印,我们的目的是让我们的perl程序接收来自zmud的命令并且把最终计算出的路径传回给zmud。因此,按照我们既定的思路,我们需要先把我们的perl源码写成perlctrl能够识别的格式。让我们一步一步的来揭露转化的过程。

首先,要保证在你的机器上安装了5.8版本以上的activeperl,接下来,安装perl dev kit,在这之后,你就拥有了perlctrl的运行环境,注意,perlctrl是依赖于activeperl解释器的,因为它不是一个解释器。perlctrl生成.dll时将打包一个perl解释器,perl源码和这个源码所依赖的所有perl模块以及依赖的其他.dll,当你运行这个.dll时,这些一起打包的所有模块将协同工作,实际上运行的仍然是perl程序。枯燥的理论终于过去啦,让我来看看我们应该怎么做!

首先,打开一个命令行程序,然后运行perlctrl -t > template.pl,这样保证perlctrl给我们的新.dll生成一个唯一的GUID,这是COM组件的特性决定的,每个COM都必须有一个独一个无二的GUID,如果没有COM知识,那么让我们类比一下,写过mush插件的人都知道要给插件申请一个唯一的ID,所以这里的GUID就是这个唯一的ID了。生成的template.pl会长成这样:=pod

=begin PerlCtrl

    %TypeLib = (
        PackageName   => 'MyPackage::MyName',
      DocString       => 'My very own control',
      HelpFileName    => 'MyControl.chm',
      HelpContext   => 1,
        TypeLibGUID   => '{EFF666F8-8423-474F-81E8-D2ECC92F3148}', # do NOT edit this line
        ControlGUID   => '{726CBD22-C14E-4E74-8558-E0C9F0692CF9}', # do NOT edit this line either
        DispInterfaceIID=> '{215A95FA-641A-49D4-9443-F78E691934B0}', # or this one
        ControlName   => 'MyObject',
        ControlVer      => 1,# increment if new object with same ProgID
                             # create new GUIDs as well
        ProgID          => 'MyApp.MyObject',
      LCID            => 0,
        DefaultMethod   => 'MyMethodName1',
        Methods         => {
          MyMethodName1 => {
                DocString         => "The MyMethodName1 method",
                HelpContext         => 101,

                DispID            =>0,
                RetType             =>VT_I4,
                TotalParams         =>5,
                NumOptionalParams   =>2,
                ParamList         =>[ ParamName1 => VT_I4,
                                        ParamName2 => VT_BSTR,
                                        ParamName3 => VT_BOOL,
                                        ParamName4 => VT_I4,
                                        ParamName5 => VT_UI1 ],
          },
          MyMethodName2 => {
                DocString         => "The MyMethodName2 method",
                HelpContext         => 102,

                DispID            =>1,
                RetType             =>VT_I4,
                TotalParams         =>2,
                NumOptionalParams   =>0,
                ParamList         =>[ ParamName1 => VT_I4,
                                        ParamName2 => VT_BSTR ],
          },
        },# end of 'Methods'
        Properties      => {
          MyIntegerProp => {
                DocString         => "The MyIntegerProp property",
                HelpContext       => 201,

                DispID            => 2,
                Type            => VT_I4,
                ReadOnly          => 0,
          },
          MyStringProp => {
                DocString         => "The MyStringProp property",
                HelpContext       => 202,

                DispID            => 3,
                Type            => VT_BSTR,
                ReadOnly          => 0,
          },
          Color => {
                DocString         => "The Color property",
                HelpContext       => 203,

                DispID            => 4,
                Type            => VT_BSTR,
                ReadOnly          => 0,
          },
          MyReadOnlyIntegerProp => {
                DocString         => "The MyReadOnlyIntegerProp property",
                HelpContext       => 204,

                DispID            => 5,
                Type            => VT_I4,
                ReadOnly          => 1,
          },
        },# end of 'Properties'
    );# end of %TypeLib

=end PerlCtrl

=cut

接下来让我们简化一下template.pl,让它只包含我们需要的部分:=pod

=begin PerlCtrl

    %TypeLib = (
        PackageName   => 'MyPackage::MyName',
      DocString       => 'My very own control',
      HelpFileName    => 'MyControl.chm',
      HelpContext   => 1,
        TypeLibGUID   => '{EFF666F8-8423-474F-81E8-D2ECC92F3148}', # do NOT edit this line
        ControlGUID   => '{726CBD22-C14E-4E74-8558-E0C9F0692CF9}', # do NOT edit this line either
        DispInterfaceIID=> '{215A95FA-641A-49D4-9443-F78E691934B0}', # or this one
        ControlName   => 'MyObject',
        ControlVer      => 1,# increment if new object with same ProgID
                             # create new GUIDs as well
        ProgID          => 'MyApp.MyObject',
      LCID            => 0,
        DefaultMethod   => 'MyMethodName1',
        Methods         => {
          MyMethodName1 => {
                DocString         => "The MyMethodName1 method",
                HelpContext         => 101,

                DispID            =>0,
                RetType             =>VT_I4,
                TotalParams         =>5,
                NumOptionalParams   =>2,
                ParamList         =>[ ParamName1 => VT_I4,
                                        ParamName2 => VT_BSTR,
                                        ParamName3 => VT_BOOL,
                                        ParamName4 => VT_I4,
                                        ParamName5 => VT_UI1 ],
          },
        },# end of 'Methods'
        Properties      => {
        },# end of 'Properties'
    );# end of %TypeLib

=end PerlCtrl

=cut

template.pl里包含了什么呢?它其实是提供了供perlctrl生成.dll的必要信息,他会告诉perlctrl我的progID是什么,我的模块名字是什么,我向外界暴露了哪些方法,这些方法的传入参数和传出参数是什么类型,我向外界暴露了哪些属性,等等。

接下来,我们要把gtpl.pl和template.pl合并起来,这样,perlctrl在执行的时候才知道究竟.dll里面要装些什么内容。第一步,我们把gtpl.pl的内容完全复制到template.pl的最前面部分,让我们把这个新的代码叫做gtplcom.pl。接下来,我们就需要提到perl的package概念。package修饰字表示这是一个perl的模块,如果你对perl有稍许了解的话,也许你曾经听说过.pm文件,这是从perl5.6起引入的perl的模块化编程概念,它通过package修饰字将perl程序变成一个一个的模块,从而实现单独功能的封装。好了,那么首先,我们要让我们的gtpl.pl变成一个package。实现起来非常简单,在合并后的gtplcom.pl的第一行加入packge genroute;就可以啦,是不是非常简单呢?当然,你可以把genroute改成任何你喜欢的名字,但是可不要用中文啊。

package也就是perl的模块里面的程序都是以子程序的方式存在的,所以下一步,我们要把gtpl.pl中原来的主程序部分变成子程序的模样。这就是我们的genroute子程序的模样啦。在子程序的最后一步,当然要记得返回我们计算好的路径。在一步中,唯一需要注意的就是全局变量和局部变量的处理,全局变量的定义要放在最外面,这样就可以了。sub genroute
{
        $src_area = shift;
        $dest_area = shift;
        $debug_enable = 0;
       
$degree = 0;
        $debug_enable = shift;

        my $ret_val =0 ;
        # route_list is re-use in generate_path and search_path function
        @rout_list=();
        @rout_list_from=();
       
        @areaopen_list=();
        @areaopen_list_from=();
        @areaopen_list_prev=($src_area);
        @areaclose_list =($src_area);
       
        $degree = 1;

        generate_hash();
       
       
        # input parameter check
        ($ret_val,$src_area) = check_area_valid($src_area);
        if ($ret_val > 0)
        {
                print "start area $src_area illegal\n";
          exit;       
        }
       
        ($ret_val,$dest_area) = check_area_valid($dest_area);
        if ($ret_val > 0)
        {
                print "dest area $dest_area illegal\n";
          exit;       
        }
       
        search_path();
        generate_path();
        return($route_list);
}

代码的修改已经完成了,我们是不是可以试试可不可以生成.dll了呢?嗯,别急,还记得最早的时候在template.pl中的那些让perlctrl识别的程序片断么?我们可得修改一下它们让它们为gtplcom.pl服务,不然perlctrl可不知道它们是给gtplcom.pl用的呢。我把解释用中文和下面的代码写在一起了,请移步往下看。

=pod

=begin PerlCtrl

    %TypeLib = (
      PackageName   => 'genroute',#修改名字和上面package修饰字取的一样

        # DO NOT edit the next 3 lines.
        TypeLibGUID   => '{62C50D29-FF11-4796-80AC-8180E38D3313}', # do NOT edit this line
        ControlGUID   => '{EA2C7471-311F-42C9-BB87-087E895BD952}', # do NOT edit this line either
        DispInterfaceIID=> '{E35C3806-100F-4C60-AAF2-EC8288921BA7}', # or this one
      ControlName   => 'genroute',#还是一样的名字
      ControlVer      => 1,#如果你在原有的.dll中增加了新的method,property然后更新了.dll,那么就把版本号加1
      ProgID          => 'goto.genroute',#这是最重要的一个名字啦,取个好记的名字,因为你在zmud中调用.dll的时候要用到它,它是.dll和.dll调用者之间的脐带
      DefaultMethod   => '',#这个默认的method在你创建com对象的时候会直接执行,用来初始化一些变量是最不错的了,不过在这里我们不使用它

      Methods         => {#方法是最重要的了,为了接收数据和传回数据,我们需要好好的声明我们的方法
            'genroute' => {#这是方法的名字,也是在zmud中调用的时候会使用到的
                  RetType             =>VT_BSTR,#返回值的类型
                  TotalParams         =>3,#传入参数的个数
                  NumOptionalParams   =>1,#有多少个可选的参数
                  ParamList         =>['src' => VT_BSTR,#第一个传入参数的名字和类型
                                           'dest' => VT_BSTR,#第二个传入参数的名字和类型
                                           'debug_en' => VT_BOOL, #第三个传入参数的名字和类型]
                },
            },# end of 'Methods'

      Properties      => {
            }
          ,# end of 'Properties'
      );# end of %TypeLib

=end PerlCtrl

=cut

至此为止,所有代码修改工作都结束啦。激动人心的时刻到来了,让我们来看看我们.dll是不是能够如期生成。在命令行键入perlctrl gtplcom.pl,运行完毕后,gtplcom.dll被生成在与gtplcom.pl同一目录下。
可是这个.dll它是个二进制文件诶,我怎么知道它好不好用!别急,让我们先来注册它。使用regsvr32.exe gtplcom.dll就可以直接注册一个.dll了。一旦注册成功,就会有个欢乐的对话框弹出来告诉你成功啦。



这里提一下.dll的注册相关的内容。由于我安装的activelperl和perl dev kit都是32位的,所以生成的.dll是32位的,而我的机器是64位的,因此,使用的regsvr32的路径有所不同:
在64位机下,请使用c:\windows\sysWOW64\regsvr32.exe去注册32位的.dll,如果你的机器是32位机,那直接在命令行里键入regsvr32.exe gtplcom.dll即可。因为windows默认的regsvr32是路径是与32/64对应的。

注册.dll成功之后,就可以在zmud使用它了。为了将它与逍遥行无缝结合,我们把gt alias中routecreate处替换成:
            #VAR gtpl_route %comcreate( "goto.genroute")
            #var Route_list @gtpl_route.genroute(@CurrentPosition,%1,0)
            #t+ goto/walk
            step=1       
            stepmax=%numitems( @Route_list)
            stepaccu=1
            #if (@walkmode=1) {set brief 1}
            walk
现在,享受秒速的逍遥行吧。



附件:
gtplcom.pl 源码供参考
gtplcom.dll .dll供直接使用
perl516.dll 我没在没有安装perl的机器上测试过,如果你的机器上没安装perl,请将这个.dll和zmud.exe放在同一目录下
gt.txt 修改过的gt alias,直接覆盖逍遥行原gt alias即可

使用方法:
把gtplcom.dll和perl516.dll和zmud.exe放在同一目录下。注册gtplcom.dll。
在原来的逍遥行中删除原有gt alias导入gt.txt。





inkflower 发表于 2013-10-13 10:53:02

本帖最后由 inkflower 于 2013-10-14 05:43 PM 编辑

三、抛砖引玉

其实如果熟悉js或者vbs的话,可以直接在zmud中用js,vbs重写routecreate,保证事半功倍,由于只会用perl,最后使用这样的曲折的法子,实在是事倍功半的路子
另外,alucar在zmud462版中貌似对寻径算法有一定优化,不过貌似没看到在zmud721中更新,嘻嘻,求更新

当前这个perl的com还有许多可以优化的地方,比如数据和程序的分离,寻径的优化,个人没什么时间做,感兴趣的玩家可以继续研究,欢迎各种交流。

在zmud中使用COM外挂程序去解决一些复杂问题是复杂机器人必经之路,目前觉得除了fullme破解有这个必要外,其他的还没发现哪个功能有这个必要。

其实工具什么的都是次要的,关键是解决问题的思路。而对于mud的机器人制作来说,了解游戏,了解你所使用的客户端,找到解决问题的思路都是写一个好机器人不可或缺的。这里有很多牛人出没,期待各种新鲜的思路碰撞火花~

游戏的最大用处还是for fun,祝大家玩得愉快。

inkflower 发表于 2013-10-13 10:55:56

发完贴占完楼就后悔了,可以直接贴代码么?

写贴子要浪费好多口水啊,稍安勿躁,等我去喝点水,做个饭,出去溜达一圈再回来继续写啊~~~

nol 发表于 2013-10-13 12:33:01

搬个板凳慢慢看

shuigui 发表于 2013-10-13 12:40:15

黑花花也是技术宅?

when 发表于 2013-10-13 12:42:48

ttk_00高端货啊

hijacker 发表于 2013-10-13 12:54:49

黑花花也是技术宅?
shuigui 发表于 2013-10-13 12:40 PM http://pkuxkx.net/forum/images/common/back.gif


    同感震惊!占个前排等更新

pinkagaric 发表于 2013-10-13 13:02:03

强硬插入,名帖前排留名~~
页: [1] 2 3 4 5 6
查看完整版本: 使用perl和COM技术加快zmud721逍遥行计算速度