北大侠客行MUD论坛

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

mudlet基础脚本开发-6、武当门派任务 - 诵经(念经)

[复制链接]
发表于 2022-9-29 18:29:29 | 显示全部楼层 |阅读模式
本帖最后由 shanghua 于 2022-9-30 09:44 AM 编辑

上一节,我们开发了炼丹的机器,这一节我们继续填架子。


这个教程里的机器,不过多考虑性能问题,以教会新手开发思路和基础语法为目的,我尽量把每一步的执行过程都写清楚。

念经的机器对于新手来说,有一点点的小难度,但是也不用怕,把这教程看明白了,你发现其实也没多难。

念经验机器的难点有两处:
    难点一:路径问题,要能去能回。念经的路径需要自己整理,这里只做举3个例子。wiki里提供了一些常用路径。路径的录制可以用 walk add 命令,不会的请 help walk。北侠自带的路径录制命令,反正我感觉相当好用。
    难点二:抓取经书内容,正则匹配很简单,关键在于怎么把内容存起来,对lua语法不熟悉的话,确实有点麻烦。

还是老样子,找之前的地方新建一个sj.lua文件,和前几节的文件放到一起,用vscode打开它,以下所有的脚本代码全都复制到这个文件里,最后把文件里的代码一起复制到mudlet的脚本里去。。

先来梳理开发思路,然后再来逐个分析每一步的实现过程:
1、先更新 jobSwitch.lua 脚本
2、接任务并记录相关内容
3、去复真观借经书
4、借到了,回到冲虚处,判断本次任务是否需要下山
    4-1、不需要下山的用单向路径直接跑过去念经
    4-2、需要下山的准备往返双向路径,确保自己能安全抵达并返回。
5、到地方之后,获取经书籍信息,借用了 Zmud462武当新手任务念经机器人的制作 提供的经书数据,表示感谢。
6、念完还书
7、交任务


1、更新 jobSwitch.lua 脚本
-- 武当路径
paths = {
    wd = {
        -- 广场 -- 后院
        wdgc_hy = 'n;n;n;n',
        -- 紫霄宫 -- 太子岩
        zxg_tzy='eu;e;e;e;eu;eu;eu;e;e;e;eu',
        -- 太子岩 -- 紫霄宫
        tzy_zxg = 'wd;w;w;w;wd;wd;wd;w;w;w;wd',
        -- 太子岩 -- 雷神洞
        tzy_lsd = 'eu;eu;ne;nu;nw;nu;ne;eu;se;u;nu;eu;su;wu;u;eu',
        -- 雷神洞 -- 太子岩
        lsd_tzy = 'wd;d;ed;nd;wd;sd;d;nw;wd;sw;sd;se;sd;sw;wd;wd'
    }
}

function jobSwitch(name)
    disableTrigger('找老宋下山')
    disableTrigger('武当山门')
    disableTrigger('诵经地点')
    disableTrigger('诵经')
    disableTrigger('炼丹')
    disableTrigger('侠客')
    disableTrigger('练阵')
    disableTrigger('XK-土匪挡道')
    disableTrigger('XK-土匪死了')

    disableScript('诵经脚本')
    disableScript('炼丹脚本')
    disableScript('侠客脚本')
    disableScript('练阵脚本')

    disableTimer('xkTimer')

    xk = nil
    cj = nil
    zf = nil

    if name~=nil then
        enableTrigger(name)
        enableScript(name..'脚本')
    end
end

我们把要用到的路径全都做成变量,这样的好处是别名也可以用,程序里也可以用。

开始之前先设置别名,要不然机器跑不动的(我的教程全都不提供xml文件,自己动手实现才有意义):
借经书 ^wd_jjs$ nu;n;n;n;w;w;u
下山 ^xiasha$:tempTimer(2, [[send('ask song about 下山')]])
任务完成 ^cxok (.+)$ send('ask chongxu about success')
紫霄宫-太子岩 ^wd_zxg_tzy$ send(paths.wd.zxg_tzy)
太子岩-紫霄宫 ^wd_tzy_zxg$ send(paths.wd.tzy_zxg)
太子岩-雷神洞 ^wd_tzy_lsd$ send(paths.wd.tzy_lsd)
雷神洞-太子岩 ^wd_lsd_tzy$ send(paths.wd.lsd_tzy)


sj.lua脚本里定义两个变量,存目的地
-- 需要下山的地点
local areasD = {'太子岩','雷神洞'}
-- 山上的地点
local areasI = {'后院'}


2、接任务还是通过 这一节教程 接到任务,去执行 startSJ() 函数,脚本代码:
function startSJ (a,b,c,p)
    sj = {
        -- 地点
        area = a,
        -- 书名
        book = b,
        -- 章节
        chapter = c,
        -- 章节页数
        page = tonumber(p),
        -- 记录经书内容
        content = '',
        -- true是借书,false是还书
        go = true,
        -- 抓取章节内容时用来记录内容行数
        i = 1,
        -- 念经的路径,方便返回, 为-1时表示已下山
        path = ''
    }
    print('\n地点:' ..sj.area)
    print('书名:' ..sj.book)
    print('章节:' ..sj.chapter)
    print('页数:' ..sj.page)
    expandAlias('wd_jjs', false)
end


3、去复真观借经书
触发:
  1. ^\s+复真观二层\s+$
  2. ^\s+〓\s+$
  3. ^\s+复真观一层\s+
复制代码

触发代码:
  1. jieSJ()
复制代码
脚本代码:
function jieSJ ()
    -- sj.go 是控制借书还书的: 默认值为 true 执行借书,念经完成之后设为 false 执行还书
    if (sj.go) then
        tempTimer(1, [[
            send('jie '..sj.book)
            feedTriggers('\n我来借书\n')
)
    else
        tempTimer(1, [[send('give zhike jing')]])
    end
end


4、借到了,回到冲虚处,准备跑路
触发:
  1. 我来借书
  2. 知客道长递给你一本.+并对你说
复制代码

借书的描述所有人都一样,如果只做一行触发的话,别人借书的话,也会执行程序。所以这里自己多加了一行触发。
触发脚本:
  1. readySJ()
复制代码
脚本代码:
function readySJ()
    send('d;e;e;do 3 s')

    if getAreasD() then
        send('n')
        enableTrigger('找老宋下山')
        expandAlias('xiashan', false)
        send('set brief 3')
    elseif getAreasI() then
        goingSJ();
    else
        cecho('------------------------------------------\n| 念经点不在现有的路径内,请补全路径信息 |\n------------------------------------------\n')
    end
end


函数 getAreasD :
-- 获取山下地点
function getAreasD()
    for i, value in ipairs(areasD) do
        if (sj.area == value) then
            sj.path = -1
            return value;
        end
    end
    return false
end


函数 getAreasI :
-- 获取山上地点
function getAreasI()
    for i, value in ipairs(areasI) do
        if (sj.area == value) then
            return value;
        end
    end
    return false
end


4-1、从上边的逻辑中能判断出是否需要下山,我们先处理要下山的情况。
要下山?那么问题就来了。怎么样精准定位房间?其实常用的也就那么几种方法:
1、简单粗爆的,直接跑过去。缺点:路程太远的话,命令太长,不知道会发生什么,导致脚本停止运行。
2、抓取房间信息,然后跟我们事先保存的 sj.area 变量对比一下,能对的上就是到了。缺点:每走一个房间都会去匹配一下,效率可能受影响
3、通过lua去循环路径,每一次循环就发出一条行走命令,循环到最后两条命令的时候,打开抓取房间的触发。优点:比第2种方式更安全稳定,效率也高,缺点:开发成本高那么一点点,新手不好理解。

这里对于不下山的路径用第2种方式,下山的路径用第3种方式。

触发:
  1. 你向宋远桥打听有关『下山』的消息。
  2. 宋远桥点了点头。
复制代码
多行触发,delta设为1触发脚本:
  1. downHillBase()
复制代码
脚本代码:
function downHillBase()
    if (sj.go or xk.go) then
        tempTimer(1, [[
            send('s;sd;')
            loopPath()
            disableTrigger('找老宋下山')
)
    end
end


函数 loopPath:
function loopPath()
    local path = '';
    if (sj.area == '太子岩') then
        -- split:把字符串分割成数组。 这是百度上找的一个通用的轮子
        path = split(paths.wd.zxg_tzy, ';')
    elseif (sj.area == '雷神洞') then
        -- paths.wd 是 jobswitch 脚本中定义的变量
        path = split(paths.wd.zxg_tzy..';'..paths.wd.tzy_lsd, ';')
    end

    -- 获取总数
    local count = table.size(path);
    -- 记录时间点
    local last = 2;
    for i, value in ipairs(path) do
        -- 倒数第2步,打开触发,开始记数
        if (i > count-2) then
            -- 打开房间抓起触发器
            enableTrigger('抓取房间');
            tempTimer(last, function ()
                send(value)
            end)
            last = last + 1.5
        else
            send(value);
        end
    end

    send('set brief 0')
end




轮子函数 split(别人写好的工具类或函数,行业内称为轮子,直接拿来用就好了,没必要自己再造个轮子。):
-- 返回一个 table,类似于其他弱类型语言的数组
function split(str,delimiter)
    local dLen = string.len(delimiter)

      local newDeli = ''
      for i=1,dLen,1 do
          newDeli = newDeli .. "["..string.sub(delimiter,i,i).."]"
      end

      local locaStart,locaEnd = string.find(str,newDeli)
      local arr = {}
      local n = 1
      while locaStart ~= nil
      do
          if locaStart>0 then
              arr[n] = string.sub(str,1,locaStart-1)
              n = n + 1
          end

          str = string.sub(str,locaEnd+1,string.len(str))
          locaStart,locaEnd = string.find(str,newDeli)
      end
      if str ~= nil then
          arr[n] = str
      end
      return arr
end



split是个工具函数,在mudlet的脚本栏目里,新建一个 "公共函数” 组,然后新建一个“字符串分隔”的脚本,把代码扔进去:




函数 loopPath 里打开了房间抓取的触发,触发器:
  1. ^(.+)\s\-\s\[(.*)\]
复制代码
触发脚本(下边这1段代码直接写在mudlet里,就不上图了,都学到这了,应该能知道怎么添加。):
if sj.path and sj.path~=-1 and matches[2]=='武当广场' then
    -- 山上,去还书
    expandAlias('wd_jjs')
    disableTrigger('抓取房间')
else
    -- 快到了,抓取房间
    getRoomSJ(matches[2])
end




脚本函数 :
-- 房间匹配,判断是否到了
function getRoomSJ(r)
    if r==sj.area then
        -- 找到目的地,关闭触发,获取经书内容
        disableTrigger('抓取房间');
        getPageSJ()
    end
end







4-2、不用下山的情况。这种情况一般路程都很近,我们直接跑过去就行了,所以我们用mudlet自带的 speedwalk 函数来做路径反转,就能实现1条路径走个来回。

在拿到经书后,程序判断我们不需要下山,执行 goingSJ 函数:
function goingSJ()
    if sj.area=='后院' then
        -- speedwalk(路径,是否反转,每一步的间隔时间)
        speedwalk(paths.wd.wdgc_hy, false, 0.1)
        -- 记录路径,返回时用到
        sj.path = paths.wd.wdgc_hy
        tempTimer(2, function ()
            getPageSJ()
        end)
    end
end



两种跑路情况,都已经完成了,现在我们已经到目地的了,开始准备念经了。

5、到地方之后,获取经书籍信息
函数 getPageSJ:
function getPageSJ()
    print('\n=== 获取页码 ===\n')
    -- 获取页码
    if (sj.book=='庄子·外篇「上卷」') then
        if (sj.chapter=='骈拇') then sj.currentPage = sj.page+0 end
        if (sj.chapter=='马蹄') then sj.currentPage = sj.page+3 end
        if (sj.chapter=='胠箧') then sj.currentPage = sj.page+11 end
        if (sj.chapter=='在宥') then sj.currentPage = sj.page+30 end
        if (sj.chapter=='天地') then sj.currentPage = sj.page+65 end
        if (sj.chapter=='天道') then sj.currentPage = sj.page+115 end
        if (sj.chapter=='天运') then sj.currentPage = sj.page+150 end
        if (sj.chapter=='刻意') then sj.currentPage = sj.page+189 end
        if (sj.chapter=='缮性') then sj.currentPage = sj.page+200 end
    end
    if (sj.book=='庄子·外篇「下卷」') then
        if (sj.chapter=='秋水') then sj.currentPage = sj.page+0 end
        if (sj.chapter=='至乐') then sj.currentPage = sj.page+50 end
        if (sj.chapter=='达生') then sj.currentPage = sj.page+68 end
        if (sj.chapter=='山木') then sj.currentPage = sj.page+106 end
        if (sj.chapter=='田子方') then sj.currentPage = sj.page+142 end
        if (sj.chapter=='知北游') then sj.currentPage = sj.page+176 end
    end
    if (sj.book=='庄子·内篇「上卷」') then
        if (sj.chapter=='逍遥游') then sj.currentPage = sj.page+0 end
        if (sj.chapter=='齐物论') then sj.currentPage = sj.page+20 end
        if (sj.chapter=='养生主') then sj.currentPage = sj.page+68 end
    end
    if (sj.book=='庄子·内篇「下卷」') then
        if (sj.chapter=='人间世') then sj.currentPage = sj.page+0 end
        if (sj.chapter=='德充符') then sj.currentPage = sj.page+45 end
        if (sj.chapter=='大宗师') then sj.currentPage = sj.page+76 end
        if (sj.chapter=='应帝王') then sj.currentPage = sj.page+123 end
    end
    if (sj.book=='庄子·杂篇「上卷」') then
        if (sj.chapter=='庚桑楚') then sj.currentPage = sj.page+0 end
        if (sj.chapter=='徐无鬼') then sj.currentPage = sj.page+41 end
        if (sj.chapter=='则阳') then sj.currentPage = sj.page+97 end
        if (sj.chapter=='外物') then sj.currentPage = sj.page+140 end
        if (sj.chapter=='寓言') then sj.currentPage = sj.page+167 end
    end
    if (sj.book=='庄子·杂篇「下卷」') then
        if (sj.chapter=='让王') then sj.currentPage = sj.page+0 end
        if (sj.chapter=='盗跖') then sj.currentPage = sj.page+46 end
        if (sj.chapter=='说剑') then sj.currentPage = sj.page+98 end
        if (sj.chapter=='渔父') then sj.currentPage = sj.page+113 end
        if (sj.chapter=='列御寇') then sj.currentPage = sj.page+138 end
        if (sj.chapter=='天下') then sj.currentPage = sj.page+164 end
    end
    if (sj.book=='道德经「上卷」') then sj.currentPage = sj.page end
    if (sj.book=='道德经「下卷」') then sj.currentPage = sj.page-40 end
    if (sj.book=='阴符经') then sj.currentPage = sj.page end

    -- 翻页
    send('page '..sj.currentPage);


    -- 3秒倒计时,拼接内容
    cecho('\n==== 准备念经 ====\n')
    tempTimer(3, [[cecho('\n==== 内容整理中... 1 ====\n')]])
    tempTimer(2, [[cecho('\n==== 内容整理中... 2 ====\n')]])
    tempTimer(1, [[cecho('\n==== 内容整理中... 3 ====\n')]])

    -- 念经

    tempTimer(4, [[songSJ()]])
end



上边代码,直接确定了页数,开始抓内容吧。

触发:
  1. ^==\s+(\S+)
复制代码
触发脚本:
  1. -- 抓取每行的内容
  2. getContentSJ(multimatches[1][2])
复制代码
获取经书内容,用的是单行触发。
因为每一行的正则模式都是一样的,所以每一行都会被触发,我们要做的事情是 依次 把每一次匹配到的内容记录下来。
注意:是依次,因为有几行内容,就会执行几次触发。而不是执行一次触发匹配所有行数。刚开始有不少人这里都理解不了,也包括我自己 0.0。
脚本代码:
function getContentSJ(c)
    -- 抓取每行的内容
    sj['contents'..sj.i] = c
    sj.i = sj.i+1
end



页数、内容都有了,开始念吧。

函数 songSJ :
function songSJ()
    -- 拼装经文
    for i = 1, sj.i-1 do
        sj.content = sj.content..sj['contents'..i]
    end

    send('chanting '..sj.currentPage..' '..sj.content)
end



6、念完还书
触发:
  1. ^你诵唱完了\S{2,5}
复制代码
触发脚本:
  1. print('\n结束-回城-还书')
  2. bookReturnSJ()
复制代码
脚本代码:
function bookReturnSJ()
    sj.go = false
    if (sj.path==-1) then
        -- 回山,还书
        send('set brief 3')
        tempTimer(2, function ()
            if (sj.area == '太子岩') then
                expandAlias('wd_tzy_zxg', false)
            elseif (sj.area == '雷神洞') then
                expandAlias('wd_lsd_tzy', false)
                expandAlias('wd_tzy_zxg', false)
            end
            send('nu')
            send('set brief 0')
            expandAlias('wd_jjs', false)
        end)
    else
        -- 未下山,回广场


        enableTrigger('抓取房间')
        speedwalk(sj.path, true, 0.3)
    end
end

这里打开了房间抓取的触发。因为 speedwalk 这个函数,用我能理解的表达方式,我只能说它是一个异步函数。异步是个啥意思自己百度。
由于它这种异步的机制,导致写在它下边的代码会优先执行,自己可以做个实验:speedwalk('n;n;n', false, 1) send('e') 看看它是怎么执行的。

未下山的话,走到武当广场,会执行还书的操作。

7、交任务
触发:
  1. ^你给知客道长一本.+
复制代码
触发脚本:
  1. overSJ()
复制代码
脚本代码:
function overSJ()
    sj = nil
    tempTimer(1, [[
        send('d;e;e;do 3 s')
        expandAlias('cxok', false)
)
end



目录结构:







本帖子中包含更多资源

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

x
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
发表于 2023-2-10 21:09:00 | 显示全部楼层
感谢分享,很有用,虽然我是使用mushclient来做这个诵经机器人,但是思路可以借鉴,而且那个经书的目录页数很正确,
北大侠客行Mud(pkuxkx.net),最好的中文Mud游戏!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-3-28 09:51 PM , Processed in 0.012769 second(s), 15 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

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