本帖最后由 秋风残叶 于 2021-12-4 23:21 编辑

Denizen新手教程
Denizen Beginner's Guide

译者按
Denizen是一款功能非常强大的脚本引擎类型的服务端插件,如果您对Denizen还不了解请参考这个 站内搬运帖
Denizen虽然功能强大,但学习起来较为复杂,无编程基础的新人想要入门总是十分不易。在以前,Denizen只提供了用来查阅变量/脚本的参考文档,甚至连个入门教程都没有,这更让新手入门雪上加霜。
本插件的搬运者在摸索了插件脚本的基本用法,并在搬运帖中写了少量教程,写的十分外行且不系统,但就算是这样的内容也确实帮助了一些服主,帮助他们燃起对Denizen脚本的兴趣,搬运者十分感谢他们的支持。
现在,想必作者团队也发现了插件入门不易的问题,现改版了旧版的 Denizenscript论坛 并逐步推出了浅显易懂的入门教程,Denizen的搬运者立刻着手于本教程的翻译工作,现发布本译文。

秋风残叶


本教程总共9个大章节,您现在阅读的是第2大章节
迈出第一步
Your First Steps




目录


此部分内容将教你如何整出Denizen的使用环境,且掌握一些学Denizen之前必要的概念性的玩意儿。

  • 搞出一个测试服来
  • EX指令
  • 选择你的脚本编辑器
  • 你的第一个任务型(Task)脚本
  • 你的第一个变量
  • 你的第一个触发型(World)脚本
  • 问题处理


搞出一个测试服来


(译者注:本部分就是教你怎么本地开服,我觉得大家应该都会的吧?不会有人不会吧?会了的可以跳过)

搞出一个测试服来


请在本地进行测试


本地开服是咋弄的捏?



EX指令


开始使用Denizen


Denizen指令和服务器指令


但是你刚刚不是说咱能在游戏里写Denizen指令吗?


前面提到了narrate?“奶瑞艾特”是啥意思?


那我现在该怎么学?


回到标题:指令/ex究竟是干什么的?


所以,Denizen指令和脚本是什么关系?


相关的参考文档



选择你的脚本编辑器


(译者注:这一篇讲的是如何选择代码编辑器,我觉得也没必要阅读,毕竟Notepad++或者VS Code那么普遍了)

脚本编辑器


安装


使用



你的第一个任务型(Task)脚本


任务型(Task)脚本


Task脚本写法


在VS Code里写你的第一个任务型(Task)脚本


脚本完成!


再延伸一点


相关的参考文档



你的第一个变量


变量(Tag)是啥?


变量到底长啥样?


所以变量是咋写的呢?


PlayerTag = Player + Tag


某些变量可以填入内容


未来展望


名词解释


相关的参考文档



你的第一个触发型(World)脚本


坐上来,自己动


当然有!请看这个触发型(World)脚本!


举个栗子


那……他们挖掘的是哪种方块?


别老是拿挖掘方块举例子啦!地都挖秃啦!


事件事件事件!一大串事件!


相关的参考文档



问题处理


脚本根本不运行!我该咋办呀!


Debug信息


Debug信息怎么看?


找大佬求助!


相关的参考文档






来自群组: PluginsCDTribe
2021.12 数据,可能有更多内容 Denizen新手教程
Denizen Beginner's Guide

译者按
Denizen是一款功能非常强大的脚本引擎类型的服务端插件,如果您对Denizen还不了解请参考这个 站内搬运帖
Denizen虽然功能强大,但学习起来较为复杂,无编程基础的新人想要入门总是十分不易。在以前,Denizen只提供了用来查阅变量/脚本的参考文档,甚至连个入门教程都没有,这更让新手入门雪上加霜。
本插件的搬运者在摸索了插件脚本的基本用法,并在搬运帖中写了少量教程,写的十分外行且不系统,但就算是这样的内容也确实帮助了一些服主,帮助他们燃起对Denizen脚本的兴趣,搬运者十分感谢他们的支持。
现在,想必作者团队也发现了插件入门不易的问题,现改版了旧版的 Denizenscript论坛 并逐步推出了浅显易懂的入门教程,Denizen的搬运者立刻着手于本教程的翻译工作,现发布本译文。
秋风残叶



本教程总共9个大章节,您现在阅读的是第2大章节 迈出第一步 Your First Steps


目录

原文链接
此部分内容将教你如何整出Denizen的使用环境,且掌握一些学Denizen之前必要的概念性的玩意儿。
  • 搞出一个测试服来
  • EX指令
  • 选择你的脚本编辑器
  • 你的第一个任务型(Task)脚本
  • 你的第一个变量
  • 你的第一个触发型(World)脚本
  • 问题处理


搞出一个测试服来

原文链接
(译者注:本部分就是教你怎么本地开服,我觉得大家应该都会的吧?不会有人不会吧?会了的可以跳过)


搞出一个测试服来


你看着这个标题也许会想:我已经在开服了啊?难不成这教程还要教我怎么开服?我知道,很多人已经在开服了,但是大家也许更应该拥有一个“正式服”和“测试服”,所以它们的区别是啥?
  • 正式服允许你的玩家正常加入,且比较稳定,通常不会崩服或者出现bug
  • 测试服一般只允许你信得过的人加入,比较脆弱,有可能崩服或者出现bug

当你学写Denizen脚本时,尤其是你还是个新手时,你很有可能把服务器搞崩,或者搞出一些奇葩的事情来(比如每次挖掘泥土都会导致爆炸啥的)请务必在“测试服”里进行脚本测试,不要让你的玩家承担你在服务器里乱搞的烂摊子。


请在本地进行测试


很多人都会把服务端安装在云主机上,哪怕是测试服也是一样,实际上,那应该是“正式服”该承担的位置,实际上,“测试服”应该是一个你可以随时删掉,随时搞崩,随时清空,而不会影响到任何玩家的服务器。
开测试服最好的方法当然是在本地开(别担心,简单的很, 简单到译者翻译到这里都懒得往下译了


本地开服是咋弄的捏?


很简单!只需按照以下步骤:
注意:本教程的背景是在Windows系统内进行开服,因为这个比较普遍(如果其他系统和Windows有所不同都会在教程内标出)
  • 第0步:安装Java,你可能还没在你的电脑上安装Java,你需要先安装一个,你需要先下载 OpenJDK 8 or 11 (如果是Linux/Mac系统,你需要自己百度OpenJDK8或者11咋下载的)
  • 第1步:下载一个服务端核心jar,而且必须下载的是Spigot才行,不过想要通过官方渠道的BuildTools安装Spigot确实蛮麻烦的,如果你真的想用那个方法的话可以读读 BuildTools Wiki 。不过用Paper也许是个更好的选择,你可以在 这里 一键下载服务端。实际上,Paper是基于Spigot的一款更加高效的服务端,也新增了很多功能,更加易用,通常情况下这俩服务端核心互换不会对服务器造成什么影响。很多人喜欢把服务端核心命名为Paper.jar(也就是把版本号什么的删掉)来让第3步里的开服脚本稍微简略一些。




  • 第2步:选择一个服务器文件夹,随便哪个文件夹都行,哪怕电脑桌面(desktop)都行,只需要注意文件夹里不要有别的乱七八糟的文件就行,因为运行服务端核心会额外生成一堆其它文件,请把你下载的服务端核心放到这个文件夹里。




  • 第3步:写一个开服脚本,最简单的写法:请打开你的记事本,把文件命名为start.bat(保存时请注意文件类型要选“所有文件”而非“txt文本文件”)请在记事本内写上java -Xms1G -Xmx1G -jar paper.jar nogui 这个脚本里的变量你是可以编辑的,脚本中两次出现的“1G”代表你想给服务端分配多少RAM(你也可以写2G或者500M什么的都行)“paper.jar”是服务端核心的名字(如果是Linux/Mac系统,请创建一个start.sh并使用你熟悉的文本编辑软件编辑其内容,脚本内容与上面的相同)




  • 第4步:开服运行一次,只需双击开服脚本文件运行即可,成功的话一个CMD风格的命令行窗口会被打开,显示一些消息,然后可能会再度消失,这时你可能会在文件夹里找到一个eula.txt文件,按照Mojang的要求你需要同意eula才能继续,你需要打开这个文件并把false改为true来代表你接受了Minecraft的EULA,暂时还不要重新启动服务器。




  • 第5步:添加插件:请在服务端文件夹里创建一个名叫plugins的文件夹,请下载最新的Denizen、Citizens等插件并将其添加进这个文件夹里,对于测试服来说,可以尝试Denizen的最新 开发版 但如果是正式服,推荐 稳定版




  • 第6步:开服!再次双击启动服务端,这次可能需要等待很久来让一切都配置好,在之后的使用中加载速度会快一些,本次运行中,世界文件夹、插件文件夹等等,都会生成好。




  • 第7步:加入服务器,请在Minecraft多人游戏中添加进你的服务器,服务器IP可以设置为localhost,这个IP代表:服务端和客户端在同一台电脑上,如果万事俱备,你就可以进入你的服务器了!(如果出现错误,你的服务器控制台上可能会有报错内容)你可以在控制台上使用指令将自己添加为OP或做其它任何事情,请随时关注服务器控制台,很多debug信息都会在上面显示。

(译者注:翻译完这一部分让译者十分痛苦TwT,末菠萝菠萝哒哟……)



EX指令

原文链接
开始使用Denizen


正在读这篇内容的你,肯定是一位Denizen的新手,且想要学一些基础的东西,如同前文所述,Denizen是一个脚本引擎,你估计认为自己即将开始在脚本文件里写冗杂的代码了,害怕了不?别急,你暂时还不需要在配置文件里写脚本,我们首先来学一点基础的玩意儿,接下来我即将给你展示Denizen的一个最大卖点:你可以直接在游戏内执行Denizen指令,如同在脚本里运行一样!


Denizen指令和服务器指令


在此之前,我们首先要搞清楚Denizen指令和普通的服务器指令之间的区别。
服务器指令,就是你平时在游戏里敲个斜杠“/”在聊天栏里打出的那个,比如/gamemode creative就是一个常见服务器指令
Denizen指令是可以在脚本中运行的指令,由Denizen脚本引擎运行,比如- narrate "你好呀 <player.name>" 就是一个Denizen指令,请注意这个指令是由横杠“-”打头的(这也是Denizen指令的标准写法)同时也包含了变量(比如<player.name>)这些都是服务器指令没有的特点。
一个不容忽视的要点是这两种“指令”是不能互相代替的,你在服务器里输入指令/narrate "hi <player.name>"只会提示你“未知指令”,而你在Denizen脚本里写上- gamemode creative,运行时也只不过得到报错而已。


但是你刚刚不是说咱能在游戏里写Denizen指令吗?


当然当然,实际上你是可以把服务器指令写进脚本的,其实Denizen提供了一个指令来让游戏内可以直接运行Denizen指令,Denizen指令也是有方法运行服务器指令的。
如果你正在游戏内敲指令,你可以使用游戏内指令/ex来运行Denizen指令,比如/ex narrate "你好呀 <player.name>" 由于/ex是游戏内指令,所以这个指令是管用的。

反过来,想在脚本里运行服务器指令,你可使用指令- execute 比如- execute as_op "gamemode creative" (你还可以使用其他变量,例如 as_player, as_server, 或者 as_npc) 实际上滥用- execute运行服务器指令并没多大意思,因为Denizen指令本身就可以做的比服务器指令更好(从前面gamemode的例子就能看出来)。
另外还需注意:请记住/ex和- execute应该是单独使用的,请不要把这俩指令一块儿用,因为会造成不必要的循环,比如某些用户用的- execute as_server "ex narrate '你好呀'",这种冗余的写法明显没有过脑子,正确的写法应该是- narrate '你好呀'


前面提到了narrate?“奶瑞艾特”是啥意思?


narrate是“旁白”的意思,这个应该是初学者最先接触的Denizen指令:它在运行时会给玩家输出一些聊天栏文字(如果你使用/ex执行narrate,你可能会在控制台上看到一些debug信息,但实际上脚本中运行的narrate不会有这些问题)narrate应该是Denizen最最最简单的一个指令了,同样也最适合用来作为测试用指令,这个指令可以用来绑定NPC来让玩家与NPC交互时和NPC对话,或者是作为自定义指令的反馈(比如当玩家执行了你的自定义指令/command,返回一条信息给玩家)等等。


那我现在该怎么学?


如果我前面讲了这么多你还是没听明白,不妨打开游戏,输入指令/ex narrate "你好呀 <player.name>" 试一试,这也是一个学习经验:接下来的教程里你可能会看到很多脚本例子,而且我们会告诉你这些脚本是啥,有什么用,即便我们不说,你也应该把这些脚本拷贝一份放到服务器里自己运行一下试试(记住,用测试服!)当你熟练到可以自己开始独立写脚本时,我们将会提供带有颜色标识的脚本示例,以方便用户复制,您可以在新窗口打开 此教程 来预览带颜色标识的代码系统。
请注意,学习脚本除了死板地复制粘贴之外,你可以举一反三地改写示例中的脚本,好让你更加透彻地理解脚本每一个单词的用途,同时也算是一个“课后小测验”,当然,你可以随时使用游戏内指令/ex来方便地测试脚本(相比较于复制粘贴来说)


回到标题:指令/ex究竟是干什么的?


你可以使用指令/ex来在游戏内测试脚本,可以测试任何脚本,其功能非常强大,所以我建议你千万不要在正式服务器里把执行指令/ex的权限给你不信任的玩家,你以为我是在唬你?请记住玩家如果有权限执行/ex,那他就可以随时使用指令/ex adjust <player> is_op:true 来让自己成为OP


所以,Denizen指令和脚本是什么关系?


Denizen指令是脚本的核心内容,一个完整脚本文件通常由一条条的Denizen指令组合而成(所谓的“脚本文件”说白了就是表示出什么时候要执行什么Denizen指令而已)
/ex指令可以非常便捷地在游戏内测试Denizen指令,在接下来的教程中也会多次提到它,它是Denizen提供的数款强大工具之一(便捷到能让Java程序员都自愧不如) (译者注:这是原文的观点,不是我的观点)


相关的参考文档


如果你想阅读更多有关指令/ex的内容,也许你可以读读以下链接内容。
请注意:对于Denizen新人来说,以下链接内容过于复杂,不建议现在就阅读,建议继续阅读本指南接下来的部分,当你已经有了Denizen基础能力以后才建议回来阅读链接内容。
EX指令相关文档 narrate指令相关文档



选择你的脚本编辑器

原文链接
(译者注:这一篇讲的是如何选择代码编辑器,我觉得也没必要阅读,毕竟Notepad++或者VS Code那么普遍了)


脚本编辑器


我们建议您使用VS Code进行Denizen脚本编辑,搭配Denizen拓展(译者注:所以Notepad++是真没排面)



安装


首先,下载并安装 VS Code ,请注意不是下载Visual Studio,别下错了!毕竟这俩软件名字这么相似(都怪微软)
当你安装好VS Code之后,请安装 Denizen拓展 ,只需点击绿色的“安装”(Install)按钮即可
另外这个拓展也会自动安装 .NET 5.0 某些用户反馈说安装完这个以后必须重启电脑才行。


使用


请使用VS Code打开你服务端文件夹里的脚本文件夹plugins/Denizen/scripts/(是打开文件夹本身,而非打开某个文件,你可以在编辑器左侧看到文件列表树)
若任意一个文件拥有.dsc拓展,Denizen拓展的功能将会自动启用(出于历史原因 .yml并没有对应的拓展,也就是说以.yml结尾的文件无法显示代码高亮,所以必须使用.dsc)
最重要的一步,如同编辑其它文件一样,请在VS Code里开始编辑你的脚本文件



你的第一个任务型(Task)脚本

原文链接
任务型(Task)脚本
任务型脚本(以下称Task脚本)是可以单独运行的,你可以使用游戏内指令/ex运行,或者在脚本里使用指令run运行。
Task脚本会按部就班地运行其内包含的所有Denizen指令,一个Task脚本可以非常简单也可以非常复杂,甚至可以使用一个Task脚本运行其他Task脚本,正因如此,在脚本与脚本之间的逻辑关系上你必须头脑清楚才行。


Task脚本写法
以下是一个Task脚本的例子
可复制

代码:

  1. example_task:
  2. type: task
  3. script:
  4. - narrate "这是你的第一个task脚本"
可复制


运行这个脚本,会给执行的玩家展示这句话:“这是你的第一个task脚本” 如果你想用指令/ex来运行此脚本,你可以使用/ex run example_task来在游戏内运行,此时玩家本人将会是脚本执行者。


请注意这个脚本范例将会以绿色高亮显示代码,也就是说可以方便地将其复制粘贴进你的服务器来进行测试和学习,本页内容下方的地方还有使用蓝色高亮的脚本代码,其含义是虽然脚本是个好例子但不完整,需要被填入更多内容,之后你还会看到红色高亮的脚本代码,这些是反例,是警示你不要这样写,想查看更多与颜色高亮代码相关内容请参阅 This Guide - Sample Scripts
(译者注:原帖中用不同颜色的框标出了各示例脚本,代表不同的含义,译者将在本帖使用相同颜色的色条来标示每个脚本,比如本示例脚本使用绿**条框出,代表是可以直接复制粘贴用来测试的)


在VS Code里写你的第一个任务型(Task)脚本


你应该已经认真阅读过如何安装VS Code部分


创建文件
想创建id第一个Task脚本,请使用VS Code打开你的脚本文件夹

在文件选择页面右键点击你的脚本文件夹,点击“新文件”(New File)按钮

随便输一个文件名,但请务必以拓展名.dsc结尾,只有这个拓展名可以激活Denizen拓展的功能

现在你可以开始写脚本啦!


那咱们开始写吧!
我们可以用这个Task脚本的模板作为起始
需补充

代码:

  1. my_first_task:
  2. type: task
  3. script:
  4. - narrate (一些内容)
需补充


看起来有点眼熟,没错这个脚本和上面示例那个非常相似。
你需要在第一行首先写出脚本名称my_first_task: (请注意使用下划线_而非空格,再加一个英文冒号:在句尾,然后按回车键切换到下一行,然后按TAB键顶格空出一点继续写,当你继续写下去时,你会发现下面的每一行开头都有TAB空出来的空档,也就是说你换行的时候不需要再敲TAB了,除非你要再往下续一级配置内容)
(译者注:写YAML文件用TAB空格也许不违反YAML语法,但译者个人非常排斥,还是建议各位用space普通空格键留空)


脚本名称
这个脚本的名称叫my_first_task,它是顶格写的(也就是说它的前面没有留空格,而其它行的内容前面都留了4个空格)每一行顶格写的内容都是一个个单独的脚本,比如下面这个例子是两个单独的脚本


可复制

代码:

  1. my_first_task:
  2. type: task
  3. script:
  4. - narrate "这是第一个任务型脚本"

  5. my_second_task:
  6. type: task
  7. script:
  8. - narrate "这是第二个任务型脚本"
可复制


这个例子展示了一个文件里写了两个不同脚本是怎样的,这两个脚本my_first_task和 my_second_task 可以包含在同一个脚本文件 my_first_scripts.dsc 里,只有脚本名称(例如my_first_task)比较重要,而脚本文件名(my_first_scripts.dsc)你可以随便写什么都行,不影响脚本运行。


脚本的种类(Types)
脚本中,脚本名称下方,也就是第二行内容,你可以看到一个Type设置项
Type设置项代表的是这个脚本属于哪种类型,在上面的例子里,我们写的是Task(任务型)类型的脚本,你在其它例子里还能看见诸如world(触发型)脚本、item(物品型)脚本、inventory(容器型)脚本等等,只不过现在,让我们还是从最简单的Task脚本开始学起,你可以在 这里 学到所有类型的脚本
请确保你在脚本第二行写了脚本的类型type: task(从脚本名称位置敲回车键来到下一行,再敲一次TAB键,这将会添加4个空格,之后你每次敲回车键换行,这顶格的4个空格都会一直存在,除非你按删除键删除它们),类似这样:


需补充

代码:

  1. my_first_task:
  2. type: task
需补充
脚本中的Denizen指令
在脚本种类type下方,你会看到脚本内容设置项script,对于Task(任务型)脚本而言,script就是写Denizen指令的地方——你要告诉Denizen,这个脚本该做些什么,别的种类的脚本可能还要别的设置项,那些你之后会学到,如果要添加Denizen指令,效果会类似这样子:
可复制

代码:

  1. my_first_task:
  2. type: task
  3. script:
  4. - narrate "这是一个可以运行的任务型脚本"
  5. - narrate "恭喜你!这是你的第一个脚本!"
可复制


请注意我们是怎么写narrate后面的信息内容的,我们使用英文双引号””来将其括起来,这是因为这句话中包含了空格,我们使用引号将其括起从而确保这个指令只有一个参数,例如 run 1 2 3包含了三个参数1、2、3,如果将其括起来以后run “1 2 3”那么就只有一个参数了。


脚本完成!


恭喜你,现在你已经拥有了一个可以运行的脚本!那还不快来运行一下试试!请在游戏内输入/ex reload来重载所有脚本,随后游戏内输入/ex run (你的Task脚本名称)来在游戏内运行脚本,对于我们上面的例子而言,指令是/ex run my_first_task
如果你运行之后成功在游戏中看到了消息输出,说明我们成功了!老哥你太棒了!



再延伸一点


让我们结合所学知识再往外延伸一点,你需要知道的是我们使用了/ex指令运行了run命令,这玩意是个游戏内指令,让我们再看一个例子,你可以使用task脚本来触发另一个task脚本。
可复制

代码:

  1. my_first_task:
  2. type: task
  3. script:
  4. - narrate "这是一个可以运行的任务型脚本"
  5. - narrate "恭喜你!这是你的第一个脚本"
  6. - run my_second_task

  7. my_second_task:
  8. type: task
  9. script:
  10. - narrate "这是你的第二个任务型脚本"
可复制


这只是一个基础例子,如果是你的话一定可以写出更加复杂和强大的脚本,我是这样坚信着的!


上面例子中的两个脚本my_first_task和 my_second_task 都是单独的脚本,可以写在同一文件里也可写在不同文件里,没有区别。


相关的参考文档
如果你想阅读更多有关Task(任务型)脚本的内容,也许你可以读读以下链接内容。
请注意:对于Denizen新人来说,以下链接内容过于复杂,不建议现在就阅读,建议继续阅读本指南接下来的部分,当你已经有了Denizen基础能力以后才建议回来阅读链接内容。 任务型脚本相关文档 run指令相关文档



你的第一个变量

原文链接
变量(Tag)是啥?


如果你一直在阅读这篇教程,那你肯定已经接触过了所谓的“变量”,比如在ex指令教程中,你使用了指令/ex narrate "你好呀 <player.name>" 其中的<player.name>就是一个变量,就某些其它插件而言,这个词可能也叫“占位符”(Placeholder)但是变量可比占位符功能强大多了。对于变量<player.name>而言,相信你肯定已经猜到了,它在脚本中会被替换为玩家的名称
变量在脚本中使用英文书名号<>来括起来,你可以随时随地使用变量。



变量到底长啥样?
一个简单的变量运用例子 - teleport <npc> <player.location>这个指令可以让一个NPC被传送到玩家所在位置,其中的<npc> <player.location>都是变量
变量的使用也许可以很复杂,类似这样子的:- teleport <player.location.find.living_entities.within[<[range]>]> <player.cursor_on[<[distance]>]||<player.location.forward[<[distance]>]>> ... 别被吓到了嗷!这个例子只是为了让你看看大佬用户是如何运用变量的(而且这还只是入门水平)不过,我们的基础教学里不会包含这么复杂的变量。


所以变量是咋写的呢?
通过上面那个复杂而吓人的变量你也许猜到了,Denizen的变量并非提供一个可选列表,来让你选择该用哪个变量,变量本身是可以通过增加参数来编辑的。


拆解一个变量
一个变量通常包含两部分:基础变量(Base tags)和附加变量(sub-tags)
基础变量
一个简单的基础变量类似<player>,其返回值为一PlayerTag数据类型的玩家,变量可以用圆点区分开来不同部分,比如<server.motd>(尽管这也可以用变量<server>代替)
基础变量部分顾名思义是一个变量的基础,这部分只能被写在变量的开头部分,每个变量都肯定包含一个基础变量。


附加变量
一个简单的附加变量类似<PlayerTag.name>,其返回值为一ElementTag数据类型的玩家名称,一个附加变量在参考文档里常常被写作“xxxTag”类似PlayerTag或者ItemTag,如果变量没有包含这些内容,那它肯定是一个基础变量。
附加变量部分只能被写在变量的结尾部分,每个变量可以包含任意数量的附加变量,可以是0个(就是一个基础变量),1个(一个简单的附加变量),500个(连大神都不敢用的超神级变量)等等等等。


组合起来!
我们前面已经看到了附加变量的例子<PlayerTag.name>,如果一个附加变量以PlayerTag打头,其含义是“这个位置应该填入一个玩家”,我们都知道基础变量<player>代表玩家,那么刚刚好,直接组合起来,就变成了<player.name>,返回的是玩家名称。


稍微写长一点?
变量<PlayerTag.target>返回的是玩家正在盯着的实体,如果此时玩家正盯着另一个玩家,返回的则是PlayerTag数据,也就是说,我们可以把一个“不管是啥只要是PlayerTag数据类型的数据”也就是<player>填入之前的变量,得到<player.target>,在刚才的例子里,玩家盯着看的是另一个玩家,那么我们还按照之前的原则,从<PlayerTag.name>开始,把<player.target>填入进去,得到的就是<player.target.name>,这个变量返回的就是玩家盯着的另一个玩家的名称!



PlayerTag = Player + Tag
不难理解,PlayerTag指的是一个玩家,使用一个变量表示,我们再举一反三一下,NPCTag指的是一个NPC用一个变量表示,EntityTag指的是一个实体用一个变量表示,ItemTag指的是一个物品用一个变量表示……诸如此类,你可以用变量表示很多东西。
哦对了,我们之前还提到过一个ElementTag,这个变量有点牛X:它可以表示任何种类数据!它可以是一串数字,一段文字,甚至一个玩家!


我都被绕糊涂了,所以玩家就是PlayerTag,对吧?
是的,玩家就是PlayerTag,玩家同样也是EntityTag,同样也是ElementTag
在Denizen里,对象的种类可以细分为子种类,比如PlayerTag和NPCTag都是EntityTag的子种类,举例说明,任何需要EntityTag的地方(比如<EntityTag.health>)填入一个PlayerTag或者NPCTag都是OK的,这也就解释了为什么<player.health>返回的是玩家血量。
ElementTag是最基础的变量种类,任何变量都是其子种类,任何需要ElementTag的地方,任何种类的Tag都可以填入,PlayerTag, NPCTag, ItemTag还有别的什么乱七八糟的Tag,随便填入。


某些变量可以填入内容
某些变量需要被填入一些具体的值,例如变量<util.random.int[<#>].to[<#>]>需要被填入两个数值,其返回值是这两个数值之间任意一个数字
最基础的,这些需要被填入数值的地方填入什么数值都可以,因此你可以在游戏中使用指令/ex narrate <util.random.int[1].to[10]>,然后它返回的肯定是1到10之间的随机数字,多重复几次,返回的值肯定都不同(当然也有可能重复)

某些变量还能填入变量
往变量中填入一个数字也许很简单,但很多时候你需要往变量里填入一个动态变化的数字,那咋办?请看下面这个例子:


可复制

代码:

  1. slowly_dying:
  2. type: task
  3. script:
  4. - narrate "你现在有 <player.health> 血量,你以为这很多?"
  5. - hurt <player> <util.random.decimal[0].to[<player.health.div[2]>]>
  6. - narrate "看招! <player.name>! 现在你只剩 <player.health> 血量啦!"
可复制


每次这个脚本被运行,玩家都会损失某随机数字到一半血量之间的随机血量,当脚本执行完毕,还会输出玩家剩余的血量值
你可以看到,例子中变量&lt;player.health.div[2]&gt;被塞进了变量&lt;util.random.decimal[&lt;#.#&gt;].to[&lt;#.#&gt;]&gt;之中。
内变量的作用是获得玩家相关的数据,&lt;PlayerTag.health&gt;指的是玩家的血量,&lt;ElementTag.div[&lt;#.#&gt;]&gt;指的是将其二等分之后的值。


未来展望


在未来,当你真正写脚本时,我想你应该不会总是在变量文档里翻找合适的变量,我们会在 这里 寻找合适的变量创意,并将其添加进Denizen中。


名词解释
本文中提到了不少专有名词,很多你应该已经猜出其含义了,但是毕竟是新手向的指南,这里还是解释一下某些专有名词的含义。
  • 返回(Returns) 当我们讨论变量时,我们总是说“变量&lt;PlayerTag.name&gt;返回的是玩家名字”,其含义是,当我们使用这个变量时,其会被替代为玩家的名字,所以指令- narrate &quot;你好呀 &lt;player.name&gt;&quot; 在真正执行时显示的应该是“你好呀 Andy”。“返回”这个词按道理来说指的是“替代”,意指“变量&lt;PlayerTag.name&gt;使用时会被玩家名字替代”。
  • 对象(Object) 指代我们讨论的一些内容,我们之前一直在讨论不同种类的“对象”比如一个PlayerTag就是一个对象,NPCTag也是一个对象,当脚本执行时,玩家就是对象,玩家对象指代的就是PlayerTag变量。



相关的参考文档
如果你想阅读更多有关变量(Tags)的内容,也许你可以读读以下链接内容。
请注意:对于Denizen新人来说,以下链接内容过于复杂,不建议现在就阅读,建议继续阅读本指南接下来的部分,当你已经有了Denizen基础能力以后才建议回来阅读链接内容。
变量反馈相关文档 ObjectTag相关文档 通用对象与特殊对象 ElementTag相关文档 对象种类相关文档 全部变量列表



你的第一个触发型(World)脚本

原文链接
坐上来,自己动
现在你肯定已经学会了 如何使用/ex指令执行Denizen命令 以及 如何使用Task脚本来运行一组Denizen命令 ,尽管它们功能很强大,但还是需要你主动在游戏内输入指令才能运行,试想一下Denizen万事都需要敲指令/ex来执行,这情况让人想都不敢想吧?那么,究竟有没有一种脚本,可以自动被触发并运行,不需要我们手动操作呢?


当然有!请看这个触发型(World)脚本!
让我们先看看这种可以“自动触发”的脚本是咋写的。
一个触发型脚本(以下简称World脚本)包含了events部分,“event”指的是游戏世界里发生的一系列事件,比如,当玩家挖掉一个方块,这就是一个“event事件”,很多event事件光看名字就知道它的含义了……比如上面提到的玩家挖掘方块,其事件名为player breaks block,请注意任何一个事件都需要以单词on或者after打头,事件的写法也基本遵循英语语法,类似这样:on player places block, after entity dies, on creeper powered, after lightning strikes(关于on和after的区别我们后面会讲)


举个栗子


可复制

代码:

  1. my_world_script:
  2. type: world
  3. events:
  4. after player breaks block:
  5. - narrate "玩家 <player.name>, 你刚刚挖掉了一个方块!"
可复制
请把这个脚本放进你的脚本文件夹,使用指令/ex reload加载并运行,然后在游戏内挖掘个方块试试,你会在服务器里看到相应的文字消息输出。

在这个例子里需要注意的点:
  • 脚本的种类type是world
  • 在我们前面学的Task脚本里,我们用script:列出Denizen指令,但现在我们使用events:列出事件名。
  • 事件名(after player breaks block)距离上一行(events:)开头对齐部分空出了4个空格,这也是events和scripts写法不同之处。
  • 上面的例子里,挖掘方块的玩家即为本脚本的触发玩家,所以narrate指令也会朝该玩家显示,类似&lt;player.name&gt;变量显示的也是该玩家的信息。

只要事件名中的事件被触发,换句话说只要玩家挖掘方块,这个脚本就会被触发,如果5名玩家同时挖掘方块,脚本就会被运行5次——当然是针对他们每一个玩家,假如某个玩家有一把效率V附魔的铲子然后一下子挖掉了10个泥土方块……那这个脚本就会运行10次。


那……他们挖掘的是哪种方块?
所以现在,如果你看到了上面的脚本被触发,那么你知道肯定有玩家在服务器里挖掘了方块,那么,如何得知他们挖掘的方块是哪一种?(如果只是监听挖掘方块事件,那挖掘泥土块和挖掘钻石块从原理上并无区别)我们有两种方法:


环境变量(Context Tags)
第一种方法是使用环境变量,顾名思义,环境变量的作用是对这个脚本何时/何地/何故运行相关细节进行描述,在事件中,环境变量包含了一切关于这个事件的细节信息。
在上面的事件on player breaks block中,环境变量&lt;context.material&gt;数据类型为MaterialTag,指的就是玩家挖掘的方块,我们之前还学过附加变量,附加变量&lt;MaterialTag.name&gt;返回的就是这个方块的名称,所以组合起来以后就是&lt;context.material.name&gt;
想把这个变量运用进narrate消息中,你可以写成- narrate &quot;Whoa &lt;player.name&gt;, you broke a &lt;context.material.name&gt;!&quot; 重载脚本并再次尝试游戏内挖掘方块,你就可以看到你挖掘的方块的名称了!

请注意环境变量等同于我们之前学的基础变量(Base Tags),环境变量本身永远不会是附加变量(sub-tags),但你可以在其后面加内容来将其写成一个附加变量。
请注意,事件的环境变量只在这一个事件里有效,例如事件on entity dies不可能包含环境变量&lt;context.material&gt;,因为根本没有material嘛,同样地,一个Task任务型脚本或者/ex指令也不会拥有环境变量,因为环境变量只和事件挂钩


让事件更具体一些
事件名和前面介绍的变量一样,里面也是可以加内容的。
挖掘方块的事件名可以写作player breaks block 或 player breaks &lt;material&gt;,对于后者而言,我们可以看到一个变量&lt;material&gt;,我们前面已经讲过变量就是“可以填入内容的地方”,所以,不妨尝试一下这个事件名after player breaks stone:,重载插件以后再挖掘几种不同的方块试试,你会发现只有你挖掘石头方块时才能触发提示语,挖掘泥土或者其它方块,什么都不会显示。
除了往事件名中填入具体的变量之外,你还可以给事件名写入更多附加的内容,比如这个with:&lt;item&gt; 来让玩家只有使用某种具体物品挖掘方块才能触发脚本。
举例说明:事件after player breaks stone with:diamond_pickaxe: 代表只有玩家使用钻石镐挖掘石头时才会触发,如果你使用的是石镐或别的什么工具,脚本不可能生效的。


不要在事件里加入变量!
请务必注意事件这一行脚本不应该添加任何变量——也就是说事件这行字看起来应该是一行正常的英文句子,在【接下来的部分】你可以学到更多可触发的事件。
也许你会觉得,事件里不给用变量会不会显得太死板?例如事件中的with:diamond_pickaxe 能不能把它写成使用任何种类的镐子挖掘都能触发呢,当然可以,你可以写成with:*_pickaxe,其中的“*”符号代表“这里填入什么都可以”,另外,我们还可以使用类似这样的写法with:diamond_pickaxe|iron_pickaxe 其中的“|”符号代表“这些选项中任意一个均可触发”,这些写法同样也可以用在事件本身中,例如,你想让玩家挖掘任意种类木头方块时触发,但你又不想把所有种类的木头全都写进去,那么你就可以写成after player breaks *_log:


别老是拿挖掘方块举例子啦!地都挖秃啦!
现在你已经学会了简单的触发型脚本,当某个事件发生时相应的脚本会被触发,同时你也学会了给事件添加更多细节,那么我想你也许会好奇:被触发的脚本指令,能不能直接作用于事件本身呢?比方说直接阻止玩家挖掘方块?也许下面这个narrate脚本有点帮助

代码:

  1. - narrate "我求求你别挖了"

觉得我在逗你玩?好吧我是开玩笑的,接下来我要介绍的是determine指令,这个指令的功能为对触发的事件本身进行干预,对于一个正常的事件触发来说,覆水难收,触发了就是触发了,但是一旦我们使用了determine指令,我们可以直接对事件触发本身进行修改(请注意determine只能修改事件本身,修改不了别的,如果你想达到别的修改目的请你使用别的Denizen指令)
determine脚本最常见的用法是- determine cancelled,顾名思义:取消事件,使用这个指令之后触发的事件会被立刻取消,在上文中的挖掘方块(breaks block)例子中,如果我们使用determine指令取消事件,那玩家哪怕把手都挖烂了也挖不掉方块的。(对于玩家的客户端来说不会有任何可见提示,玩家只会看到方块被破坏之后瞬间恢复原状,这是Minecraft客户端本身的特性,改变不了的)
所以我们来写个脚本试试看吧!某些用户会在narrate指令的下一行加一个determine指令来取消挖掘方块事件,类似这样
反面例子

代码:

  1. my_world_script:
  2. type: world
  3. events:
  4. after player breaks stone with:diamond_pickaxe:
  5. - narrate "玩家 <player.name>, 你刚刚挖掘了一个 <context.material.name>!"
  6. - determine cancelled
反面例子
运行一下……咦?阻止挖掘方块的机制根本没有生效!玩家还是把方块挖掉了!你是不是在骗我?


on和after——事件之前与事件之后
先把你手里的40米长的大刀放下,接下来我要告诉你的就是事件开头的on和after的区别。after代表这个事件已经发生之后再触发脚本,而on代表事件发生之前就触发脚本,使用after时,这事件早就已经发生过了,而使用on时这事件还没发生,所以,如果你要使用determine指令取消事件的触发,必须搭配on而非after,道理也好懂:你是无法改变过去的!
上面那个例子中,我们用on代替after以后:
可复制

代码:

  1. my_world_script:
  2. type: world
  3. events:
  4. on player breaks stone with:diamond_pickaxe:
  5. - narrate "玩家 <player.name>, 你刚刚挖掘了一个 <context.material.name>!"
  6. - determine cancelled
可复制
这个脚本的功能是:如果你使用钻石镐挖掘一个石头方块,事件会被取消从而导致你无法挖掉方块,但如果你挖掘的是石头以外别的方块,或者你用的不是钻石镐,那这个取消无法生效。

请注意,determine指令一般情况下应该写在一系列脚本指令的最后一行,但如果你是想在修改事件之后继续运行其他指令(比如继续运行其它determine脚本来修改事件的其它部分),你可以不用将其写在最后一行,但务必在脚本指令中加一个单词passively,类似这样- determine passively cancelled
(译者注:- determine passively cancelled这个指令脚本对于添加新指令的脚本而言非常非常常用,通常会写在第一行,Denizenscript论坛提供的很多成品脚本文件中你几乎都能找到这条内容,因为你使用的脚本添加的“新指令”无法被Bukkit识别,因此即便脚本可以正常运行,Bukkit还是会给玩家输出“Unknown command”消息,因此就要使用这个determine脚本,在脚本首行把使用指令事件取消,再接着写脚本触发内容。)
(译者注:关于determine指令,Denizen插件的搬运帖中举了一个非常直观的“不爆炸的苦力怕”例子,还提供了脚本写法,建议一并参考阅读)



改变事件,改变世界
如果你是想要“修改”一个事件,而非直接将其取消,很多事件都包含了可设置的内容以方便修改,例如上文的挖掘方块事件,包含了一个设置项nothing来让生存模式下挖掘方块不掉落产物,你也可以将一种或一系列物品写入方块的不掉落列表中,来让挖掘此方块不会掉落相应物品。
让我们用之前所学的变量知识来举个例子,首先使用附加变量 &lt;MaterialTag.item&gt;来获得某方块的物品形式,再将其和环境变量&lt;context.material&gt;组合起来一起使用,最终我们得到- determine &lt;context.material.item&gt;,那么现在你就拥有了“精准采集”附魔的超能力!也就是说每次你挖掉方块,掉落的永远是方块物品本身而非其原版的掉落物,比如你挖掘钻石原矿,掉落的就是钻石原矿方块而非钻石(哦差点忘了,前面的脚本例子里你要把事件名里的stone改**lock,或者改成你要测试的方块名称,钻石原矿diamond_ore啥的)


事件事件事件!一大串事件!
你还需要知道的是:一个触发型(World)脚本可以包含不止一个事件,脚本写起来可能是这样的:
可复制

代码:

  1. my_world_script:
  2. type: world
  3. events:
  4. after player breaks block:
  5. - narrate "玩家 <player.name>, 你刚刚挖掘了 <context.material.name>!"
  6. on player breaks stone with:diamond_pickaxe:
  7. - determine cancelled
  8. after player places stone:
  9. - narrate "你干嘛要在这里放石头?你想用钻石镐挖它们吗?"
可复制


相关的参考文档
如果你想阅读更多有关触发型脚本以及事件的内容,也许你可以读读以下链接内容。
请注意:对于Denizen新人来说,以下链接内容过于复杂,不建议现在就阅读,建议继续阅读本指南接下来的部分,当你已经有了Denizen基础能力以后才建议回来阅读链接内容。
“开关”相关文档 “玩家开关”相关文档 进阶匹配相关文档 事件取消相关文档 特殊环境变量相关文档 on和after的区别 事件中的安全问题 优先级相关文档 Bukkit优先级相关文档 触发型脚本相关文档 所有可触发事件列表



问题处理

原文链接
脚本根本不运行!我该咋办呀!
对于初学者来说,通常会发生:费心费力写好一个脚本,加载进服务器,然后……啥都没发生?脚本有可能根本不工作,也有可能乱来一气,搞出很多bug来,那该咋办?


Debug信息
Denizen有个很重要的功能:详细的debug信息!你的脚本每一步都做了些啥都会在服务器控制台的debug信息里显示出来(除非你在脚本中设置了debug: false来关闭脚本debug信息输出)debug信息可以让你更快地发现脚本问题所在。



Debug信息怎么看?
控制台上的Debug信息通常会列出脚本包含的所有Denizen指令,当脚本运行时,Debug信息会按部就班显示所有指令的执行情况,变量的填入情况,有时也会输出某些指令的执行结果等,所有变量也会直接显示为填入内容后的形式,这些指令的运行情况也会以队列(queue)的形式展示(每个队列都会有不同的名字,用不同的颜色高亮区分开来)


慧眼识bug
当脚本出现异常运行结果时,你估计会对照着debug消息,一行一行看到底是哪里出了问题(举个例子,你有可能会发现一个实体变量实际返回的名称是“zombie_pigman”但你误以为应该返回的是“pigzombie”,结果接下来的指令都是围绕你误以为的错误内容而写的,因此,整个脚本全废)


错误(Errors)
某些幸运的情况下,你的脚本中的错误会直接被Denizen抓到,它会直接输出红色的ERROR! 然后输出它发现了什么。
在这些情况中,错误提示会直接告诉你脚本哪里错了,比如写变量时不小心把教程里包含变量种类Tag的附加变量&lt;PlayerTag.name&gt;写进去了(正确的应该是&lt;player.name&gt;)运行之后会出现十分清晰的错误提示:
'ObjectTag' notation is for documentation purposes, and not to be used literally. An actual object must be inserted instead. (译者注:ObjectTag只是在参考文档里的书写形式,不能被写进实际的脚本中,你需要写入一个具体的变量)
在另一些情况下,错误提示可能会指出一些语法性错误,但可能不会指出哪行脚本出了问题,也不会告诉你正确的该怎么写,举例:比如你写双引号把文字括起来的时候漏了一边,运行脚本时错误提示就会输出(some word) is an unknown argument! 这是因为Denizen引擎还没有聪明到能猜出你想做啥,它只能告诉你你输入的内容运行不了(其实Denizen已经很努力在猜你究竟哪里写错了,比如上面那个变量的例子,最新版本的Denizen已经在尝试分析你是不是漏了一边引号这一类的错误)当遇到这些情况,你只能自己找到脚本中的问题所在,一般来说,debug信息中Denizen输出的错误提示总是和出现bug的脚本位置靠的非常近,很有可能根本就是错误提示的上面一条内容出了bug,请注意脚本的出错之处永远应该位于错误信息输出之前,而不是之后。


找大佬求助!


某些情况下,你就算把眼珠子都瞪裂了,估计还是找不到一些隐蔽的脚本bug所在,这时你就需要寻求别人的帮助,让别的大佬看看你的代码来发现你漏看的部分。
最好的寻求帮助的地方当然是Denizen的官方Discord群组,本群组有脚本讨论频道#denizen,你可以在这里和其他脚本用户一起讨论脚本,本群组还有很多机器人可以帮助你查询脚本文档(不妨试试发送一个!help),自动分析脚本出错之处(使用 !ds),在#bot-spam频道你可以探索机器人更多功能。

当你想拿着你写的脚本找别人提问时,建议先把脚本内容粘贴至 这里 并提供一个粘贴后的链接,同时也别忘了提供debug信息,你可以使用游戏内指令/denizen debug -r复现debug信息,然后游戏内输入/denizen submit 它会自动生成一个粘贴debug信息后的链接,这样你就可以方便地把这些内容分享给别人看了。
当你加入我们的Discord群组以后,先别急着输入任何聊天内容,请务必认真阅读#rules频道里的群规,以避免踩雷,discord总体上是个友好的聊天平台,但群规依旧森严(别担心,犯一点点小错误不会立刻就被封,每一次封禁之前都至少有一次警告,除非你一直作死。)
需要做好心理准备,如果你在群里贴了一份错误百出的脚本,群里的老哥也许会直接骂你,并一条一条指出你的脚本错误之处,请注意骂人并非我们的本意,不要往心里去,毕竟文字性质的聊天内容也许会非常伤人,甚至超出发言者本来用意。请注意我们的本意是想要教你学会使用脚本,并能独立写出完美的脚本,如果愤怒真的是我们的本意,估计我们根本不会理你。
(译者注:Discord国内无法正常连接without a ladder. 对于MCBBS用户也许“联机问答”版块是一个提问的好地方。此外需注意Discord聊天环境与国内的QQ不同,滥用刷屏或者艾特等等惹人厌的行为将会导致你被封禁)


相关的参考文档
如果你想阅读更多有关脚本问题解决的内容,也许你可以读读以下链接内容
请注意:对于Denizen新人来说,以下链接内容过于复杂,不建议现在就阅读,建议继续阅读本指南接下来的部分,当你已经有了Denizen基础能力以后才建议回来阅读链接内容
debug指令相关文档 submit指令相关文档