【超新手教程】PaoTin++/TinTin++ 语法入门
本帖最后由 dtp 于 2024-7-16 01:37 PM 编辑# TinTin++ 语法入门
TinTin++ 功能很多,提供了大量的命令,这些命令有很多细节,大部分也都不需要刻意记忆,用的久了自然就记住了,不会用的时候现查都来得及。
但是,有一些基础知识是从一开始就需要知道的,如果不经过系统的学习,仅仅只是靠摸索来慢慢总结就不那么容易掌握。它们是:
* #help CHARACTERS
* #help SUBSTITUTIONS
* #help PCRE
建议按顺序认真阅读,深入理解。
鉴于以上内容均为英文,内容详略也有失当之处,结合笔者多年的社区运营经验,现重新整理其主要内容介绍如下:
## 基本符号(命令解析,定界符)
写在前面:注意,本文中提到的所有特殊符号都必须是半角字符。请关闭你的中文输入法或者自己设置好半角模式。
首先认识几组最重要的标点符号:
### 井号
井号`#`用来标记一条 TinTin++ 命令的开头。如果你在脚本中看到井号,那么井号后面的那个单词通常就是一个 TinTin++ 命令。如果你不认识这条命令,你可以试着用 #help 来查询它的用法。比如你看到 #tick,就可以用 #help tick 查到它的用法。
所有 TinTin++ 命令在不引起歧义的情况下,是可以缩写的,最短只需要两个字母就够了。例如,命令 #alias 可以写作 #alias、#alia、#ali,都没问题。但最好不要写作 #al,因为还有另一条命令 #all 的两字母缩写也是 #al,所以可能会导致混乱。
因为本条规则的存在,你可能会在阅读别人代码时,看到陌生的命令(例如我看到新人喜欢用的 #act 就感到很陌生),不要担心,任何时候你都可以用 #help 查看它们的真身究竟是什么。
与之相对应的,不以井号开头的命令,就不是 TinTin++ 命令。这通常用两种情况,一种是客户端别名,另一种是服务器命令(服务器别名也算作服务器命令)。
不过很可惜的是,TinTin++ 并没有很轻便的方法能够一眼分辨出哪些是客户端别名,哪些是服务器命令。
有鉴于此,PaoTin++ 发明了一套最佳实践如下,呼吁广大用户共同遵守:
* 客户端别名建议尽量用小写字母的模块名+小数点+别名组成,例如 sachet.CombineGem fullsk.lian prompt.refresh 等等。参见 HELP STYLE。
* 用户自己发明的快捷命令,尽量用全大写字母组成,例如 LM KM MODS VARS 等等。
* 纯粹由小写字母开头的,视为服务器命令。但这部分也可能是客户端伪装的服务器命令,目的是为了在尽量不影响用户使用习惯的前提下,额外为服务器命令增加新功能(比如美化或者解析命令结果)
* 如果确实希望明确将字符串发送给服务器,而无视其命名规则,则用 xtt.Send {......} 明确说明。
本帖最后由 dtp 于 2024-7-9 01:19 PM 编辑
### 分号
分号在 TinTin++ 中的作用很重要。前文说了,井号开头的是 TinTin++ 命令,否则就是客户端别名或者服务器命令。但是不论是哪一种命令,都需要以分号结尾。
这是因为一个完成的命令通常包括不止一个单词,那么第一个单词一般是命令本身的名字,而后面可能会包含若干参数,由于参数的个数是不确定的,所以就必须统一有一个符号来表示命令结束。这就是分号的意义。
实践中,分号一般有三种用法:
#### 当一行文字中包含多条命令时,用作命令间分隔符,例如:
yun recover; dazuo max
#### 用作单行命令的结束符,例如:
#alias yq {yun recover};
#### 用作多行命令的结束符,例如:
#alias daz {
yun recover;
dazuo max;
};
有些新人可能会机智地发现,有些时候不写分号也没事,但笔者建议最好不要这么做。由于 TinTin++ 对脚本解析的细节很难简单归纳,养成错误习惯之后,很容易掉坑里。
不过有一种情况是可以安全地省略分号的,那就是:
紧挨着右花括号的分号,可以省略,且出于美观考虑,建议尽量省略。
举例来说:
#alias daz {yun recover; dazuo max;};太丑了,最好写成:
#alias daz {yun recover; dazuo max};这样好看一点。
当然了,这两种写法并不完全等价,但是要严格论起来,抛开美观因素不说,第二种方法还要更好一些,所以建议大家一定要省略这个分号。
学会了正确使用分号,那么不管是你,还是你的读者,还是 TinTin++,大家就很容易对一条命令的边界是从哪到哪形成共识,遇到错误了也很容易排查。
本帖最后由 dtp 于 2024-7-9 01:16 PM 编辑
### 空格和花括号
前面提到的分号用来区分命令和命令的边界,那么空格和花括号就是用来区分命令内部的参数和参数之间的边界。
首先跟萌新解释一下,什么叫作参数。参数的意思就是说,有些命令它单独只是一个名字的话,并不能表示完整的意思。例如 #echo 是个 TinTin++ 命令,表示要显示一串文字,但是显示什么文字?光看名字就看不出来。再比如说 perform 是一个服务器命令,意思是要释放一个绝招,但是要释放哪个绝招?也不清楚。
或者反过来讲,如果单独的名字就代表完整的意思的话,那么如果有不同的意思想要表达,就必须要使用不同的名字,这可是个很大的记忆量。就好比 #zap 用来结束一个连接,#end 用来结束整个 TinTin++,sk 表示查询技能,hp 表示查询气血,sm 表示查询状态,jq 表示查询任务,lm 表示查询地图,还有 i、i2、id 这些似是而非的命令,这些都是一个单词代表一个单独的意思,就需要分别记忆。以至于有些命令如果很久不用的话,就需要想很久才能想起来,不然 help 都不知道该 help 啥。
所以大多数命令系统都采用参数来丰富命令的功能,或者汇聚不同的功能。这就意味着,用户不仅要知道命令的名字,还需要知道命令有哪些参数,每个参数分别代表什么意思,能接受怎样的格式,然后按照格式要求,一个一个填好参数,TinTin++ 才可以正确理解你的意思。
显而易见,TinTin++ 并不认识人类语言,无法按照人类的阅读习惯来断句。所以你写「helloworld」这就是一个参数,写「hello world」这就是两个参数。「扬州的客店」是一个参数,「扬州 客店」是两个参数。「dtp's jiazei」是两个而不是三个参数。「give gem to dtp」这就是 give 命令和它的三个参数。
以上例子中,显而易见可以看到,空格起到了至关重要的作用。空格可以把连续的一串符号,拆分成多个参数。
那么,如果我的参数本身就包含空格该怎么办呢?答案是:用花括号。花括号可以把多个单词和特殊符号全部都当做一个整体。
举个例子:「dtp's jiaze」是两个参数,而「{dtp's jiaze}」就是一个参数。「liu laoban」是两个参数,而「{liu laoban}」就是一个参数。
再举个例子:
#alias daz {yun recover; dazuo max};
当中,「yun recover; dazuo max」这部分就是一个参数,具体来说,是 #alias 这个命令的第二个参数。因为它们被花括号给包裹起来了。
最后再举一个复杂的例子:
#alias {wudang.volunteer.Job} {
#class wudang.volunteer.job open;
#action {^你向谷虚道长打听有关『守山门』的消息。$} {
#class wudang.volunteer.job open;
#action {^谷虚道长说道:「已经有人在守山门了,$PLAYER请稍后再来。」$} {
wudang.volunteer.job.done;
#delay wudang.volunteer.job {wudang.volunteer.Job} 10;
};
#action {^谷虚道长说道:「既然$PLAYER有意,就代贫道看守(volunteer)一下山门吧。」$} {
wudang.volunteer.job.done;
wudang.volunteer.goto.wdsm;
};
#action {^谷虚道长说道:「$PLAYER今日已经守过山门了。」$} {
wudang.volunteer.job.done;
};
#class wudang.volunteer.job close;
};
#alias {wudang.volunteer.job.done} {
#class wudang.volunteer.job kill;
sync.Ignore wudang.volunteer.job;
};
#class wudang.volunteer.job close;
ask guxu daozhang about 守山门;
sync.Wait {wudang.volunteer.job.done} wudang.volunteer.job;
};上面这段长长的代码,看着吓人,实际上就一个命令,那就是 #alias,这条命令定义了一个别名,名字叫做「wudang.volunteer.Job」,也就是 #alias 命令的第一个参数,后面那一大坨看着吓人,实际上就是一个长长的字符串,作为 #alias 的第二个参数。当然了,你稍微看一眼就知道这个长长的字符串像是一串代码,但是它现在只是一个字符串,除非 wudang.volunteer.Job 别名被触发,否则永远都不会执行,即使其中有错误,那也是执行的时候才知道。此时此刻,TinTin++ 只关心四件事:
1,因为这条命令是以井号开头的,所以这是一个 TinTin++ 命令,命令名字叫 #alias,作用是创建一个客户端别名。
2,以空格为分隔符,吞掉了第一个参数,也就是 wudang.volunteer.Job,作为别名的名字。
3,又以空格为分隔符,试图吞掉第二个参数,但因为遇到了花括号,所以把它后面的那一大坨都当成了一个参数,作为别名的命令。由于花括号的威力,那一大坨里面的井号、空格、分号、换行符、甚至是成对的花括号,都被当成了普通字符来处理。
4,最后遇到了分号,所以尽管 #alias 实际上还支持第三个参数,但 TinTin++ 已经不做他想,整条命令到此结束。
最后留一个小问题:
已知刚才这个例子是正确的,那么当别名 wudang.volunteer.Job 开始执行时,TinTin++ 又会发现几条新的命令?它们分别是什么?请把你的答案留在评论区。
本帖最后由 dtp 于 2024-7-9 06:49 PM 编辑
## 基本符号(文本替换,引导符)
「TinTin++ 是一门基于替换的语言」,笔者经常这样讲。实际上,理解替换发生的时机,以及替换的格式,这非常重要。你在脚本文件里看到的内容,在脚本实际运行过程中,统统都需要经过不止一次替换,才能最终生成服务器命令,然后再通过网络发出去。
TinTin++ 中除了别名替换无迹可循之外,大部分替换都有一个专门的引导词,来明确指示想要何种替换。下面一一为大家分解。
### 参数替换(百分号 %)
最常见的替换莫过于百分号引导的参数替换。参数替换最常见的是发生在两种情形下,一个是别名触发(#alias),一个是文本触发(#action)。我们先来看一下别名:
#### 别名中的参数
#alias lb {look bag %1};
本例中,我们定义了叫做 lb 的别名,方便我们查看包袱,其中 %1 就是 lb 的第一个参数,按照设想,它应当是一个数字,代表包袱的序号,这样我们就可以用同一个别名查看不同的包袱。
假如我有三个包袱,我就可以用 lb 2 查看第二个包袱,因为 lb 2 在执行的时候,TinTin++ 发现 lb 是个别名,那么就会展开成别名的内容 look bag %1,而 TinTin++ 在展开时,发现内容中存在 %1,那么就把 %1 替换成了 lb 的第一个参数,也就是 2,最后得到了:「look bag 2」,然后这时候 TinTin++ 发现这条命令不能匹配任何别名,那么就断定是一个服务器命令,就把「look bag 2」发送给服务器。
类似的,假如我有 18 个包袱,我可以用 lb 18,因为我经常查看包袱,所以这对我来说很方便。
显然,有时候一个参数是不够用的,可能需要多个参数:
#alias gt {node walk %1_%2};然后我们输入「gt yz ly」就可以从扬州到洛阳,这里的 yz ly 就分别会用来替换 %1 %2,所以最后实际执行的命令就是 node walk yz_ly,这个很好理解。
类似这样的别名参数一共有 %1~%99,也就是最多允许 99 个,在 PaoTin++ 中,别名的参数被限制为 %1~%19,也就是只剩下了 19 个。其余参数则另有安排。
除此之外,还有一个特殊的参数,叫做 %0,代表所有参数。请自行摸索体会,不再赘述。
参数也可以重复使用,这并不影响。比如我可以用下面的命令对 NPC 进行加倍调查:
#alias ll {look %1; lookin %1};那么当我输入 ll suicong 的时候,就会被替换成以下两条命令:
look suicong; lookin suicong这样就可以用一个别名发起多个操作。
前文说过,参数也可以包含空格,但必须用花括号,所以我们出于严谨起见,可以用「ll {tianshen suicong}」,这样执行的就是如下操作:
look tianshen suicong; lookin tianshen suicong看起来应该更保险一点。
#### 参数替换的时机
参数替换实际上发生得很早,对于别名来说,当别名被定义的时候,因为此时还不知道参数是什么,因此内存中保存的与别名名称相对应的代码块部分中,%0~%99 还没有发生替换。但是别名一旦被触发,那么 TinTin++ 立马会生成一个替换后的副本,把它当成一段脚本,开始解析。而当该脚本被解析时,已经找不到 %0~%99 了(而是被相应的值取代)。这比后续将要提到的其它几种替换发生得都要早。
简而言之,没有什么事可以阻止参数被替换,除了 % 本身。
如果我们想要阻止 %1 被替换,那么我们可以用 %%1。%%1 经过替换之后,会变成 %1(去掉了一层 %),那么这个 %1 就不会被替换,直到再次需要展开为止。举个例子:
#alias xuexi.set {
#alias xuexi.cha {cha -learn %0};
#alias xuexi.do {xue %0 for %%1 10};
};那么当我输入 xuexi.set zhang sanfeng 时(顺便演示了一下 %0 的用法),就会生成两个新的别名,一个用来查看张三丰都可以学哪些技能,另一个用实际学习。效果就像是这样:#alias xuexi.cha {cha -learn zhang sanfeng};
#alias xuexi.do {xue zhang sanfeng for %1 10};注意 %0 被替换了,%%1 只是脱了一层壳。
由于第二个命令需要一个新的参数,也就是技能名称,在 xuexi.set 时,还不能确定技能的命令,所以不能还不能让它替换,所以就先加一重 %保护一下,这样 %%1 经过替换之后就会变成 %1,那么下次输入 xuexi.do force 时,%1 就会被替换成 force。
#### 文本触发中的匹配参数
为了介绍文本触发中的参数替换,先简单说一下文本触发(#action)命令的用法。一般来说,文本触发(#action)有两个参数:
#action {这里没有%*这种食物。} {okLog 食物不足,急需补充 %1};如上所示,#action 命令有两个参数,第一个参数是用来匹配服务器文本的正则表达式,有些客户端也称之为「句型」,第二个参数是一行代码,这行代码会打印一行文字。
注意,在参数一中,出现了一个正则表达式通配符「%*」,这也是最常用的通配符,表示「任意长度的任意字符串」。
整个语句的意思就是说,当服务器发送过来一行文本,其内容与 #action 第一个参数的内容相仿时,则本条文本触发器就会被触发,触发后就会执行第二个参数所指定的命令(或者命令序列,前面说过,花括号里面可以包含多条命令)。
那么命令部分的 %1 是什么意思呢?你可能已经猜到了,就是 %* 这个通配符在实际触发时,所对应的文本。我们举个例子来说明一下:
2024-07-09 15:22:26 INPUT: #action {这里没有%*这种食物。} {okLog 食物不足,急需补充 %1};
#OK: #ACTION {这里没有%*这种食物。} NOW TRIGGERS {okLog 食物不足,急需补充 %1} @ {5}.
2024-07-09 15:22:31 INPUT: #show 这里没有gan liang这种食物。
食物不足,急需补充 gan liang
这里没有gan liang这种食物。
这里我们用 #show 命令模拟了一条服务器发送的文本。可以看到,%* 匹配了「gan liang」,而匹配参数 %1 就抓取到了「gan liang」。
这基本上就是文本触发中匹配参数的用法。类似于别名触发,TinTin++ 的文本触发同样有 %1~%99 共 99 个参数,但是 PaoTin++ 将其限制到了 %1~%49 共 49 个。其余留作备用。
另外,%0 代表整个正则表达式所匹配的文本。
最后再补充一点,#action 的第一个参数中也可以使用 %1~%99,作用类似于 %* 但有所不同,它们本质上属于正则表达式通配符,而不属于替换参数,并且笔者也不推荐使用,因此不予展开讨论。
有兴趣的话可以自行钻研。
本帖最后由 dtp 于 2024-7-10 03:12 PM 编辑
### 变量替换(美元符 $)
变量替换是另一种常见的替换。为了说明什么是变量替换,先来简单学习一下什么是变量:
#var foo bar;如果你认真阅读过前面的内容的话,就应该很轻松可以判断出来,这是一条 TinTin++ 命令(别忘了 # 号的作用),命令名是 var,后面跟着俩参数,foo 和 bar。
实际上 var 只是命令的缩写,它的全称是叫 variable,写起来有点长,所以一般都写作 var。(不知道你还是否记得前面说过的命令缩写部分)。
#var 命令在这里的作用就是定义了一个变量,名字叫做 foo,并且给它设定了一个变量值,叫做 bar。没错,foo 和 bar 是「变量名」和「变量值」的关系。
那么「变量替换」,就是指,我可以通过 foo 这个名字,取到 bar 这个值。这时候就需要用到美元符号:
okLog foo 的值是:$foo;
这样就可以了。
$ 引导的变量替换和前面说的百分号 % 引导的参数替换有个很重要的区别,那就是变量替换发生的时机不同。变量替换只有执行到那一条语句的时候,才会发生。
相同之处则是,变量替换也可以通过在前面叠加 $ 符号,来达到延迟替换的目的。
变量名中也可以带方括号,有特殊的含义:
#var char[当前气血] 1800;你可以简单地理解成,变量名是「char[当前气血]」,变量值是 1800,也不会有太大的问题。但实际上这不是一个普通的变量,而是聚集到一堆的多个变量中的其中一个。
你可以用 #var char 查看「那一堆」都有什么。看了之后就会发现,实际上 char 变量里,有好几堆,其中一堆叫做 char,而 char 里也有好多变量,其中一个叫做 char[当前气血]。
$ 替换对于包含方括号的变量名也可以生效:
okLog 气血:$char[当前气血];
占楼5 占楼6 再占一楼。 dtp 发表于 2024-7-9 11:31 AM
### 空格和花括号
前面提到的分号用来区分命令和命令的边界,那么空格和花括号就是用来区分命令内部的参数 ...
萌新来答一个,我的理解#class open close之间标记的class范围,第一次执行的是ask 任务,然后触发开始生效,根据回答选择等待,工作或者结束等,同时把class关闭。但是sync.ignore 和wait这对我没理解作用,我照猫画虎时没加也能用。 silverx 发表于 2024-7-9 04:07 PM
萌新来答一个,我的理解#class open close之间标记的class范围,第一次执行的是ask 任务,然后触发开始生 ...
你要看清楚问题哦。我问的是几条命令,分别是啥。