| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348 |
- #nop vim: set filetype=tt:;
- /*
- 本文件属于 PaoTin++ 的一部分。
- PaoTin++ © 2020~2023 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 享有并保留一切法律权利
- 你可以在遵照 GPLv3 协议的基础之上使用、修改及重新分发本程序。
- */
- #var MODULE {paotin};
- ///=== {
- ///// PaoTin++ 编程框架功能介绍
- /////
- ///// PaoTin++ 主要由以下四部分组成:
- /////
- ///// - 1. 一个 TinTin++ 衍生品: https://github.com/mudclient/tintin/tree/beta-develop
- ///// 它在 TinTin++ beta 版的基础上,提供了一些额外的补丁。
- ///// - 2. 一个编程框架: 其主要部分包含一些针对 TinTin++ 的语法扩展,一些辅助函数,
- ///// 还有一个模块加载器,以及一份基于最佳编程实践的编程规范。
- ///// 另外,PaoTin++ 也有着完善的日志系统,允许用户灵活方便地记录游戏日志,并保持游戏主界面干净整洁。
- ///// - 3. 一组标准库函数: 其主要部分涉及基本数据类型操作,以及一些功能性扩展,计有:
- ///// - UI: 标准化的开放功能 UI/UE,增强 TinTin++ 交互体验的同时,允许其它插件进行标准化的信息展示。
- ///// - xtintin: 意指 eXtended TinTin++,主要包含一百多个预定义函数以增强对基本数据类型的操作。
- ///// - event: 一个事件驱动编程框架
- ///// - storage: 一个标准化的客户端本地存储管理系统
- ///// - speedo: 一个数据测速仪
- ///// - option: 一个标准化的选项管理系统
- ///// - telnet: 一组 TELNET 字符定义
- ///// - gmcp: 一组 GMCP 处理例程以支持 telnet GMCP 子协议
- ///// - ga: 对 telnet IAC GA 的增强
- ///// - 4. 预置的与特定 MUDLIB 有关的触发。
- ///// - 北大侠客行: 登录模块,角色信息,玩家技能,锦囊内容,聊天信息
- ///// - 清华西游记: 登录模块
- /////
- ///// 下面是 PaoTin++ 增加的语义扩展关键字。
- //
- // ## load-file <文件名>
- // 规范的脚本文件加载方法。假设文件名是 foo/bar.tin,则本命令会从以下位置按照从上到下的顺序进行查找:
- //
- // - 1. 玩家自定义位置: var/foo/bar.tin
- // - 2. MUD定制版位置: mud/$MUD/foo/bar.tin
- // - 3. 默认脚本位置: foo/bar.tin
- //
- // 如果前面的某个位置找到了相应的文件,则不再继续查找。
- // 其中 $MUD 代表当前选择的游戏服务器,可通过 #var gCurrentMUDLIB 查看。
- //
- // ## load-config <配置文件名>
- // 规范的配置加载方法。假设配置文件名是 foo/bar,则本命令相当于 load-file etc/foo/bar.tin,
- // 也就是说,配置文件 foo/bar 对应的实际物理文件可能是:
- //
- // - 1. 玩家自定义位置: var/etc/foo/bar.tin
- // - 2. MUD定制版位置: mud/$MUD/etc/foo/bar.tin
- // - 3. 默认脚本位置: etc/foo/bar.tin
- //
- // 如果前面的某个位置找到了相应的文件,则不再继续查找。
- // 其中 $MUD 代表当前选择的游戏服务器,可通过 #var gCurrentMUDLIB 查看。
- //
- // ## VAR <变量中文含义> <变量名> <值>
- // 声明并初始化变量。和 #var 不同,如果该变量已存在,则不会修改它的值。
- // 另外,如果在模块中使用本方法,则声明的变量会自动存放在 #class data/lib/xtintin 中。
- // 这意味着即使你重新载入模块代码,也不会破坏该变量的值。
- // 因此建议将通过触发抓取到的任务进度信息用本方法存储,可以有效避免机器代码迭代
- // 开发过程中,丢失任务信息从而导致任务失败。
- //
- ///// 辅助函数
- //
- // #@ uuid
- // 根据时间戳和随机数生成一个唯一的 ID。
- //
- // #@ existsSession <会话名>
- // 检测是否存在指定的会话。
- //
- // #@ existsAlias <别名> [<class>]
- // 检测是否存在指定的别名。如果指定了 class,则同时校验此别名是否属于该 #class。
- //
- // #@ existsFunction <函数名> [<class>]
- // 检测是否存在指定的函数。如果指定了 class,则同时校验此函数是否属于该 #class。
- //
- // #@ existsVar <变量名>
- // 检测是否存在指定的变量。
- //
- // #@ mkdir <全路径>
- // 在文件系统上创建目录。
- //
- // #@ existsFile <文件全路径>
- // 检测文件系统上是否存在该文件。
- //
- // #@ existsPlugin <模块全限定名>
- // 检测文件系统上是否存在该模块。
- //
- // #@ existsJobPlugin <任务模块名>
- // 检测文件系统上是否存在该任务模块。
- //
- ///// 模块管理器
- /////
- // #= TERM 一,术语和定义
- //
- // 为了便于下文叙述方便,这里对一些基本术语进行约定:
- //
- // - 大写字母:指 A-Z 26 个大写拉丁字母
- // - 小写字母:指 a-z 26 个小写拉丁字母
- // - 字母:指大写字母和/或小写字母
- // - 数字:指 0-9 十个阿拉伯数字
- // - 字母数字:指字母和/或数字
- // - 单词:指英文单词或汉语拼音,通常仅由字母组成,但有些缩写或者术语可能会出现少量数字,但不应该
- // 出现在单词开头。单词可用于命名各类标识符,此时应当尽量选取具有明确的含义的词语,应避免
- // 使用过于模糊的字眼,如果英语能力不足以精确表达,可采用汉语拼音;如果汉语拼音过于冗长,
- // 也可以采用汉字命名,但汉字命名风格应当仅限于模块局部范围使用的别名和变量。
- // - 大骆驼风格:只包含字母数字的一个或多个单词序列,其中每个单词的第一个字母必须是大写,其余字母小写
- // - 小骆驼风格:只包含字母数字的一个或多个单词序列,其中除第一个单词全部小写外,其余单词的第一个字母
- // 必须是大写,其余字母小写
- // - 蛇形风格:一个或多个单词序列,仅允许小写字母,之间以下划线分隔。
- // - 羊肉串风格:一个或多个单词序列,仅允许小写字母,之间以连字符(半角减号)分隔。
- // - 变量:指通过 #var、#list、#local 等语句创建的内存片段,其中 #local 也称为局部变量。参见 HELP DataType
- // - 定义块:指 #action、#alias 等语法定义代码,有时也称为「触发器」,参见 #help triggers
- // - 顶级块:直接写在文件最外层的,不包含在任何定义块中的定义块,被称为顶级块。
- // - 次级块:那些被包含在别的定义块当中的嵌套定义块,被称为次级块。
- // - 流程块:指 #if/#else/#switch/#foreach 等流程控制语句,参见 #help statements
- // - 语法块:定义块和流程块,合称为语法块
- // - 代码块:大部分语法块结构中,都包含有一个表示将要执行的代码的花括号,该部分连同花括号一起,
- // 被称为代码块。其中花括号内的部分有时也被称为命令序列。
- //
- // #= MODULE 二,什么是模块?
- //
- // 模块是一种组织代码的方式。当代码量日益增长时,人们常常会产生模块化的需求。也许是因为时间久了,
- // 前面写的代码细节你会忘掉。也许是因为你拿到了一份别人分享的代码,企图与自己的需求进行结合。不论
- // 是哪一种原因,好的工程实践要求人们尽量做好代码和代码之间的隔离,将相关的内容写在一起,而不相关
- // 的内容则尽量区分开来。这样做可以有效地提高代码的通用性,减少不必要的重复。一方面降低由于不得不
- // 熟悉那些自己暂时并不关心的逻辑细节而造成的心智负担,另一方面也可以精细化某个局部细节,使之更加
- // 完善,服务更多的场景。反过来,能够服务更多场景的模块,也一定会被更多地方调用,而不是反复地在低
- // 水平层次被重写。当需求发生变更时,修改公共的模块也可以让多个调用场景同时受益,而当新的需求产生
- // 时,如果有更多的可重复使用的公共模块,也可以事半功倍。
- //
- // 在大多数编程语言中,都提供模块化组织代码的机制。这些机制通常与文件目录结构、名字空间、可访问性
- // 等概念密切相关。这允许人们通过规范的方式来命名、存放、组织代码,也可以避免不小心污染或者修改程
- // 序的其它部分。然而很不幸的是,TinTin++ 作为一门脚本语言,它在这方面的功能非常弱,几乎没有。主要
- // 表现在:
- //
- // * 你可以任意修改一个变量或者定义块,即使它们原本不是你所创建和维护的。
- // * 所有变量和定义块的名字都是平坦的,没有层次。仅从名字上无法分辨它们各自都是哪部分代码在管理。
- // * TinTin++ 语言和官方指南并没有对多个脚本文件在电脑中如何摆放做任何约束或者合理化建议。
- // * 你无法方便地将一组相关联的代码从磁盘上加载到内存,或者是反过来从内存中擦除(或是导出到磁盘)。
- // * TinTin++ 仅有的和模块化编程有关的一个语法特性,#class,其设计和实现存在巨大的使用陷阱:
- // - #class close 并不能让 #class 失效。#class open/close 看起来更像是 #class begin/end。
- // - #class kill 虽然可以让 #class 失效,但失效的 #class 就真的再也找不回来了。
- // - #class 和文件的对应关系非常糟糕,#read 加载的文件仍处于之前的 #class 中。
- // - #class read 虽然可以为加载的文件分配一个 #class 名字,但文件中的语法块由于其执行时机的
- // 变化,往往会不小心将数据和触发器创建到别的 #class,污染它们。
- // - 由于所有的名字都是全局的,并且没有强访问约束,也没有行之有效的命名规范,因此原本隶属于
- // #class A 的变量和定义块很容易被 #class B 的同名变量或定义块所覆盖,此时该变量或定义块的
- // 内容和行为表现由 #class B 所定义,而归属权仍属于 #class A,导致加载 B 会影响 A,卸载 A
- // 会影响 B。
- //
- // 以上问题随着玩家在深入 TinTin++ 的过程中,会逐渐“踩坑”,而在“踩坑”的过程中,不同的玩家会给出
- // 不同的解决方案。而这些不同的解决方案又进一步造成了 TinTin++ 社群的隔阂,导致几乎所有的人都是在单
- // 打独斗,无法形成合力。例如,TinTin++ 社群至今尚未出现一个权威的常用函数集,来解决诸如取几个数的
- // 最大值、最小值、平均值、求和,或者识别中文数字串、时间串一类的函数。也许每个人都搞了一套,但相互
- // 之间并不通用,也没有一个统一的获取渠道。
- //
- // 有鉴于此,PaoTin++ 提出了一个综合性的方案来治理此问题,主要包括三部分:
- //
- // 1. 一套基于模块化编程思路的命名规范,涉及文件、目录、变量、定义块等标识符。
- // 2. 为了支持该命名规范,对 TinTin++ 进行了必要的修改,主要是修改了标识符定义规则。
- // 3. 基于该命名规范,实现了一组可扩展的标准库。
- //
- // #= STYLE 三,模块化编程体系下的 PaoTin++ 命名规范
- //
- // 由于 TinTin++ 本身所有的标识符都是全局的,并不提供强访问约束,因此 PaoTin++ 提供的命名规范只能是
- // 协作性的。其主要涉及以下几个部分:
- //
- // 1. 模块命名规范
- //
- // - 模块名由斜杠(/)分隔的多个部分构成,完整的模块名称为「全限定名」。
- // 全限定名的各个部分仅允许羊肉串风格
- //
- // 以下是一些合格的模块名称举例:
- // * foo 其短名称为 foo
- // * foo/bar 其短名称为 bar
- // * hello-world 其短名称为 hello-world
- // * my-basic/hello-world 其短名称为 hello-world
- // * bot/make-h2o 其短名称为 make-h2o
- //
- // 以下是一些不合格的模块名称举例:
- // * FOO 不能用大写字母
- // * foo_bar 不能用下划线
- // * 3com 不能以数字开头
- // * mud.pkuxkx.net 不能包含小数点
- // * /newbie/xingxiu 斜杠只能做分隔符,但不能作为开头
- //
- // - 模块全限定名的最后一部分被称为「短名称」。短名称不宜太短,要有足够的辨识度,尽量避免重名。
- //
- // 2. 模块内的定义块命名规范
- //
- // 模块可以看作是定义块和变量的容器,其内部的定义块和变量应当通过其命名准确反映它所属的模块
- // 和层次结构。本规范用小数点作为分隔符,以体现这一点。
- //
- // - 顶级块:模块内的顶级块名称由模块的短名称和顶级块的基本名两部分组成,两者以小数点连接。例如:
- //
- // * #alias {sachet.CombineGem} {#0}; 模块名为 sachet,基本名为 CombineGem
- // * #alias {fullsk.lian} {#0}; 模块名为 fullsk,基本名为 lian
- // * #tick {prompt.refresh} {#0}; 模块名为 prompt,基本名为 refresh
- // * #func {option.Get} {#0}; 模块名为 option,基本名为 Get
- //
- // 顶级块的基本名根据其可访问性的不同,采用不同的命令风格,以图「望文生义」。
- //
- // * 如果顶级块是可供外部访问的别名或者函数,则应当使用大骆驼风格。
- // * 如果不可访问的内部别名,则应当采用羊肉串风格。
- // * 如果不可访问的内部函数,则应当采用蛇形风格。
- //
- // - 次级块:模块内的顶级块在运行时,用户可通过其代码块创建新的次级块,那么这些次级块的名字应
- // 当由其所在的父块名称做前缀,与其基本名以小数点连接。例如:
- //
- // * #alias {sachet.CombineGem.get} {#0}; 所属顶级块为 sachet.CombineGem,基本名为 get
- // * #tick {prompt.refresh.draw} {#0}; 所属顶级块为 prompt.refresh,基本名为 draw
- // * #delay {char.dazuo.check-busy} {#0}; 所属顶级块为 char.dazuo,基本名为 check-busy
- //
- // 由于次级块都不可以从模块外部访问,因此命名风格应该仅限于羊肉串风格(别名)和蛇形风格(函数)。
- //
- // 3. 变量命名规范
- //
- // PaoTin++ 规范下,有几种不同类型的变量,它们分别是(作用范围依次递减):
- //
- // - 全局变量:由 #var 或 #list 命令创建,全局作用域,可供所有人访问。
- // - 模块全局变量:由 VAR、#var 或 #list 命令创建,模块作用域,可供模块内所有代码共享。
- // - 定义块全局变量:由 #var 或 #list 命令创建,模块作用域,可供该定义块所有下属次级块共享。
- // - 局部变量:由 #local 命令创建,块作用域,仅在代码块内可以访问。
- //
- // 其中全局变量采用大骆驼命名风格,前面加 g 前缀,例如 gCurrentMUDLIB。
- // 局部变量采用小骆驼命名风格,不加前缀。
- // 模块全局变量和定义块全局变量的名字则由前缀部分和基本名两部分组成,两者以小数点连接。其中
- // 基本名采用羊肉串风格,模块全局变量的前缀部分取模块的短名称,而定义块内的全局变量则以所在的
- // 定义块名称为前缀。
- //
- // 以下是一些合格的变量名称举例:
- //
- // * $gCurrentMUDLIB,全局变量,该变量可以从任何地方访问。
- // * $fullsk.current-skill,模块全局变量,该变量仅供 fullsk 模块内部使用。
- // * $fullsk.lingwu.skill,定义块全局变量,该变量仅供 fullsk.lingwu 别名及其次级块使用。
- // * $name 局部变量,可能是什么东西的名字,仅可在代码块内访问,离开该代码块则失效。
- //
- // 4. #class 命名规范
- //
- // 模块加载后其顶级块会自动放置到与模块全限定名同名的 #class 中。该 #class 由模块加载器维护,
- // 用户不宜再做干预。
- //
- // 无论是顶级块,还是次级块,如果其中需要嵌套更次一级的定义块的话,用户就需要创建 #class,那么
- // 这些新创建的 #class,其命名应当与所在的定义块名称保持一致。所有的 #class 在遵循以上规则的前
- // 提下,最终会形成一个 #class 名字空间。
- //
- // 用户可以通过不带参数的 #class 命令来查询所有的 #class,规范的 #class 名称一定会让人感到赏心
- // 悦目,方便查错。
- //
- // 5. 模块内的文本触发器命名规范
- //
- // TinTin++ 的文本触发器,即 #action 实际上都是匿名的。这在有些时候可能会带来不便。PaoTin++ 鼓励
- // 人们使用「炮式触发」来避免麻烦,即在正则表达式末尾缀一个无用的匹配规则来指示该触发所属模块。
- // 方便查错,也可以防止触发器冲突。例如:
- //
- // * #action {^你一觉醒来。{|ID=char/sleep}$} {#0};
- //
- // 这里的 `{|ID=char/sleep}` 则可以认为是,给本条文本触发器起了一个名字,叫做 char/sleep。这里
- // 的命名规范类似于其它具名定义块的命名,不过相对随意一些,并且建议用斜杠(/)来代替小数点作为
- // 分隔符。
- //
- // 6. 文件和目录命名规范
- //
- // 一个 PaoTin++ 模块在磁盘上可以对应一个文件,或者对应一个目录。为了便于用户扩展,PaoTin++ 框架
- // 提供了三个不同的位置来加载模块,它们分别是:
- //
- // - 默认位置: $PaoTinRoot/plugins/ 优先级最低,由 PaoTin++ 维护
- // - MUD定制版位置: $PaoTinRoot/mud/$MUD/plugins/ 优先级居中,由 PaoTin++ 维护
- // - 玩家自定义位置: $PaoTinRoot/var/plugins/ 优先级最高,由用户自行维护
- //
- // 其中 $PaoTinRoot 代表 PaoTin++ 的安装目录,$MUD 代表 MUDLIB 的类型 ID。
- // PaoTin++ 的模块应当存放在以上位置其中之一。模块全限定名的各个部分就相当于一个多级目录结构,
- // 这样所有的 PaoTin++ 模块就会形成三棵目录树,并在加载后从逻辑上来说位于同一个树状名字空间当中。
- //
- // 文件和目录都应当采用羊肉串风格命名,并且模块的全限定名和它在磁盘上的位置存在某种对应关系,
- // 具体参见 HELP load-module。
- //
- ///// 四,模块加载器相关 API
- //
- // ## load-module <模块名称>
- // 规范的模块加载方法。假设模块名称是 foo/bar,则本命令类似于:
- //
- // ```
- // class foo/bar open;
- // load-file plugins/foo/bar.tin;
- // load-file plugins/foo/bar/__init__.tin; #nop 如果前面的文件不存在;
- // load-file plugins/foo/bar/__main__.tin; #nop 如果前面的文件不存在;
- // class foo/bar close;
- // ```
- //
- // 也就是说,模块 foo/bar 对应的实际物理文件可能是:
- //
- // - 1. 玩家自定义位置: var/plugins/foo/bar.tin
- // - 2. MUD定制版位置: mud/$MUD/plugins/foo/bar.tin
- // - 3. 默认脚本位置: plugins/foo/bar.tin
- // - 4. 玩家自定义位置: var/plugins/foo/bar/__init__.tin
- // - 5. MUD定制版位置: mud/$MUD/plugins/foo/bar/__init__.tin
- // - 6. 默认脚本位置: plugins/foo/bar/__init__.tin
- // - 7. 玩家自定义位置: var/plugins/foo/bar/__main__.tin
- // - 8. MUD定制版位置: mud/$MUD/plugins/foo/bar/__main__.tin
- // - 9. 默认脚本位置: plugins/foo/bar/__main__.tin
- //
- // 如果前面的某个位置找到了相应的文件,则不再继续查找。
- // 其中 $MUD 代表当前选择的游戏服务器,可通过 #var gCurrentMUDLIB 查看。
- //
- ///// 日志系统
- /////
- ///// PaoTin++ 里面预置了大量的日志功能,默认情况下已经记录了完善的日志。另外也提供了
- ///// 一组相关的别名,用于用户自行记录日志。
- /////
- ///// 所有的日志都按照 ID 分类存放在各自的目录,例如玩家 dzp 的日志,存放在 log/dzp/*.log 里。
- ///// 根据你的部署方式和运行方式不同,也可能在 var/log/ 目录下。
- /////
- ///// - 1. 主屏日志: buffer.log
- ///// PaoTin++ 下,任何在游戏主屏上看到的信息,都可以从主屏日志中找到,方便玩家需要时翻阅。
- /////
- ///// - 2. 网络日志: socket.log
- ///// 网络日志忠实记录了和服务器交互的完整信息,既不多,也不少。如果你想知道你的机器
- ///// 最终向服务器发送了什么命令,或者是收到了什么信息,可以看看网络日志。
- ///// 网络日志也是向 wiz 反映问题时非常有说服力的的素材。
- /////
- ///// - 3. 聊天记录: chat.log qq.log
- ///// 每个聊天频道都有一个单独的聊天记录日志。聊天频道和日志文件的对应关系可以配置。
- ///// 参考 HELP ui/chat
- /////
- ///// - 4. 任务信息: job.log quest.log
- ///// 友好的机器人应当通过日志来记录自己的主要行为轨迹,或者任务简报。根据任务性质不同,
- ///// 分别记录在 job.log 或者 quest.log 中。一般经验收益类的任务在 job.log,非经验收益
- ///// 类的在 quest.log。
- /////
- ///// - 5. 练功记录: fullsk.log dz.log
- ///// 你可以通过练功记录来分析并优化 ID 的挂机练功速度。
- /////
- ///// - 6. 交互日志:
- ///// - infoLog: 一般的信息。默认为白色。
- ///// - okLog: 积极的信息。默认为亮绿色。
- ///// - warnLog: 醒目的信息,需要引起注意的信息。默认为亮黄色。
- ///// - errLog: 消极的信息,代表某种错误发生。默认为亮红色。
- ///// - dbgLog: 调试信息,包含大量细节的信息。不显示在主屏,只记录到文件。
- /////
- ///// - 7. 个性化日志:
- ///// 你也可以自定义自己的日志,任何以 Log 结尾的别名调用都会导致产生一个相应的 log 文件,
- ///// 无需提前声明。例如,你在你的模块中使用 myjobLog,将会导致产生一个 myjob.log 文件。
- ///// 类似的,xxxLog 将会产生 xxx.log。
- // };
|