北大侠客行MUD论坛

 找回密码
 注册
搜索
热搜: 新手 wiki 升级
查看: 1751|回复: 10

mushclient - 基于wait编程的机器人

[复制链接]
发表于 2023-4-4 06:02:26 | 显示全部楼层 |阅读模式
本帖最后由 xfox 于 2023-4-4 07:04 AM 编辑

最早开始用mushclient写机器人是用的javascript,当时就听说lua的wait很厉害,但是由于当时我用javascript已经写了好多行代码了,而且也用javascript"完美的解决"了mushclient没有wait的问题,所以就拒绝学习lua,直到最近重玩研究过lua后才发现当时是多么的无知,lua是多么的强大。今天发一点心得供大家参考吧。大牛们请绕道~

最重要的心得就是利用wait来实现同步模式的机器人,而如今大量的机器人都是基于触发器,定时器来实现,根据我的理解,这些都是异步模式。因为人脑的思维是偏向于同步模式的,所以维护一个大型的异步模式的机器人会非常困难,例如你很快就会忘了哪个触发器在什么时候需要关闭,当然你可以实时的创建触发器和定时器,但是代码本质上还是异步的,你无法把触发前的code和触发后的code放到一起。

如今有了wait,那么绝大部分的触发器就都可以说拜拜了,关键是代码流程非常清晰,可读性很高,下面我会用几个例子来演示一下。


北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2023-4-4 06:03:41 | 显示全部楼层
本帖最后由 xfox 于 2023-4-4 08:41 AM 编辑

上文提到了大量的触发器会说拜拜,大家可能会问:没有触发器能做机器人吗?答案是,能,非常能。
首先要解释一下触发器的原理,mushclient收到服务器端发过来的文本,第一件事情就是去检查所有的触发器(包括插件),让这些文本的每一行都去匹配那些触发器,如果你处于激活状态的触发器非常多,那么可想而知,你的系统负担也会非常重,使用多行触发会导致更大的开销。机器人高手会只在必要的时候激活触发器,但是这也是需要维护成本,调试的工作量也是巨大。

如果不使用触发器,那我们怎么写机器人?答案是,自己写触发器

听起来好像有点过于玄乎了,其实很简单,原理就是当你需要一个触发器的时候,你开一个定时器,不停的去查询输出窗口的最后几行文本,看是否与你的正则表达式匹配,这个定时器受到mushclient本身的限制,最小精度是0.1秒,所以这篇文章的前提是你对触发器灵敏度要求不能太高,如果想要在0.1秒之内就被触发的话,您请绕行~

这样做的开销看上去很大,其实非常小,尤其是在大型机器人中,你省掉了很多正则表达式匹配的开销。下面上个具体的例子:

  1. -- cmd (string): the cmd will be sent to pkuxkx
    -- regex (table): a collection of regex, when any line matches any regex, return
    -- timeout (number): timeout
    function PkuxkxWaitForNextMatchingLine(cmd, regex, timeout)
        timeout = timeout or 3
        world.Note("WaitForNextMatchingLine - never comment this line out, we need an output to eliminate previous partial line")
        local startLineCount = world.GetLineCount()
        local deadline = os.time() + timeout
        Info("Executing "..cmd)
        world.Send(cmd)
        repeat
            local endIndex = world.GetLinesInBufferCount()
            local lineCount = world.GetLineCount() - startLineCount
            local startIndex = endIndex - lineCount
            for i = startIndex, endIndex do
                local line = world.GetLineInfo(i, 1)
                for k, v in ipairs(regex) do
                    if string.match(line, v) then
                        return startIndex, i, k
                    end
                end
            end
            wait.time(0.1)
        until os.time() > deadline
    end


  • 复制代码



    解释一下:
    1. 这个函数上来首先读取输出窗口有多少行,从这行以后开始,所有的输出都需要跟我们预期的正则表达式匹配
    2. 向mud发出一条命令
    3. 循环查询输出窗口,一旦发现有匹配的行出现,立即返回 (行号,以及匹配到的正则表达式的索引),直至超时

    由于输出窗口有个缓冲区的概念,缓冲区是有大小的,例如5000行,而你的真实行号却是自从这个窗口打开算起,可能早已超过10万行,也就是说不是所有的输出都在缓冲区里,太早的内容早就已经被抹掉了,但是这并不影响我们,因为我们关心的是缓冲区的底部。正是因为这个缓冲区的存在,所以我们调用mush API去读取指定行号的时候需要做点小算术,具体参见code,不多解释了

    北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
     楼主| 发表于 2023-4-4 06:04:14 | 显示全部楼层
    本帖最后由 xfox 于 2023-4-4 06:36 AM 编辑

    大家看到上面的例子可能还没太明白有什么用,下面上个有真正用处的例子:
    1. function Sync()
          local pattern = "系统回馈:sync = 0"
          local line1, line2, index = PkuxkxWaitForNextMatchingLine("response sync", {pattern})
          print(line1, line2, index)
      end
      function PkuxkxTest()
          wait.make(function()
              Sync()
          end)
      end

    复制代码

    大家看完是不是一脸懵逼?就这有啥用?

    其实这个Sync()函数是大大的有用,很多机器人现在被限流就是因为没有很好的跟服务器同步,这个Sync就是用来同步的。

    大家看到我这里可没有使用mushclient自带的触发器。

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?注册

    x
    北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
     楼主| 发表于 2023-4-4 06:04:53 | 显示全部楼层
    本帖最后由 xfox 于 2023-4-4 07:06 AM 编辑

    有人可能会说了,你这样一次只能匹配一个触发器,要是我需要匹配多个触发器怎么办?那么我们再来一个例子看看,同时大家可以看到同步模式的威力:

    1. function PkuxkxTest()
          wait.make(function()
              local patterns = {
                  "你捡起一根烤鸡腿。",
                  "你附近没有这样东西。"
              }
              local _, _, index = PkuxkxWaitForNextMatchingLine("get jitui", patterns)
              if index == 1 then
                  world.Send("do 3 eat jitui")
              else
                  world.Send("chat 求哪位大大给点买鸡腿的钱吧~")
              end
          end)
      end
    复制代码


    WaitForNextMatchingLine - never comment this line out, we need an output to eliminate previous partial line
    Executing get jitui
    get jitui
    你附近没有这样东西。
    > say 求哪位大大给点买鸡腿的钱吧~
    你说道:「求哪位大大给点买鸡腿的钱吧~」
    > drop jitui
    你丢下一根烤鸡腿。
    >
    WaitForNextMatchingLine - never comment this line out, we need an output to eliminate previous partial line
    Executing get jitui
    get jitui
    你捡起一根烤鸡腿。
    > do 3 eat jitui
    你拿起烤鸡腿咬了几口。
    你拿起烤鸡腿咬了几口。
    你拿起烤鸡腿咬了几口。
    >


    北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
     楼主| 发表于 2023-4-4 06:51:41 | 显示全部楼层
    本帖最后由 xfox 于 2023-4-4 06:54 AM 编辑

    下面是一个看雪涨轻功的机器人,仅仅使用了一条别名,流程是不是很清晰?


    1. __KANXUE = {}
      function OnAliasKanXue(param)
          wait.make(function()
              if param == "stop" then
                  __KANXUE.running = false;
                  Info("stopping __KANXUE")
              else
                  __KANXUE.running = true;
                  repeat
                      local readyForXingzou = false
                      repeat
                          local pattern = {
                              "你突然发现在路旁的一片积雪上行走%(xingzou%)似乎可以用来练习轻功。",
                              "突然间,你被积雪闪耀着的刺眼的光芒灼伤,只觉头痛欲裂,眼前什么也看不到了!",
                              "雪,雪,还是雪。",
                              "洁白的雪地上零星的散落着几朵脚印。",
                              "环顾四周,到处都是白皑皑的积雪。"
                          }
                          local index, result = WaitForMultipleOutputV2("l snow", pattern, pattern)  
                          if index == 1 then
                              readyForXingzou = true
                          elseif index == 2 then
                              Error("retry in 3 seconds")
                              wait.time(3)
                          end
                      until readyForXingzou or __KANXUE.running == false
                     
                      local pattern = {
                          "你一路走下来,看着脚印回想方才的步法,轻功水平提高了!",
                          "你摇摇摆摆走在雪地上,忽然脚底一滑,“扑通”摔在了雪堆里,骨碌碌滚了五、六步才停下。"
                      }
                      local index, result = WaitForMultipleOutputV2("xingzou snow", pattern, pattern, 10)
                      if index == 2 then
                          wait.time(3)
                      end
                  until __KANXUE.running == false
                  Info("Done")
              end
          end)
      end
    复制代码


    北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
     楼主| 发表于 2023-4-4 06:57:19 | 显示全部楼层
    原以为可以写很多的,发现没有必要继续写了,如果反响不错的话我会继续加一些内容,目前看来就这样吧,欢迎批评指正交流
    北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
    发表于 2023-4-4 07:37:01 | 显示全部楼层
    哇哦,太酷了
    北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
    发表于 2023-4-21 11:54:17 | 显示全部楼层
    大神啊,膜拜了
    北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
    发表于 2023-4-21 12:34:13 | 显示全部楼层
    用js的话需要了解下promise

    然后再按那个方向自己写一下。
    北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
    发表于 2023-4-28 16:33:52 | 显示全部楼层
    大神,等待更新哈。
    北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
    您需要登录后才可以回帖 登录 | 注册

    本版积分规则

    Archiver|手机版|小黑屋|北大侠客行MUD ( 京ICP备16065414号-1 )

    GMT+8, 2024-4-28 09:07 PM , Processed in 0.012039 second(s), 15 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2020, Tencent Cloud.

    快速回复 返回顶部 返回列表