编写脚本¶
本教程仅提供 PySpigot 的概述,并不详细介绍 Bukkit/Spigot API 或编写 Python 代码。如有关于 Python 语法或一般 Python 代码编写方面的问题,请转至相应论坛咨询,因为本教程不会介绍基本 Python 代码的编写。
在编写 PySpigot 脚本时,有几个基本要点需要记住:
- PySpigot 需要 Java 17 或更高版本。
- PySpigot 官方支持 Minecraft 版本为1.16及更新版本上的 Spigot 和 Paper。
- 在内部, PySpigot 使用 Jython,这是 Python 的 Java 实现。目前,Jython 仅实现了 Python 2,因此在编写 PySpigot 脚本时应使用 Python 2 语法。
- 脚本必须使用 Python 语法编写,脚本文件的扩展名应为
.py。不以 .py 结尾的文件将不会被加载。 - 脚本放置在 PySpigot 插件文件夹下的
scripts文件夹中。PySpigot 允许在脚本文件夹中创建子文件夹以进行组织,但脚本名称必须在所有子文件夹和项目中是唯一的。 - 项目放置在 PySpigot 插件文件夹下的
projects文件夹中。 - 避免使用变量名
global和logger。这些变量名会在运行时自动赋值。有关更多信息,请参见下面的 全局变量 部分。 - 脚本和项目在功能上是彼此隔离的。除了
global变量(请参阅下面的全局变量部分)之外,脚本或项目之间没有共享任何内容。 - 要使用 PySpigot 提供的任何管理器(如注册监听器、任务等),必须在您的脚本中导入它们。有关更多详细信息,请参阅 managers 页面。
- 如果您正在使用除 ProtocolLib 或 PlaceholderAPI 之外的任何插件的 API,请确保在
script_options.yml文件中将该插件指定为依赖项。有关更多信息,请参阅 脚本选项 页面。
这个页面涵盖了写单文件脚本和多文件项目的核心基础知识。然而,编写多文件项目需要注意一些关键区别。欲了解更多信息,请参阅编写项目页面。
关于 Jython 的说明¶
在内部,PySpigot 使用了 Jython,这是 Python 的一个 Java 实现。与其他 Bukkit 插件相比,PySpigot 的 jar 文件相当大,因为 Jython(以及其依赖项)被捆绑到了 PySpigot 中。
Jython 的设计使得脚本在 Java 中被编译并完全解释执行。这意味着脚本在运行时可以完全访问整个 Java 类路径,非常容易与 Spigot API 和服务器的其他方面交互。考虑下面的例子:
1 2 3 4 5 6 7 8 9 | |
从上面的代码块可以看出,与 Java 类/对象交互是直观的。如果在与 Java 进行接口操作时遇到问题,Jython 有相当不错的文档,你可以在这里查阅。
默认情况下,Jython 内部在服务器启动时初始化(PySpigot 载入时也会初始化),这可能导致服务器启动时短暂的停顿。即使没有加载任何脚本,这也会导致一些内存开销。若要禁用此功能,并延迟 Jython 的初始化直到加载脚本时,可以在PySpigot 配置文件的 jython-options 部分中将 init-on-startup 设置为 false。
当前,Jython 的最新版本实现了 Python 2。因此,目前 PySpigot 脚本是用 Python 2 编写的。虽然有些人可能认为这是一个缺点,但 Python 2 通常对于大多数 PySpigot 的用途来说已经足够了,我还没有找到需要 Python 3 功能的脚本功能的情况。Jython 的开发人员打算在将来的某个版本中实现 Python 3,但目前更新的时间表尚不明确。在Jython GitHub 代码仓库上正在进行相关工作。
有关 Jython 的更多信息,请访问jython.org。
标准 Python 库¶
Jython 不 支持许多内置的 Python 模块(即使用 C 语言编写的模块)。这些模块必须要转移到 Java 中或者用 JNI 桥接进行实现。一些内置模块已经被转移到了 Jython,其中最显著的包括 cStringIO、cPickle、struct 和 binascii。Jython 的文档指出,JNI 模块很可能永远不会包含在 Jython 本身中。
现在,Jython 已经支持了大部分标准 Python 库。然而,Jython 的文档在跟进这些新增内容方面进展缓慢,因此如果 Jython 的文档没有提及某个库,可能仍然得到支持。
如果您想在脚本中使用标准的 Python 模块,请尝试导入它。如果成功了,那么可能就已经做好了准备。您也可以对模块调用 dir() 来检查其实现的函数列表。
基本脚本信息¶
所有的 PySpigot 脚本都被设计为自包含的,单个文件。这意味着每个脚本最多只会包含一个文件。此外,脚本之间是隔离的,意味着它们不会共享变量、函数或作用域。脚本可以以各种方式相互交互(下面会详细介绍),但请将scripts文件夹中的每个.py文件视为一个独立的实体,在其自己的环境中执行。
PySpigot 脚本被放置在scripts文件夹中,该文件夹位于 PySpigot 的主插件文件夹中。支持在scripts文件夹内创建子文件夹以实现组织。PySpigot 将尝试加载scripts文件夹中(包括子文件夹中)以.py扩展名结尾的任何文件。如果scripts文件夹中的文件没有以.py结尾,则不会被加载。
警告
脚本名称必须在 其他脚本名称 和 项目名称 中保持唯一,因为它们的名称在运行时用于识别它们。如果在scripts文件夹内使用子文件夹,此限制也适用。例如,scripts/folder1/test.py和scripts/folder2/test.py会发生冲突,但scripts/folder1/test.py和scripts/folder2/test2.py不会。
脚本选项¶
每个脚本都可以设置各种选项,包括启用状态、加载优先级和日志选项。这些选项在 PySpigot 的主插件文件夹中的script_options.yml文件中设置。有关脚本选项的更多信息,请参阅Script Options页面。
须知
为每个脚本定义脚本选项是可选的;脚本可以在没有明确定义选项的情况下正常运行。
脚本权限¶
PySpigot 允许脚本定义其所使用的权限列表。如果脚本想要限制对某些功能的访问,这将非常有用。脚本权限是在解析和执行脚本代码之前初始化和加载的,并在脚本停止后立即移除。
脚本权限是在script_options.yml文件中定义的。有关如何定义权限的更多信息,请参阅脚本选项文档。
脚本加载¶
PySpigot 在插件加载或服务器启动时自动加载和运行脚本文件夹中的所有脚本(包括子文件夹中的脚本)。脚本加载顺序由script_options.yml文件中定义的加载优先级确定。未指定加载优先级的脚本将继承config.yml中script-option-defaults部分指定的默认加载优先级。具有相同加载优先级的脚本将按字母顺序加载。
也可以使用/pyspigot load <scriptname>命令手动加载脚本,如果您希望在服务器启动/插件加载后加载/启用脚本。如果在运行时对脚本进行更改,必须重新加载才能生效。使用/pyspigot reload <scriptname>重新加载脚本。
与加载脚本相关的一个配置选项为:
script-load-delay: 这是 PySpigot 在服务器加载完成后等待加载脚本和项目的延迟时间,以 ticks(刻)为单位。1秒钟等于20个服务器刻。例如,如果值为20,则 PySpigot 将在服务器加载完成后等待20 ticks(或1秒钟)才加载脚本和项目。
注意
Scripts 和 projects 在加载时是交错进行的。换句话说,它们是一起加载的。这意味着脚本和 projects 的加载优先级会同时进行比较,具有较高加载优先级的 project 会比具有较低加载优先级的脚本更早加载,反之亦然。
脚本卸载¶
可以使用 /pyspigot unload <脚本名称> 命令手动卸载脚本。运行 /pyspigot reload 在重新加载脚本之前也会先卸载脚本(如果之前正在运行)。
从脚本内部卸载脚本¶
从脚本内部卸载脚本的方式与在普通 Python 中一样,可以通过使用 sys.exit 函数来完成:
1 2 3 | |
在内部,调用 sys.exit 会引发 SystemExit 异常。PySpigot 捕获此异常并执行其标准卸载任务,以卸载引发异常的脚本。
如果要使用错误信号卸载脚本,将 1 传递给 sys.exit。这样做将防止在卸载时调用脚本的 stop 函数。
警告
不要 使用脚本管理器从脚本内部卸载脚本!这将导致意外的错误和问题。
启动和停止函数¶
在 PySpigot 脚本中,您可以包含两个特殊函数:start 和 stop。
start 函数在加载脚本时由 PySpigot 自动调用。同样,当您的脚本卸载时,stop 函数也会被 PySpigot 自动调用。如果由于错误而卸载脚本,则 不会 调用 stop 函数。这种错误情况还包括通过使用带有退出代码 1 的 sys.exit 来卸载脚本。
start 和 stop 函数可以接受零个或一个参数:
-
如果您定义了一个参数,PySpigot 将会将 脚本对象 传递给该函数。 这个对象是在运行时加载的脚本的表示。 这使您可以获取有关脚本的信息,以及其他关键功能,包括日志记录、脚本文件等等,在
start和/或stop函数内。 -
如果您定义了零个参数,PySpigot 将不会向该函数传递任何参数。
注意
start 和 stop 函数是可选的。 如果不需要,您无需在脚本中定义它们。
pyspigot.py 辅助模块¶
PySpigot 随附一个名为 pyspigot.py 的辅助模块,其中包含各种有用的函数,用于访问 PySpigot 的管理器类。可以通过简单的导入来访问这个模块:
1 2 3 | |
有关更多信息,请参阅 PySpigot 辅助模块 页面。
全局变量¶
PySpigot 包含一个全局变量系统,允许变量在不同的脚本之间共享。 在 Java 方面,该系统由 HashMap 支持,它将数据存储在键值对中,就像 Python 中的字典一样。 这个系统的目的是作为一组全局变量,在多个脚本需要访问相同变量的情况下使用。 如果您希望在多个不同的脚本之间共享变量/数值,这是一个很棒的功能。
请查看 全局变量 页面,详细了解如何使用全局变量系统。
脚本错误和异常¶
脚本可能会产生错误/异常。PySpigot处理脚本生成的所有异常,以便:1)将异常记录到控制台和脚本的记录器中,以及2)在异常是致命的情况下终止脚本的执行。
如果一个脚本在加载时生成一个未处理的错误/异常,脚本将会自动卸载以防止进一步问题。如果以后的某个时间点发生了未处理的错误/异常,比如在事件监听器或命令执行器执行期间,脚本将保持加载,但函数内的后续代码将不会被执行(除非异常被包裹在try/except块中)。
无论如何,错误/异常将记录到控制台和相应脚本的日志文件中(若脚本的脚本选项启用了文件记录)。
注意
由于PySpigot是一个处于活跃开发阶段的项目,您可能会遇到由PySpigot本身内部bug引起的异常。如果您的脚本出现问题,而您的调试工作毫无结果,请在Github上提交一个问题或加入Discord寻求帮助。
脚本可能产生两种类型的错误:
Python错误¶
当脚本自身的代码中出现错误/异常时会发生这些异常。由于这些异常起源于Python代码,调试它们相对比较简单。将显示一个回溯以及引发的错误。下面是Python错误的示例:

Java 异常¶
当脚本调用 Java 代码时,如果异常发生在被调用的 Java 代码中(而非脚本本身内部),就会发生这些异常。这些异常也会生成一个带有 Python 回溯的日志条目,指示导致异常的脚本文件和行数。这里是一个 Java 异常的示例:

这些异常看起来类似于 Python 异常,但除了 Python 回溯外,还会有相应的 Java 异常和堆栈跟踪。伴随 Java 异常的消息(在上图中用红色框起来)通常提供了关于异常发生原因的额外细节。例如,上述异常所附带的消息表明命令“test”已经被注册过了(即脚本尝试注册同一个命令两次)。
PySpigot 经常抛出这些类型的异常(以 ScriptRuntimeException 的形式),用于脚本尝试执行错误/不允许的操作,比如为同一个事件注册两个不同的监听器,或者注册两个同名的命令。如果 PySpigot 内部发生未处理的异常,比如在使用 Bukkit 注册事件监听器时发生异常,也会抛出 ScriptRuntimeException。
Note
Java 异常也可能有一个 cause,这基本上意味着该异常是作为另一个异常的原因而抛出的。如果存在 cause,它也将包含在错误消息中(连同消息和堆栈跟踪)。
处理异常¶
你可以使用Python的try:和except:语法自行处理异常。例如:
1 2 3 4 5 6 7 8 9 10 | |
- 导入适当的Java异常,以便稍后在
tryexcept块中使用。 - 以与接收Python错误相同的方式接收Java
RuntimeException。
脚本日志¶
与脚本本身类似,脚本的记录器是自包含的。每个脚本都有自己的记录器,它是java.util.logging.Logger的子类。PySpigot为每个正在运行的脚本创建一个新的记录器。脚本的记录器会自动分配给其全局命名空间下的变量名logger。要访问您脚本的记录器,使用logger变量。例如:
1 2 3 4 5 6 7 8 9 | |
- 可以立即使用
logger变量,无需首先进行分配,因为当脚本启动时,脚本的记录器会自动分配给logger变量。
访问脚本的记录器时,您可以使用这里列出的任何函数。PySpigot为方便起见添加了两个额外的功能:
注意
截至 PySpigot 0.7.0 版本,通过Python的 print 函数打印的任何消息都会自动捕获并重定向到脚本的日志记录器。这样就更容易区分哪些打印语句来自哪个脚本(如果你有多个脚本在运行),因为脚本的名称附加到所有打印消息中。
[STDOUT] 也附加到打印消息中,以指示消息是打印到 STDOUT 的。
脚本日志文件¶
如前所述,每个脚本都有其自己的日志文件。脚本日志文件可以在 PySpigot 插件文件夹内的 logs 文件夹中找到。与脚本相关的所有消息都将记录在其各自的日志文件中。如果要禁用脚本的文件记录日志功能,请将 file-logging-enabled 脚本选项 设置为 false。
脚本记录器还会记录带有级别的消息。这些级别是Java日志级别。简而言之,这些级别代表日志消息的严重性/重要性。你将看到的三个最常见的日志级别是 INFO、WARNING 和 SEVERE:
INFO: 此级别将消息标记为信息性消息。WARNING: 此级别将日志消息标记为潜在问题,但不需要立即采取行动。SEVERE: 此级别将日志消息标记为严重的故障、异常或错误,需要立即采取行动。
每个脚本记录器都有一个默认的最低级别,用于记录日志消息。默认情况下,该值设置为 INFO。当最低级别设置为 INFO 时,将记录 INFO 级别及更高级别(WARNING,SEVERE)的消息,但不会记录低于 INFO 级别(CONFIG,FINE,FINER,FINEST)的消息。
您可以通过设置min-logging-level脚本选项 来更改脚本的最低记录级别。
每条日志消息中还会打印一个格式化的时间戳,显示日志消息记录的确切时间,使用本地机器的时区。如果您希望使用不同的时间戳格式,请编辑config.yml中的log-timestamp-format 值。
脚本文件中的非ASCII字符¶
Jython 使用ASCII编码读取和编译脚本文件。这意味着它无法识别文件中的非ASCII字符。当您加载包含非ASCII字符的脚本时,可能会出现 SyntaxError。此外,在Python 2中,str 类型是8位字符的集合。因此,英文字母(以及一些基本符号)可以用这些8位字符表示,但特殊符号和非拉丁字母中的字符不能。有几种方法可以解决这两个限制:
解决方法 1¶
Jython 允许您指定脚本文件的编码。通过在脚本中指定编码声明,可以实现此目的。这样可以确保Jython读取和编译文件时能够识别非ASCII字符。在脚本文件的第一行或第二行添加以下内容:
#coding: utf-8
当然,您可以用任何字符编码标准来替换utf-8,指定Jython使用的编码方式。可以查看该页面查看支持的编码方案列表。
警告
您必须在脚本的第一行或第二行上加入编码声明,因为Jython只会在这个区域搜索编码声明。
Python 2 包含了unicode类型,支持所有UTF-8字符(符号,非拉丁字母等)。您可以通过在字符串前添加u来指定要使用unicode类型而不是str。例如:
1 2 3 4 5 | |
- 注意到
u直接在字符串前面。这表示字符串应被视为特殊的unicode字符串,而不是简单的str。
解决方法 2¶
您也可以使用想要编写的unicode字符的十六进制代码。您仍然需要通过在字符串前添加u来指定字符串是unicode字符串,但由于实际上并没有在文件中写入任何非ASCII字符,您无需在脚本文件顶部加入编码声明。例如:
1 2 3 | |