北大侠客行MUD论坛

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

【Paotin++】入门系列之二: 机器基础

[复制链接]
发表于 2024-8-7 17:09:59 | 显示全部楼层 |阅读模式
本帖最后由 doumimi 于 2024-8-7 05:29 PM 编辑

【Paotin++】入门系列之一: 客户端基础 - 技术园地 - 北大侠客行MUD论坛 - Powered by Discuz! (pkuxkx.com)

上一篇文章主要介绍了PaoTin++客户端的一些基础的、含有特色功能,这一节介绍一下 开发机器的基础。
大家学习写机器,一步一步来,可以先通过简单的机器,来记录任务信息。 逐步升级到能半自动化,比如接了任务,能自动的跑到任务地点。 最后再逐渐完善,到尽量的全自动。 上来就想着全自动,不是一个新手该干的事情,一口气吃不成胖子!


一:PaoTin++目录结构

在安装完pt之后,应该会有两个目录 paotin以及 my-paotin
paotin:这个是客户端的目录,里面的内容千万不要改动。 里面包含了很多代码,比如逍遥行,path.Walk的实现等, 可以用来参考学习。
my-paotin: 这个是用户自己的目录, my-paotin/data放数据,my-paotin/etc放配置, my-paotin/log放日志, my-paotin/plugins 放机器。   example : my-paotin/plugins/xuexi.tin

假如你已经是个高手, 感觉paotin下面的一些内容不符合心意,那么依然千万不要动paotin原本的代码HELP load-module 可以看到机器加载的顺序,以及原理,可以通过覆盖加载的方式来实现你的目的。

二:代码执行顺序

本节内容可以先大致看一眼,有个概念。后面介绍具体内容的时候,还会提到。

1. 用户在控制台上输入的命令,或者机器发送的命令, 先经过客户端处理。
2. 客户端进行命令的解析,别名的替换,白名单检查等工作。
- 解析:比如如果输入 #help,那么这就是一个tintin的客户端命令, 客户端直接处理,不会发送给北侠服务器。如果不是客户端命令,那么认为是用户要发送给服务器的。
- 别名:进行命令的替换。
- 白名单:Paotin++特有的功能,拦截明显异常的命令来大大减少北侠服务器的压力。
3. 客户端发送命令到服务器。
4. 服务器对命令进行处理,如果不识别,返回【什么?】, 如果识别,那么处理后返回结果,比如 移动、背包、kill之类、战斗信息等结果。
5. 结果到达客户端, 客户端进行处理, 用户可以通过action来进行下一步逻辑、 美化(pt框架做了一些)、日志分类(pt框架做了一些)等。



北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-8-7 17:13:17 | 显示全部楼层
本帖最后由 doumimi 于 2024-8-7 05:20 PM 编辑

三:基础语法
Paotin本身是基于tintin的, 只是对tintin的命令封装成了更加易用的功能, 所以推荐大家完整的看一遍Tintin的中文手册,有个基本的概念, 本文只介绍最常用的几个功能,会用这几个功能,就能看懂、开发游戏中至少80%的机器。

#alias 别名
语法: #alias {名称} {命令} {优先级}
用法:  是一个基础的重要功能,逻辑的封装,命令的简化都靠alias了
  1. #alias acd {ask you about 闯荡江湖};
  2. acd;
  3. #nop 等于 [ask you about 闯荡江湖];

  4. #alias acd2 {ask you about %1};
  5. acd2 闯荡江湖;
  6. #nop 等于 [ask you about 闯荡江湖]。此时 %1 就代表 闯荡江湖;

  7. #alias acd3 {ask you about %1 %2 %3 %4};
  8. acd3 闯荡江湖 哈哈 嘿嘿 吼吼;
  9. #nop 等于 [ask you about 闯荡江湖 哈哈 嘿嘿 吼吼]。此时%1 %2 %3 %4分别代表 闯荡江湖 哈哈 嘿嘿 吼吼.;

  10. #alias acd4 {ask you about %1 %2 %3; #local temp %2; okLog $temp};
  11. acd4 闯荡江湖 {哈哈 嘿嘿} 吼吼;
  12. #nop 注意和acd3的不同, 其中 第二个参数通过大括号括起来了, 此时 %1表示闯荡江湖 %2表示[哈哈 嘿嘿] %3表示 吼吼.;

  13. #alias {acd3 %1 abt %2} {ask %1 about %2};
  14. #nop 别名还有这种用法,不过萌新完全忽略就行,完全可以用方式2代替;
复制代码

取消别名通过#unalias 名称 来实现。 比如 #unalias acd; #unalias acd3;


北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-8-7 17:13:53 | 显示全部楼层
本帖最后由 doumimi 于 2024-8-20 07:38 PM 编辑

#action 触发
语法: #action {文本} {命令} {优先级}
用法:比较核心的命令, 因为mud文字游戏,你要进行的游戏逻辑就是靠服务器给你的反馈来进行的。 直接看example
#act {^刑捕头在你的耳边悄声说道:我们的线人在%*的%*留下了一幅图,看清楚之后找画师画好一样的送到%*的%*。} {okLog 图画:%1的%2。 线人:%3的%4}

里面这个就是系统发过来的一个内容, 是一个任务npc给我的任务。 在action中,还有几个关键信息通过正则表达式匹配了下来。 在后面使用的时候,也分别是%1,%2,%3,%4 这几个参数。

哪些内容能触发?
所有服务器返回的内容,都能够进行action的触发, 比如npc的对话, hp命令的返回状态面板,look命令的返回地图信息, 打架时返回的战斗回合信息等。 具体请参考上面代码执行顺序的第5步。
但是, 屏幕上看到的信息,除了服务器返回的,还有一些Pt框架给出的信息, 比如【准备保卫!!!】, 或者一些你自己通过okLog打印的消息, 这些内容是不会被触发的。

触发的写法的区别对比:
假如我们要匹配的一个npc的原话如下 【刑捕头在你的耳边悄声说道:我们的线人在扬州的中央广场留下了一幅图】
  1. #act {^刑捕头在你的耳边悄声说道:我们的线人在%*的%*留下了一幅图$} {}
  2. #nop 这种是最完整的写法, 比较特殊的就是开头和结尾,分别有个 ^和$符号, 这两个符号表示匹配范围的开始和结束,也就是说, 在“邢捕头”这三个字之前,如果有其他字符,那么就无法匹配到这个action, 结尾同样,如果【图】后面有任意的内容,也匹配不到。

  3. #act {刑捕头在你的耳边悄声说道:我们的线人在%*的%*留下了一幅图} {}
  4. #nop 这种写法和上面那种对比,去掉了开头和结尾的限制, 效果就是哪怕前面和后面加上内容,也能进行触发。

  5. #act {%*线人在%*的%*留下了一幅图%*} {}
  6. #nop 这是第三种,前后都换成了%*,并且缩短了匹配的内容。这种肯定也能匹配到,就是可能会由于%*能匹配到的内容太多导致 误触发。

  7. #act {线人在%*的%*留下了一幅图} {}
  8. #nop 同样的文本,如果方法3能触发,那么这种写法也能触发,他俩的触发机制是一致的。 区别在于:这种写法的捕获的内容更少。大家可以okLog %0看一下, 他只捕获了匹配那里面写的内容, 前后都是不知道的。
复制代码
这四种写法,推荐的顺序就是 1、2、3、4. 因为写的越完整,误触发的几率就越小。

action的取消方式:
1. 使用#unaction 来取消某个 action。  平时我也很少用,因为action和其他的几种语法的有个重要的区别就是触发没有名字,需要完整的写出来具体的action才行。比如#unaction {^刑捕头在你的耳边悄声说道:我们的线人在%*的%*留下了一幅图$};
2. 如果写在了顶级块里面,可以通过KM来取消action, 如果是写在次级块里面,可以通过 kill class的方式来取消。 具体关于顶级块和次级块的概念,参见后面 代码块章节。

另外,推荐action的命令里面,只写简单的内容,比如一个oklog以及一个alias,把逻辑都封装到alias里面,方便管理。

$E是炮式触发,萌新可以先忽略,完全不用也不影响游戏体验, 具体想了解的 请看 HELP STYLE 第五节 。 这个主要就是为了方便查错, 防止冲突,并非功能上的新用法。



北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-8-7 17:14:24 | 显示全部楼层
本帖最后由 doumimi 于 2024-8-7 05:23 PM 编辑

#delay 延迟执行
语法:#delay {名称} {命令} {延迟时间}   或者  #delay {延迟时间} {命令}   
用法:delay也比较简单, 就是在一定时间之后,执行什么事情, 最低可以0.01秒。
  1. #delay {1} {ask han about job};
复制代码
这个就是1秒之后 执行  ask han about job。

值得注意的是, #delay 命令是不会阻塞后面的命令的执行的, 给两个例子。
  1. #alias testdelay {north; #delay 1 {south}; east; #delay 1.5 {west}};
  2. #nop 上面这个命令,实际的执行顺序如下 先执行 north和east, 等1秒后执行 south, 再等0.5秒后执行west;

  3. #alias testdelay {
  4.     north;
  5.     #delay 1 {
  6.         south;
  7.         east;
  8.         #delay 1.5 {
  9.             west
  10.         };
  11.     };
  12. };
  13. #nop 上面这种delay的嵌套写法,可以实现,按照代码的前后顺序来执行,也就是 先执行north, 等1秒执行south,east. 在等1.5秒后执行west;
复制代码

取消delay的用法:
  1. #undelay {delay名称};  

  2. #nop 例子;
  3. #delay abc.delay {haha} 3; #undelay abc.delay;
复制代码




北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-8-7 17:14:55 | 显示全部楼层
本帖最后由 doumimi 于 2024-8-7 05:24 PM 编辑

#ticker 定时执行

语法:#ticker {名称} {命令} {间隔时间}  
作用:就是定时执行。也比较简单, 间隔时间间隔最低0.1
  1. #ticker ttk {#showme 123} 1;
  2. #nop 设置一个定时器,名称叫ttk, 每隔1秒后会执行 #showme 123;

  3. #untick ttk;
复制代码

类似于action, ticker的命令也推荐只写一个别名, 具体的逻辑封装到别名里面。


北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-8-7 17:15:26 | 显示全部楼层
本帖最后由 doumimi 于 2024-11-5 07:07 PM 编辑

#var 变量
语法:#var {变量名称} {变量值};
作用:定义一个全局变量, 变量的作用通常是用来记录信息、流程判断。 注意!#var 一旦定义就是全局的,所以在控制台,或者任意机器里面定义的var。 都能在其他机器访问到。
如果不想定义全局的变量,可以用#local 来定义变量。 #loacal 和 #var的唯一区别就是生命周期范围不同, 本篇文档主要是给萌新入门用的, 所以不细讲,在前期哪怕所有的机器都不用#local 都没有问题。(后面技术好了之后,最佳实践还是多用local,具体可以多学习pt源码)

Tintin的变量最常见的有4种类型,或者说4种用法:
1. 字符串。
2. 数字。
3. 列表。
4. 表。

字符串类型
  1. #action {^%*说道:你要去%*的%*找一个叫%*的人$} {
  2.     okLog 捕捉;
  3.     #var npc-name %1;
  4.     #var task-area %2;
  5.     #var task-room %3;
  6.     #var task-target %4;
  7. };

  8. #alias taskinfo {
  9.     okLog name: $npc-name;
  10.     okLog area: $task-area;
  11.     okLog room: $task-room;
  12.     okLog target: $task-target;
  13. };

  14. #show {张三丰说道:你要去扬州的当铺找一个叫张全蛋的人};
  15. taskinfo;
复制代码


上面是一个最常用的使用变量的例子,就是将任务文本,或者提示给抓到,并且将其中的重要信息提取到变量中, 大家自己先试一下,有几点说明一下:
1. 代码的执行顺序就是从上到下的,先声明了一个action(此时里面的内容还没有执行), 然后声明了一个别名taskinfo(此时里面的内容也没有执行)。 然后执行#show。 此时show的内容会触发action,然后执行action里面的赋值代码。 然后调用taskinfo这个别名, 开始执行taskinfo里面定义的4个okLog.
2. #var 定义的变量是全局有效的。如果 $npc-name 已经赋值好了。 那么在另外一个机器里面,也能通过 $npc-name来获取到变量。

数字类型
  1. #var count 1;
  2. #math count {$count + 2};
  3. okLog count: $count;
复制代码
数字类型就是可以通过 #math来对变量进行运算。 比如有些任务要执行一个动作多次, 或者进行一个循环的处理判断,都可以用数字类型。

列表类型
列表的定义,以及列表的操作方式。
  1. #var t-list {};
  2. #list t-list create {a;b;c};
  3. okLog &t-list[]; #nop 输出列表元素数量 3;

  4. okLog $t-list[]; #nop 输出列表的所有值 abc;
  5. okLog $t-list[2]; #nop 输出列表的第二个值 b;
  6. okLog $t-list[-1]; #nop 输出列表的最后一个值 c;

  7. okLog *t-list[]; #nop 输出列表每个元素的key,也就是 123;
复制代码


上面这段代码创建了一个列表, 然后列表中的元素有3个,abc。  直接访问列表的话,有3个符号很重要, 分别是 &取数量, $取value,*取key

三种遍历方法:
  1. okLog 遍历列表;
  2. #foreach {$t-list[]} {value} {
  3.     okLog $value;
  4. };

  5. #foreach {*t-list[]} {index} {
  6.     okLog $index;
  7.     okLog $t-list[$index];
  8. };

  9. #loop 1 &t-list[] {index} {
  10.     okLog $index;
  11.     okLog $t-list[$index];
  12. };
复制代码

列表的最常用的几个方法:
create , add , find, delete
  1. #var t-list {};
  2. #list t-list create {a;b;c};
  3. #list t-list add {d}; #nop 增加一个元素;
  4. #var t-list; #nop 打印这个列表,查看结果;
  5. #var t-index aaaa;
  6. #list t-list find {b} {t-index}; #nop 查找是否含有b这个元素,并且把这个元素的index赋值给t-index这个变量, 此时应该为2;
  7. okLog $t-index;

  8. #list t-list delete 1; #nop 删除第一个元素,此时应该删除的是a;
  9. #var t-list;
复制代码


表类型
  1. #var testvar {
  2.     {扬州} {当铺}
  3.     {襄阳} {酒店}
  4.     {扬} {{x}{扬1} {m}{扬2}}
  5. };
  6. #var testvar[扬州];
  7. #var testvar[襄阳];
  8. #var testvar[扬][x];
  9. #var testvar[扬][m];

  10. #nop 另外一种写法;
  11. #var testvar[扬州] 当铺;
  12. #var testvar[襄阳] 酒店;
  13. #var testvar[扬][x] 扬1;
  14. #var testvar[扬][m] 扬2;

  15. #var testvar;
复制代码

上面是定义一个表的两种写法, 上面的写法就是直接写在变量里面,通过两对大括号括起来, 前面的大括号就表示一个key,后面的大括号里面的内容就是value。  如果不好理解的话,直接看第二种就行,这种写法和直接定义一个变量使用起来区别不大。表类型的适用场景:
1. 明显适合用键值对 key-value这种形式的场景。
2. 表定义的变量更加的聚合, 方便把一系列相关的内容,封到一个变量里面,方便查看。 比如#var char。 大家可以看下pt对于角色信息是如何封装的。
3. 同一个任务的相关变量都可以封装到一起,比如纪晓芙任务, 可以jxf[area] jxf[room] jxf[report] 。 这样完成一次任务要进行变量清空的时候,可以直接 #unvar testvar。 里面所有的内容都无法访问了。



北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-8-7 17:16:03 | 显示全部楼层
本帖最后由 doumimi 于 2024-8-7 06:38 PM 编辑

#if else 逻辑分支
上面我们学会了变量的定义,那么这节讲逻辑分支,最重要的就是#if else。
  1. #var testa 纪晓芙;
  2. #var testb 123;
  3. #if {$testb > 100 && "$testa" == "纪晓芙"} {
  4.     okLog 找到了;
  5. };
  6. #else {
  7.     okLog 没有找到;
  8. };
复制代码

注意几点
1. 在进行变量判断的时候, 如果是数字,那么可以用 数学相关的比较符号来进行计算。 比如大于等于。
2. 在条件中支持 与和或, 上面的例子中的 && 就是与的意思, 两边的条件都需要满足。  如果是 || , 那么两边只用满足一个就行。
3. 如果要进行字符串相关的判断, 要注意 变量名称 和 最终比较的字符串,都要加上双引号
4. if 和 else 是对应的关系, 执行了if ,就不会执行 else。 如果需要执行else, 一定不会执行if 。 其中中间也可以穿插elseif。 这里就不展开讲解了。
5. if 里面的内容也是完全支持正则表达式的,就可以实现很多丰富的功能。




北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-8-7 17:16:35 | 显示全部楼层
本帖最后由 doumimi 于 2024-8-13 06:20 PM 编辑

四、 LM KM RLM 以及 代码块
    - 常用 PaoTin++ 命令介绍
      - 查看模块列表: MODS 或 list-modules,其中模块名称可以点击。
      - 查看模块详情: MOD <模块名称> 或 look-module <模块名称>,名称可以通过 MODS 查看。
      - 加载模块: LM <模块名称> 或 load-module <模块名称>,参见 HELP load-module。
      - 卸载模块: KM <模块名称> 或 kill-module <模块名称>,参见 HELP kill-module。
      - 重新加载: RLM <模块名称> 或 reload-module <模块名称>,参见 HELP reload-module。
      - 顶级块:直接写在文件最外层的,不包含在任何定义块中的定义块,被称为顶级块。
      - 次级块:那些被包含在别的定义块当中的嵌套定义块,被称为次级块。

关于机器的加载、卸载、重加载也是新人需要了解的,但这部分没什么好讲的,这部分就直接引用 HELP paotin的文档了,大家记着就行。  

我想再说一下顶级块,次级块。
顶级块:比如直接写在文件里面的 #alias xx {}; 或者一些#action {} {}。 这些代码执行的效果就是定义、注册或者说声明了这部分内容到客户端。 你也可以比如直接写个 okLog xxx; 或者 path.Walk xxxx; 或者开启一个定时 #tick .或者 直接写个#if 判断, 这些所有直接写到外面内容,都称为顶级块, 也就是LM的时候会执行一次 (不要搞混#alias的定义,和#alias的调用)。 KM的时候,也会删除除了变量以外的所有顶级块的内容,比如定义的别名,action,定时器等。
次级块:比如 比如在 #alias 的命令里面,我再写一个#alias 或者 #action, 后面两个就被称为次级块,简单的来看就是有嵌套关系,在里面的部分是次级块。 次级块的代码,会在执行了外层的顶级块的alias或者action后才会生效,直接LM后,里面的内容是没有执行的。 同理,KM 无法对其生效。

下面举例说明: 假如我写了个test.in的机器。
  1. #alias testmod1 {
  2.     okLog testmod1;
  3.     #alias testmod2 {
  4.         okLog testmod2;
  5.     };
  6. };
复制代码

首先,在不执行 LM test的时候,testmod1 和 testmod2肯定都是无法执行的。
1. 此时我们执行 LM test. 此时会从上到下执行一遍代码中的顶级块。 所以,此时会执行 #alias testmod1 {xxx} ; 这个代码的含义上面讲过,【声明一个别名】,注意,此时只是声明了,但是还没有调用。 我们可以通过#alias testmod1 以及 #alias testmod2 来验证,此时发现, testmod1已经有了,目前还没有 testmod2这个别名。  
2. 此时我们执行 testmod1.  那么这个别名干了啥呢? 打印一个okLog, 然后再【声明】一个testmod2的别名。 再通过上面的验证方法, 可以发现,两个现在都有了。
3. 此时我们KM test。 再验证,发现 testmod1 没了, testmod2依然可以用。

这就是为什么:  新人总发现, 我把模块卸载了,为什么好像有些action还在工作,或者某些tick还在执行, 因为他们在次级块!!! 当然写次级块里面是没有问题的,好的机器就是会有嵌套。 那解决办法是什么呢? action,alias,tick之类的都可以通过class来包裹一下,及时的做好class的kill的处理。 或者写一些 untick,unaction,unalias ,比如 #alias dushu.stop {#untick tk.du};

原理进阶:tintin 是通过class来管理代码的,回到上面的实验的第二步, 此时 输入 ALIS test你会发现, 这几个alias的class不同,所以在 KM 的时候,也就无法回收了。

更详细的请仔细阅读HELP paotin。



北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
 楼主| 发表于 2024-8-7 17:17:15 | 显示全部楼层
本帖最后由 doumimi 于 2024-8-7 05:28 PM 编辑

# 机器的思考

到底应该先玩游戏,还是先搞机器? 总体来说还是建议以游戏为主, 尤其是刚接触的时候,不建议跟一个机器死磕, 非要写到完美。  机器是用来优化游戏体验的, 可以先从抓action,然后写一些简单的alias来优化自己的体验最重要。因为随着你的游戏理解加深, 你会发现不同任务之间是有共性的, 比如你做慕容仆人的时候,你可能想着的是怎么杀死这个慕容的敌人, 等到做的任务做多的时候呢,你会想着,不同的敌人要处理的方式不一样。 等你玩的门派多的时候,你会发现你需要一个单独的管理战斗的机器模块。 再等你 四职业都体验完成之后,你会发现有些职业只需要抱头蹲着。  总之,你的需求也会随着游戏理解的增加而越来越多。
  
1,机器,或者说写机器的水平
2,经验值,或者说对游戏的理解程度
3,游戏资产
这 123 就好像是长方体的长宽高。你会发现,只提高一方面,收益不大,三方面都提高,收益最大。
如果只提高机器水平,不提高另外两方面,你就是根筷子。
如果只提高机器水平和对游戏的理解(背 wiki),你就是张印度飞饼。
如果你三个方向一起提高,你就顶个球!
-------------from dzp

机器是用来解决游戏中的问题的, 那么针对同样的问题, 解决思路是多样的,同一种思路的实现手法也是多样的
1. 解决思路是多样的:机器的实现不是一成不会变的, 也比如学习机器。 既可以通过 action来实现,也可以通过 tick来实现。 那两种方式其实各有优略, 比如,通过action的学习可能就非常的快, 但是问题就是有一定的可能会搞出来撞墙机器人,通过tick来弄呢,效率不如action,但是相对更稳一些,不会出现少了某个action的触发,整个机器就断掉。 也有可能两个结合起来,才是最好的。
2. 实现手段是多样的:比如我想要每隔几秒干一个事情, 除了用tick, 也可以用#delay递归的来实现。
  1. #alias gogo {
  2.     #if {$stop == 1} {
  3.         okLog 该结束了;
  4.             #return;
  5.     };

  6.     #delay 5 gogo;
  7. };  #nop 递归实现每隔5秒执行一次
复制代码

此贴到此完结!
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
发表于 2024-8-7 17:19:05 | 显示全部楼层
加油,会写事件相关的攻略吗,这个的教学没怎么看到。
北大侠客行Mud(pkuxkx.com),最好的中文Mud游戏!
回复 支持 1 反对 0

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-11-27 11:01 PM , Processed in 0.013496 second(s), 14 queries .

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

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