• 因近日遭受攻击,百科现已限制新用户的编辑权限,一段时间后成为自动确认用户方可编辑。

Difference between revisions of "Bukkit/插件开发教程"

From Minecraft插件百科
Jump to: navigation, search
(简单翻译部分语句)
(Tag: mobile edit)
m (小贴士)
 
(154 intermediate revisions by 18 users not shown)
Line 1: Line 1:
{{模板:待翻译}}
+
{{待完善}}
 +
{{待更新|url=http://wiki.bukkit.org/Plugin_Tutorial}}
 
本页面英文原文内容来源 [http://wiki.bukkit.org/Plugin_Tutorial]
 
本页面英文原文内容来源 [http://wiki.bukkit.org/Plugin_Tutorial]
 +
 
== 介绍  ==
 
== 介绍  ==
  
这篇内容丰富的教程旨在帮助你学会如何开发Bukkit插件.这一篇教程不可能完全涉及到Bukkit中所有的可能性,而是最基本的一般概述. 前提是你懂得Java这门语言,并在IDE中设置好你的工作区,此教程介绍了大多数Bukkit插件所必需的语法结构.
+
这篇内容丰富的教程旨在帮助你学会如何开发Bukkit插件,以及部分实例的基本概述.但由于Bukkit涉及Minecraft中的几乎所有元素,所以此教程并未包含Bukkit中所有的内容,你需要<big>发挥自己的想象和随机应变</big>.
  
 
== 学习Java  ==
 
== 学习Java  ==
  
'''理解这些教程需要具备Java这门编程语言的基本知识.'''如果你刚开始学习Java或者需要重温一下相关知识,下面有一个并不完整的网站列表可供参考.
+
'''理解这些教程需要具备Java这门编程语言的基本知识.'''如果你刚开始学习Java或者需要重温一下相关知识,下面有一个并不完整的网站列表可供参考.当然如果你是初学者并且不那么急于求成的话仍然建议你购买些入门书籍,以便更好的让Java和Bukkit听我们的命令办事.
  
 
<big>[http://docs.oracle.com/javase/tutorial/ Oracle's Java Tutorials]</big> - 官方教程
 
<big>[http://docs.oracle.com/javase/tutorial/ Oracle's Java Tutorials]</big> - 官方教程
Line 14: Line 16:
 
*[http://math.hws.edu/javanotes/ JavaNotes] - 免费的在线教科书
 
*[http://math.hws.edu/javanotes/ JavaNotes] - 免费的在线教科书
 
*[http://thenewboston.org/list.php?cat=31 TheNewBoston] - 视频教程.
 
*[http://thenewboston.org/list.php?cat=31 TheNewBoston] - 视频教程.
 +
*[http://www.yiibai.com/java/java_quick_guide.html JavaQuickGuide] - 易百快速入门教程
 +
 +
https://docs.oracle.com/javase/8/docs/api/    JavaSE的Javadocs
 +
 +
若须完全理解教程,你需要掌握这些内容:
 +
 +
*Java的基本语法及相关数据处理(例如int,float,String等),包括标识符,基本数据类型,数组等;
 +
 +
*Java的基本语句,包括if-else,switch,for,while,do-while等;
 +
 +
*Java的面向对象思想,基类子类接口的概念和他们的相关用法;
 +
 +
*Java的多文件(包)以及访问修饰符;
 +
 +
*Java中容器的定义及使用.
  
 
== 开发环境  ==
 
== 开发环境  ==
  
在研制一个插件前 (或学习Java前) you will need to set up a development environment. This includes but is not limited to installing an IDE (Integrated Development Environment). The following tutorial contains instructions for the Eclipse IDE.
+
在编写一个插件前 (或学习Java前) 你需要配置一个开发环境,为了更方便的开发,你需要一个IDE.它可以帮助你更好的管理项目文件和编译调试代码.
  
:: ''For further information, see [[Setting Up Your Workspace]]''
+
Integrated Development Environment,集成开发环境
  
You '''MUST''' download the Eclipse build for '''Java''' developers, '''NOT''' the one for '''Java EE''' developers. The version for Java EE developers does not ship Maven support, which is required for this tutorial.
+
接下来的教程中有关于Eclipse的操作指南。
 +
 
 +
:: ''更多信息,请参见 [[Bukkit/插件开发教程-建立工作区|建立工作区]]''
 +
 
 +
'''必须'''下载'''Java'''开发者使用的Eclipse构建版本, '''不是''' '''Java EE'''开发者的版本。 Java EE 开发者的版本不包含此教程需要的Maven支持。
 +
 
 +
或者使用更加方便快捷的NetBeans编写插件,当然你也可以使用Idea,但很抱歉此教程暂时不包含Idea的相关教程.
 +
 
 +
[[文件:NetBeanIDE-FristSee.jpg]]
 +
 
 +
下载地址:
 +
 
 +
https://netbeans.org/downloads/index.html
 +
 
 +
下载指定的安装包
 +
 
 +
[[文件:NetBeanIDE-download.jpg]]
 +
 
 +
当然安装前需要JDK,下载网站: 
 +
 
 +
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html   
 +
 
 +
打开安装程序后点击
 +
下一步 > 我接受许可证协议中的条款 > 下一步
 +
要不要Juit你来定 > 下一步 > 安装
 +
 
 +
== 了解BukkitAPI和学看它的Javadocs ==
 +
 
 +
=== 结构与Java文档 ===
 +
<big>工欲善其事,必先利其器</big> 我们写的是Bukkit插件,当然要先熟悉他啦.
 +
 
 +
注意,要区分CraftBukkit和Bukkit,前者是服务端,而后者是一套API接口
 +
 
 +
众所周知Minecraft的官方服务端是不支持任何插件的,但CraftBukkit不同于官服的地方是他封装了一套独有的接口以及为插件提供了相关的初始化代码,使我们的插件能够被加载,这就需要我们明白Bukkit初始化流程,以及如何使用相关接口,那么问题来了,怎么了解它们呢?
 +
 
 +
<big>反编译去看那些上万行的代码!?</big>
 +
 
 +
诶你还别说,如果没有Javadocs的话我们确实需要这样做。
 +
 
 +
Javadocs 是一种关于程序的帮助文档,帮助开发者更了解程序的构造
 +
 
 +
所幸的是Bukkit有Javadocs ,在 https://hub.spigotmc.org/javadocs/bukkit/ 上
 +
 
 +
 
 +
如果你看不懂英文,那么https://docs.windit.net/Chinese_BukkitAPI/应该可以帮到你
 +
 
 +
 
 +
全景图
 +
 
 +
[[文件:BukkitAPI-Javadocs-1.jpg]]
 +
 
 +
[[文件:BukkitAPI-Javadocs-2.jpg]]
 +
 
 +
[[文件:BukkitAPI-Javadocs-3.jpg]]
 +
 
 +
 
 +
---
 +
 
 +
解压一个服务端(Craftbukkit/Spigot)查看
 +
 
 +
[[文件:BukkitAPI-see-1.jpg]]
 +
 
 +
进入<big>org</big>文件夹 (在Java中这叫<big>包</big>)
 +
 
 +
 
 +
[[文件:BukkitAPI-see-2.jpg]]
 +
 
 +
看,BukkitAPI就在这里,这是一个Spigotmc服务端,所以也可以看到spigotAPI
 +
 
 +
Spigotmc是依靠BukkitAPI的,所以spigotAPI分量很轻(自己动手去看就知道了)
 +
 
 +
 
 +
[[文件:BukkitAPI-see-3.jpg]]
 +
 
 +
橙色框内的就是包,红框(及以下)是编译好的class文件
 +
 
 +
和全景图连起来看看,是不是一一对应。
 +
 
 +
额...那个...说好的<big>class</big>呢?
 +
 
 +
点击Javadocs的 org.bukkit 超链接
 +
 
 +
[[文件:BukkitAPI-Javadocs-4.jpg]]
 +
 
 +
就在这里
 +
 
 +
全景图拍摄的是<big>Overview</big> ,它显示的只是包和它的分支而已
 +
 
 +
拿 org.bukkit.block 和 org.bukkit.block.bander 举例
 +
 
 +
<big>block</big>
 +
 
 +
[[文件:BukkitAPI-Javadocs-5.jpg]]
 +
 
 +
[[文件:BukkitAPI-see-4.jpg]]
 +
 
 +
 
 +
<big>block.bannder</big>
 +
 
 +
[[文件:BukkitAPI-Javadocs-6.jpg]]
 +
 
 +
[[文件:BukkitAPI-see-5.jpg]]
 +
 
 +
一般情况下,许多人说的 <big>去看API</big> 实际意思是看BukkitAPI的Javadocs
 +
 
 +
=== other ===
 +
 
 +
building …
  
 
== 新建插件开发项目  ==
 
== 新建插件开发项目  ==
 +
 
=== 创建一个新的项目 ===
 
=== 创建一个新的项目 ===
 +
 +
==== Eclipse ====
  
 
在开始工作之前,你需要先在Eclipse中配置好工作区和文件. 打开Eclipse,然后依次点击''File -> New -> Project:''来创建一个新的项目.
 
在开始工作之前,你需要先在Eclipse中配置好工作区和文件. 打开Eclipse,然后依次点击''File -> New -> Project:''来创建一个新的项目.
Line 61: Line 188:
  
 
点击工程名称右边的箭头来进行下一步,现在我们正式开始。
 
点击工程名称右边的箭头来进行下一步,现在我们正式开始。
 +
 +
==== Netbeans ====
 +
 +
先创建项目
 +
 +
[[文件:Nb1.jpg]]
 +
 +
[[文件:TNb2.jpg]]
 +
 +
接下来更改主类中的内容
 +
 +
[[文件:Nb3.jpg]]
 +
 +
当一个类继承Javaplugin后 ,它就是插件的主类
 +
 +
不允许其他类继承主类。
 +
 
=== 添加Bukkit API ===
 
=== 添加Bukkit API ===
 +
 +
==== Eclipse ====
  
 
在开发插件之前,你需要添加 Bukkit API库文件到你的项目,作为一个dependency, 你也可以添加其他你想实用的API.
 
在开发插件之前,你需要添加 Bukkit API库文件到你的项目,作为一个dependency, 你也可以添加其他你想实用的API.
Line 93: Line 239:
 
     <repositories>
 
     <repositories>
 
         <repository>
 
         <repository>
             <id>bukkit-repo</id>
+
             <id>spigot-repo</id>
             <url>http://repo.bukkit.org/content/groups/public/</url>
+
             <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
 
         </repository>
 
         </repository>
 
     </repositories>
 
     </repositories>
Line 114: Line 260:
  
 
你也可以修改这里的Bukkit版本.你可以在[http://repo.bukkit.org/content/groups/public/org/bukkit/bukkit/ here]这里通过查看 maven-metadata.xml文件下的版本列表来获取可用的服务端版本号.
 
你也可以修改这里的Bukkit版本.你可以在[http://repo.bukkit.org/content/groups/public/org/bukkit/bukkit/ here]这里通过查看 maven-metadata.xml文件下的版本列表来获取可用的服务端版本号.
 +
 +
{{Hide|标题=译者注 |内容= 官方网站上的仓库地址丢失无法访问,更换为新地址,经测试可以使用。}}
  
 
当你完成上述步骤后,''pom.xml''中的内容应该是这样:
 
当你完成上述步骤后,''pom.xml''中的内容应该是这样:
Line 120: Line 268:
  
 
通过菜单 ''File -> Save''或者按住 <code>Ctrl + S</code> 来保存''pom.xml''文件 .  之后, 右键项目标题,然后依次选择 ''Maven -> Update Project''.
 
通过菜单 ''File -> Save''或者按住 <code>Ctrl + S</code> 来保存''pom.xml''文件 .  之后, 右键项目标题,然后依次选择 ''Maven -> Update Project''.
 +
 +
==== NetBeans ====
 +
 +
这个相对简单,先创建库
 +
 +
[[文件:NetBeansIDE-lib.jpg]]
 +
 +
[[文件:NetBeansIDE-lib-setin.jpg]]
 +
 +
然后添加<库类路径>,一般是服务端核心文件
 +
 +
[[文件:NetBeansIDE-lib-use.jpg]]
 +
 +
由此完成设置
  
 
=== Bukkit Java文档 ===
 
=== Bukkit Java文档 ===
  
如果你曾经使用过Eclipse和Java,你会知道当你将鼠标准心移至class或者 method中的代码部分时,一个含有相关文档内容的黄色小框会弹出来. 这就是Javadoc,你可以在线下载[http://download.oracle.com/javase/6/docs/api/ Oracle website]. Bukkit同样拥有文档,这些文档内容包含了关于API中method和class的有用描述 provided by the API, which is available [http://jd.bukkit.org/apidocs/ here] (Beta Javadocs can be found [http://jd.bukkit.org/beta/apidocs/ here], and development build Javadocs [http://jd.bukkit.org/dev/apidocs/ here]). In order to have this information available within Eclipse, so that it will pop up whenever you hover over a Bukkit method or class, first right click on the Bukkit jar where it appears under "Maven Dependencies" in the project explorer, and select "Properties". Choose the ''Javadoc Location'' item on the left of the window that pops up, and paste the url '''http://jd.bukkit.org/apidocs/''' (or that of the beta/development Javadocs linked above) into the textbox under "Javadoc URL". It should look like this:  
+
如果你曾经使用过Eclipse和Java,你会知道当你将鼠标准心移至class或者 method中的代码部分时,一个含有相关文档内容的黄色小框会弹出来. 这就是(Java的)Java文档。
 +
 +
你可以[http://download.oracle.com/javase/6/docs/api/ Oracle website 在线获取].。Bukkit同样拥有文档,这些文档内容包含了关于API中method和class的有用描述。
 +
 +
你可以在[http://jd.bukkit.org/ ]找到它 (Beta版的Java文档在[http://jd.bukkit.org/ ], 新开发出来的Java文档在 
 +
 +
[http://jd.bukkit.org/ 这]) 。为了在Eclipse里使用Java文档,这样的话当你的鼠标停留在Bukkit的class和method上时,
 +
 +
(Bukkit的)Java文档就会冒出来。 
 +
JavaDoc in spigot [https://hub.spigotmc.org/javadocs/bukkit/ Click Here]
 +
 
 +
首先右键在侧边栏里的"Maven Dependencies"下面的Bukkit jar,选择"Properties".选中窗口左边的''Javadoc Location'', 并粘贴如下网址
 +
 +
'''http://jd.bukkit.org/''' (或者上面的测试版/最新版 Java文档链接也好) "Javadoc URL"下面的输入框内. 就像这样:  
  
 
[[Image:Bukkitjavadocs.png]]  
 
[[Image:Bukkitjavadocs.png]]  
  
点击 "Validate",然后再点击"OK"就完成了.现在Bukkit Javadocs已经连接到Bukkit提供的资源,同时你也可以通过Eclipse来获得帮助类文档信息了.
+
点击 "Validate",然后再点击"OK"就完成了.现在Bukkit Javadocs已经连接到Bukkit提供的资源,同时你也可以通过Eclipse来获得帮助类文档信息了.
  
 
=== 创建一个包 ===
 
=== 创建一个包 ===
  
现在你需要创建一个包,它将储存所有我们需要使用的Java类文件. 右键展开''src/main/java''折叠并选择 ''New &gt; Package'':  
+
现在你需要创建一个包,它将储存所有我们需要使用的Java类文件. 右键展开''src/main/java''折叠并选择 ''New &gt; Package'':  
  
 
[[Image:Newpackage.png]]  
 
[[Image:Newpackage.png]]  
  
你的包名 put your group name, then a period, and then your artifact name in lowercase. For example, if your group name is ''io.github.name'' and your artifact name is ''TestPlugin'', your package name would be ''io.github.name.testplugin''.
+
包名的话,先写上你的组名,然后加个点,再写上你的小写Artifact ID。举例, 如果你的组名是''io.github.name'',你的artifact名是 ''TestPlugin''
 +
 +
你的包名就应该是''io.github.name.testplugin''.
  
 
=== 创建插件的类文件 ===
 
=== 创建插件的类文件 ===
  
既然我们已经建立好了我们的项目,我们接下来可以开始添加类文件以及制作插件了. 插件的主类指的是拓展(extends)JavaPlugin的类文件. 在你的插件中只能有一个类文件拓展JavaPlugin,无论直接还是间接. 先创建你的主类文件并保持名称与插件名一致是个很好的习惯.右键你之前新建的包,并选择&nbsp; ''New &gt; Class''.你将会创建一个新的类文件,就像下面这样<blockquote><source lang="java">package {$GroupName}.{$ArtifactName};
+
既然我们已经建立好了我们的项目,我们接下来可以开始添加类文件以及制作插件了. 插件的主类指的是拓展(extends)JavaPlugin的类文件. 在你的插件中只能有一个类文件拓展JavaPlugin,无论直接还是间接. 先创建你的主类文件并保持名称与插件名一致是个很好的习惯.右键你之前新建的包,并选择&nbsp; ''New &gt; Class''.你将会创建一个新的类文件,就像下面这样
  
import org.bukkit.plugin.java.JavaPlugin;
+
package {$GroupName}.{$ArtifactName};
 
+
public final class {$ArtifactName} extends JavaPlugin {
+
import org.bukkit.plugin.java.JavaPlugin;
 +
 +
public final class {$ArtifactName} extends JavaPlugin {
 
      
 
      
}</source></blockquote>
+
}
  
WARNING: Plugins should never invoke their constructor and create new instances
+
警告: 插件绝对不应该调用自己的构造函数并实例化
  
 
=== 创建plugin.yml ===
 
=== 创建plugin.yml ===
  
你已经创建了项目和主类,如果你想要Bukkit能够加载你的插件,你还必须创建 '''[[Plugin YAML|plugin.yml]]''' 文件. 这个文件包含有基础的插件信息,如果缺失这个文件,你的插件也将会失效. 此时你需要右键''src/main/resources''. 选择''New &gt; File''. 命名为"'''plugin.yml'''" 并右键完成新文件的创建.Eclipse会打开默认的文本编辑窗口来显示'''plugin.yml'''文件中空白的内容并提供编辑. ''(Hint:&nbsp;如果你想要使得你的工作空间更加规整,关闭文本编辑器并将'''plugin.yml''' 文件拖到主工作空间(拖到文件目录的右边) 之后你就可以在eclipse中编辑文件了.)''
+
你已经创建了项目和主类,如果你想要Bukkit能够加载你的插件,你还必须创建 '''[[Plugin YAML|plugin.yml]]''' 文件. 这个文件包含有基础的插件信息,如果缺失这个文件,你的插件也将会失效. 此时你需要右键''src/main/resources(如果服务器无法识别,请右键你的项目再进行创建)''. 选择''New &gt; File''. 命名为"'''plugin.yml'''" 并右键完成新文件的创建.Eclipse会打开默认的文本编辑窗口来显示'''plugin.yml'''文件中空白的内容并提供编辑. ''(Hint:&nbsp;如果你想要使得你的工作空间更加规整,关闭文本编辑器并将'''plugin.yml''' 文件拖到主工作空间(拖到文件目录的右边) 之后你就可以在eclipse中编辑文件了.)''
  
 
有三个基础的内容需要在plugin.yml写明.  
 
有三个基础的内容需要在plugin.yml写明.  
Line 159: Line 338:
  
 
最简单的 '''plugin.yml''' 文件内就像这样&nbsp;:  
 
最简单的 '''plugin.yml''' 文件内就像这样&nbsp;:  
<blockquote><source lang="yaml">name: {插件名称}
+
 
main: {包名}.{主类}
+
name: {插件名称}
version: {版本号}</source></blockquote>
+
main: {包名}.{主类}   (主类不包含yml后缀,主类就是XXX.Class文件,主类写XXX)
{{note| 插件的包名经常会包括插件的名称,所以看到&这个的时候不要感到惊讶;pluginname&gt;.&lt;pluginname&gt; at the end of the second line!}}
+
version: {版本号}
{{note| 你的主类是否是你的插件名取决于你之前的命名方式,时刻记住这一点很重要.}}
+
 
 +
  PS:插件的包名经常会包括插件的名称,所以看到这个的时候不要感到惊讶。
 +
    你的主类是否是你的插件名取决于你之前的命名方式,时刻记住这一点很重要。
  
 
'''''更多例子''', 请看[[#Example_Files_and_Templates]]''
 
'''''更多例子''', 请看[[#Example_Files_and_Templates]]''
Line 169: Line 350:
 
此时你的插件已经可以被Bukkit加载了,同时服务端日志也会开始记录你的插件. 不过它现在什么用都没有!
 
此时你的插件已经可以被Bukkit加载了,同时服务端日志也会开始记录你的插件. 不过它现在什么用都没有!
  
== onEnable() and onDisable()方法  ==
+
== onEnable()和onDisable()方法  ==
  
这些方法将在插件启用与卸载时生效. 默认情况下,你的插件在被加载时会调用这些方法,所以你可以在这里注册你需要用到的事件和提供一些调试信息. <code>onEnable()</code> 方法会在插件启用时被调用, 需要包含初始化插件的逻辑语句. <code>onDisable()</code> 方法会在插件卸载时被调用,需要包含清理(clean up)插件的逻辑语句and associated state. Additionally plugins can override the <code>onLoad()</code> method to perform additional logic when the plugin loads.
+
这些方法将在插件启用与卸载时生效. 默认情况下,你的插件在被加载时会调用这些方法,所以你可以在这里注册你需要用到的事件和提供一些调试信息. <code>onEnable()</code> 方法会在插件启用时被调用, 需要包含初始化插件的逻辑语句. <code>onDisable()</code> 方法会在插件卸载时被调用,需要包含清理(clean up)插件的逻辑语句和连接声明(associated state)。此外,插件可以重写 <code>onLoad()</code> 方法来在插件载入时执行附加的逻辑语句。
  
=== onEnable() and onDisable()方法介绍  ===
+
=== onEnable()和onDisable()方法介绍  ===
  
 
在前面的章节中,我们在主类创建了<code>onEnable()</code> 和 <code>onDisable()</code> 方法. 此时,这些代码看起来就像下面这样
 
在前面的章节中,我们在主类创建了<code>onEnable()</code> 和 <code>onDisable()</code> 方法. 此时,这些代码看起来就像下面这样
<blockquote><source lang="java">package {$TopLevelDomain}.{$Domain}.{$PluginName};
 
  
import org.bukkit.plugin.java.JavaPlugin;
+
package {$TopLevelDomain}.{$Domain}.{$PluginName};    //包名.类名  例如asd.ddd.XXX  XXX后不用写.Class
  
public final class {$PluginName} extends JavaPlugin {
+
import org.bukkit.plugin.java.JavaPlugin;  //导入BukkitAPI的重要库
    @Override
+
    public void onEnable() {
+
public final class {$PluginName} extends JavaPlugin {
        // TODO Insert logic to be performed when the plugin is enabled
+
    @Override //这里是java的一种注解,用来检测下面onEnable是否被重写
    }
+
    public void onEnable() {
   
+
        // 插件载入时要执行的代码(略)
    @Override
+
    }
    public void onDisable() {
+
   
        // TODO Insert logic to be performed when the plugin is disabled
+
    @Override
    }
+
    public void onDisable() {
}</source></blockquote>
+
        // 插件卸载时要执行的代码(略)
 +
    }
 +
}
  
 
这些方法已经创建,但目前它们还没有任何作用.
 
这些方法已经创建,但目前它们还没有任何作用.
 
注意: 不需要添加代码来实现输出信息"{$PluginName} has been enabled!" ,因为bukkit会自动输出此类信息
 
注意: 不需要添加代码来实现输出信息"{$PluginName} has been enabled!" ,因为bukkit会自动输出此类信息
有关更多事件的信息请查看 [[Event_API_Reference|here]].
+
有关更多事件的信息请查看 [[Event_API_Reference|这里]].
  
 
=== 发送提示信息 ===
 
=== 发送提示信息 ===
  
插件能够输出信息至控制台与服务器日志. It can accomplish this by invoking the correct method from the plugin's logger. First we must invoke the <code>getLogger()</code> method to retrieve the logger associate with this plugin. Then we can start logging.
+
插件能够输出信息至控制台与服务器日志。这是通过调用插件记录器里正确的方法来实现的。首先我们必须调用 <code>getLogger()</code> 方法来检索该插件的记录器,
 +
 +
然后我们就可以开始记录信息了。
  
We will write to the log when <code>onEnable()</code> method is called. We can do that by inserting the following line into the <code>onEnable()</code> method.
+
我们通过调用<code>onEnable()</code>方法可以实现输出信息。 我们需要在<code>onEnable()</code>方法下面插入如下代码:
<blockquote><source lang="java">getLogger().info("onEnable has been invoked!");</source></blockquote>
 
You can then do the same inside <code>'''onDisable()'''</code>, making sure to change the message.
 
  
你的主类文件就像这样:
+
getLogger().info("onEnable has been invoked!");
<blockquote><source lang="java">package {$TopLevelDomain}.{$Domain}.{$PluginName};
 
  
import org.bukkit.plugin.java.JavaPlugin;
+
在 <code>'''onDisable()'''</code>方法里面,你也可以做类似的事情,不过要改改输出信息。
  
public final class {$PluginName} extends JavaPlugin {
+
此时你的主类文件就像这样:
@Override
 
public void onEnable() {
 
getLogger().info("onEnable has been invoked!");
 
}
 
  
@Override
+
package {$TopLevelDomain}.{$Domain}.{$PluginName};
public void onDisable() {
+
getLogger().info("onDisable has been invoked!");
+
import org.bukkit.plugin.java.JavaPlugin;
}
+
}</source></blockquote>
+
public final class {$PluginName} extends JavaPlugin {
 +
        @Override
 +
        public void onEnable() {
 +
            getLogger().info("onEnable has been invoked!");
 +
        }
 +
 +
        @Override
 +
        public void onDisable() {
 +
        getLogger().info("onDisable has been invoked!");
 +
        }
 +
}
  
 
=== 防止重载后插件失效 ===
 
=== 防止重载后插件失效 ===
Line 236: Line 423:
  
 
为了插件重载以后能够正常工作, 你需要在插件启用时加载所有已经在线的玩家的信息并将之储存在HashMap中.
 
为了插件重载以后能够正常工作, 你需要在插件启用时加载所有已经在线的玩家的信息并将之储存在HashMap中.
<blockquote><source lang="java">for (Player player : Bukkit.getServer().getOnlinePlayers()) {
+
 
    playerList.put(player.getName(), playerData(player));
+
for (Player player : Bukkit.getServer().getOnlinePlayers()) {
}</source></blockquote>
+
    playerList.put(player.getName(), playerData(player));
 +
}
  
 
== 监听器  ==
 
== 监听器  ==
Line 244: Line 432:
 
:: '' 请查看: [[Event API Reference]]''
 
:: '' 请查看: [[Event API Reference]]''
  
== Commands ==
+
== 指令 ==
  
 
=== onCommand() 方法  ===
 
=== onCommand() 方法  ===
  
现在你已经知道如何注册一个事件并做出响应,但是如果你只是想要在命令输入之后做出响应呢? 你可以使用 <code>'''onCommand'''</code>.  这个代码的作用是当玩家输入“/”时,监听该操作并执行相关语句"/" . 举个例子来说.输入 "/do something" 将会执行<code>'''onCommand'''</code> 方法. 此种情况下,因为没有特定的行为被编程,所以并不会发生任何事情。
+
现在你已经知道如何注册一个事件并做出响应,但是如果你只是想要在命令输入之后做出响应呢? 你可以使用 <code>'''onCommand'''</code>方法.
 +
 +
  这个代码的作用是当玩家输入“/”时,监听该操作并执行相关语句"/" . 举个例子来说.输入 "/do something" 将会执行 <code>'''onCommand'''</code>方法.
 +
 +
此种情况下,因为没有特定的行为被编程,所以并不会发生任何事情。
 +
 
 +
请避免使用和bukkit所提供的指令重名的指令, 然后深思你的指令名的唯一性。
 +
 +
例如.指令"give"已经被好几个插件使用了, 如果你执行了另外一个"give"指令, 你的插件将会和这些插件冲突。
 +
 
 +
你必须在插件的"plugin.yml"注册你的指令 否则这个方法将不会被触发。
 +
 
 +
<code>'''onCommand'''</code>方法必须返回一个布尔值(true或false)。
 +
 +
如果返回值是true,你不会看到什么明显的事情发生。
 +
 +
但如果返回值是false,则会返回你的plugin.yml里的'usage:property'然后发送给命令使用者.
 +
 
 +
当使用 <code>'''onCommand'''</code>方法时, 你需要填写4个参数.
 +
 
 +
*<code>'''CommandSender sender'''</code> - 发送命令的对象
 +
 +
*<code>'''Command cmd'''</code> - 被执行的指令
 +
 +
*<code>'''String commandLabel'''</code> - 被执行指令的别名
 +
 +
*<code>'''String[] args'''</code> - 该指令的自变量数组。
 +
 +
例如.指令 ''/hello abc def'' 中, ''abc'' 会被存放进args[0]中, ''def'' 被存放进args[1]中。
 +
 
 +
==== 设置命令  ====
 +
@Override
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 +
    if (cmd.getName().equalsIgnoreCase("basic")) { // 如果玩家输入了/basic则执行如下内容...
 +
    // 所需要执行的事(此处略)
 +
        return true;
 +
    } //如果以上内容成功执行,则返回true
 +
    // 如果执行失败,则返回false.
 +
    return false;
 +
}
 +
 
 +
每当使用<code>'''onCommand'''</code>方法时, 在每个方法后面都加上一个 return false; 是一个惯例。
 +
 +
返回false会显示在plugin.yml里设置的usage信息 (看下面). 用这种方法的话,一旦执行出错便会显示usage信息。
 +
 
 +
每当方法return一个值的时候,这个方法就会退出,所以return true的时候,在它下面的代码就会被跳过,
 +
 +
除非return语句在一个if的嵌套中,或者类似的嵌套情况。
  
Avoid using command names that are the same as those that come with Bukkit, and also consider carefully how unique your commands names will be. E.g. the "give" command is already used by several plugins, and if you implement yet another "give" command, your plugin will become incompatible with those other plugins. You must register your commands in the plugin's '''plugin.yml''' or they will not trigger this method.
+
<code>'''.equalsIgnoreCase("basic")'''</code>代表忽略英文大小写. 在这种情况下,字符串"BAsIc" 和 "BasiC"和 basic相同,代码会被照常执行。
  
The <code>'''onCommand'''</code> method must always return a boolean value - either true or false. If the value returned is true then you won't see any noticable events. However if it returns false then the plugin will revert to your plugin files' 'usage: property' and display a message to the user showing them how to use the command as specified in the '''plugin.yml''' file.
+
和以前一样,加两行import在你的java文件头
  
When using <code>'''onCommand'''</code>, you should always register 4 parameters.  
+
import org.bukkit.command.Command;
 +
 +
import org.bukkit.command.CommandSender;
  
*<code>'''CommandSender sender'''</code> - who sent the command
+
=== 在plugin.yml中添加你的指令  ===
  
*<code>'''Command cmd'''</code> - the command that was executed
+
你需要添加你的指令到 '''plugin.yml''' 文件里. 如下是指令/basic添加到 '''plugin.yml'''的例子(请在plugin.yml的末尾添加如下代码):
  
*<code>'''String commandLabel'''</code> - the command alias that was used
+
commands:
 +
    basic:
 +
      description: This is a demo command.
 +
      usage: /<command> [player]
 +
      permission: <plugin name>.basic
 +
      permission-message: You don't have <permission>
  
*<code>'''String[] args'''</code> - an array of additional arguments, e.g. typing ''/hello abc def'' would put ''abc'' in args[0], and ''def'' in args[1]
+
*<code>'''basic'''</code> - 指令名称
  
==== 设置命令  ====
+
*<code>'''description'''</code> - 指令描述
<blockquote><source lang="java">
 
@Override
 
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 
if (cmd.getName().equalsIgnoreCase("basic")) { // If the player typed /basic then do the following...
 
// doSomething
 
return true;
 
} //If this has happened the function will return true.
 
        // If this hasn't happened the value of false will be returned.
 
return false;
 
}</source> </blockquote>
 
When coding the <code>'''onCommand'''</code> function it is always good practice to return false at the very end of the function. Returning false will display the usage dialog set in '''plugin.yml''' (see below). This way if anything goes wrong the help message will be displayed. When returning a value the function will exit so if you return true any code underneath won't be run, unless a return statement is nested in an if statement or similar.
 
  
The <code>'''.equalsIgnoreCase("basic")'''</code> just means that it won't distinguish between upper and lower case characters. For example, the string "BAsIc" and "BasiC" would both equal basic and the code would be executed.
+
*<code>'''usage'''</code> - 在oncommand方法return false后显示的用法提示。尽量简洁, 使别人能够理解指令是什么以及如何使用它。
  
Add also these two lines at the top of your file:
+
*<code>'''permission'''</code> - 使用该命令所需的权限节点。
<blockquote><source lang="java">
 
import org.bukkit.command.Command;
 
import org.bukkit.command.CommandSender;
 
</source> </blockquote>
 
  
=== Adding your Command to the Plugin.yml  ===
+
*<code>'''permission-message'''</code> - 当玩家使用了这个指令而没有权限时输出的信息。
  
You will also need to add the command to your '''plugin.yml''' file. Add the following to the end of '''plugin.yml''':
 
<blockquote><code><source lang="yaml">commands:
 
  basic:
 
      description: This is a demo command.
 
      usage: /<command> [player]
 
      permission: <plugin name>.basic
 
      permission-message: You don't have <permission></source> </code> </blockquote>
 
*<code>'''basic'''</code> - the name of the command.
 
  
*<code>'''description'''</code> - the description of the command .
+
PS:yml文件使用两个空格作为制表符,使用tab键输入制表符会导致错误。
  
*<code>'''usage'''</code> - the help dialog that users will see when you return false in the <code>'''onCommand'''</code> method. Write clearly, so that others can discern what the command is and how to use it.
+
===控制台指令vs玩家指令===
  
*<code>'''permission'''</code> - This is used by some help plugins to work out which commands to show to the user.
+
你可能注意到了上文的<code>'''CommandSender sender'''</code>参数. <code>'''CommandSender'''</code> 是个Bukkit接口,
 +
 +
它有两个(对插件编写者)有用的子类:<code>'''Player'''</code> 和 <code>'''ConsoleCommandSender'''</code>.  
  
*<code>'''permission-message'''</code> - This is output when the player attempts but does not have permission to use the command.
+
所以当你编写插件的时候, 确定这个指令从控制台发出后能完全正常工作是十分必要的,
 +
 +
那些只能由在线玩家执行的指令 ''只能'' 由一个在线玩家执行。
 +
 +
有的插件在判断命令执 行对象不是玩家时仅仅用return进行处理 (比如当有人从控制台发出指令),
 +
 +
即便这些指令能够在控制台完美运行(比如改变天气的指令)。
 +
 
 +
一个判断sender的方法:
 +
@Override
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 +
        if (cmd.getName().equalsIgnoreCase("basic")) { //如果玩家输入了/basic则执行如下内容...
 +
            // 所需要执行的事(此处略)
 +
            return true;
 +
        } else if (cmd.getName().equalsIgnoreCase("basic2")) {
 +
            if (!(sender instanceof Player)) { //如果sender与Player类不匹配
 +
                sender.sendMessage("这个指令只能让玩家使用。");
 +
            } else {
 +
                Player player = (Player) sender;
 +
                    // 所需要执行的事(此处略)
 +
            }
 +
                return true;
 +
        }
 +
        return false;
 +
}
 +
 
 +
在这个例子里,指令'''basic'''能由任何人发出 - 一个在线的玩家, 或者服务器后台的op.
 +
 +
但是指令'''basic2'''只能由一个在线的玩家发出.  
  
Note that yml files use 2 spaces for tabs, as the tab character will cause problems.
+
大体上说,你应该允许尽可能多的指令在控制台和玩家聊天框里都正常执行.  
 +
 +
需要在线玩家执行的指令可以使用以上例子中的机制来检测<code>'''CommandSender'''</code>是一个玩家.
 +
 +
很多指令广泛依靠于一些玩家的属性(例如可以被tp或被给予物品等等)。
  
=== Console Commands vs. Player Commands ===
+
  如果你想更进一步, 你可以对你的指令的自变量做一些额外的检测, 例如传送指令在提供玩家ID的情况下(也只有在这种情况下)可以在控制台被执行。
  
You may have noticed the <code>'''CommandSender sender'''</code> parameter above. <code>'''CommandSender'''</code> is a Bukkit interface which has two useful (for plugin writers) subclasses: <code>'''Player'''</code> and <code>'''ConsoleCommandSender'''</code>.
+
=== 使用独立的 CommandExecutor class  ===
  
When you're writing your plugin, it's a very good idea to ensure that commands that ''can'' be run from the console actually work, and that commands that should only be run as a logged-in player really ''are'' only run as a logged-in player. Some plugins simply return if the sender is not a player (i.e. someone tried to use the plugin's commands from the console), even when those commands make perfect sense from the console (e.g. changing the weather on the server).
+
以上的例子必须把 <code>'''onCommand()'''</code> 方法放在插件的主类里。
 +
 +
对于小插件来说,这是极好的,但如果你要写一个大点的插件,把你的 <code>'''onCommand()'''</code>方法放在独立的类里将会很有意义。
 +
 +
幸运的是,这并不难:
  
One way to do this is:
+
*在你的包里创建一个新类. 命名为'''MyPluginCommandExecutor'''之类的 (当然,要把'''MyPlugin'''替换成你制作的插件的名字)。 这个类 '''必须''' 实现Bukkit 的'''CommandExecutor'''接口。
<blockquote><source lang="java">
+
 
@Override
+
*在你的插件的<code>'''onEnable()'''</code>方法中,你需要实例化你创建的命令执行类(CommandExecutor class), 然后做一个像下面一样的调用:<code>getCommand("basic").setExecutor(myExecutor);</code>
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
+
其中"basic"是我们需要操控的指令, <code>myExecutor</code> 是我们创建的实例.
if (cmd.getName().equalsIgnoreCase("basic")) { // If the player typed /basic then do the following...
+
 
// do something...
+
实例:
return true;
 
} else if (cmd.getName().equalsIgnoreCase("basic2")) {
 
if (!(sender instanceof Player)) {
 
sender.sendMessage("This command can only be run by a player.");
 
} else {
 
Player player = (Player) sender;
 
// do something
 
}
 
return true;
 
}
 
return false;
 
}
 
</source> </blockquote>  
 
In this example, the command '''basic''' can be run by anyone - a logged-in player, or the server operator on the console. But the command '''basic2''' can only be run by logged-in players.
 
  
In general, you should allow as many commands as possible to work on both the console and for players. Commands that ''need'' a logged-in player can use the mechanism in the example above to check that the <code>'''CommandSender'''</code> is actually a player before continuing. Such commands would generally depend on some attribute of the player, e.g. a teleportation command needs a player to teleport, an item giving command needs a player to give the item to...  
+
'''MyPlugin.java''' (插件的主类):
 +
@Override
 +
public void onEnable() {
 +
        // 如果你没有在plugins.yml注册过指令的话,此处会抛出空指针异常!
 +
        this.getCommand("basic").setExecutor(new MyPluginCommandExecutor(this));
 +
}
  
If you want to get more advanced, you could do some extra checks on your command arguments so that e.g. a teleportation command could be used from the console ''if and only if'' a player's name is also supplied.
+
'''MyPluginCommandExecutor.java'''(独立CommandExecutor类):
 +
public class MyPluginCommandExecutor implements CommandExecutor {
 +
        private final MyPlugin plugin;
 +
 +
        public MyPluginCommandExecutor(MyPlugin plugin) {
 +
            this.plugin = plugin; // Store the plugin in situations where you need it.
 +
        }
 +
 +
        @Override
 +
        public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 +
            // 和以前一样执行就好…
 +
        }
 +
}
  
=== Using a separate CommandExecutor class ===
+
  请注意我们是如何把主类里的对象指向'''MyPluginCommandExecutor'''的. 这允许我们轻易的使用主类对象的方法(如果我们需要的话)。
  
The examples above just put the <code>'''onCommand()'''</code> method into the plugin's main class. For small plugins, this is fine, but if you're writing something more extensive, it may make sense to put your <code>'''onCommand()'''</code> method into its own class. Fortunately, this isn't too hard:
+
这么做的话, 我们便能更好的组织代码 - 如果 <code>'''onCommand()'''</code> 方法又大又复杂, 它就能被划分为子类,从而不使插件的主类显得凌乱。
  
*Create a new class within your plugin's package. Call it something like '''MyPluginCommandExecutor''' (although of course replacing '''MyPlugin''' with your plugin's actual name). That class ''must'' implement the Bukkit '''CommandExecutor''' interface.
+
PS:如果你的插件含有多个指令的话, 你需要为每一个指令单独设立一个commandexecutor.
  
*In your plugin's <code>'''onEnable()'''</code> method, you need to create an instance of your new command executor class, and then make a call like <code>getCommand("basic").setExecutor(myExecutor);</code>, where "basic" is the command we want to handle, and <code>myExecutor</code> is the instance we created.
+
=== 写一个安全的指令 ===
 +
当写一个指令的时候,别进行任何假设(很重要), 例如假设执行者一定是个玩家. 请牢记以下原则:
  
Best explained by example:
+
==== 在命令执行前确定发出者是玩家 ====
  
'''MyPlugin.java''' (the main plugin class):  
+
使用简单的例子来进行检测:
<blockquote><source lang="java">
 
@Override
 
public void onEnable() {
 
// This will throw a NullPointerException if you don't have the command defined in your plugin.yml file!
 
this.getCommand("basic").setExecutor(new MyPluginCommandExecutor(this));
 
}
 
</source></blockquote>
 
'''MyPluginCommandExecutor.java''':
 
<blockquote><source lang="java">
 
public class MyPluginCommandExecutor implements CommandExecutor {
 
private final MyPlugin plugin;
 
  
public MyPluginCommandExecutor(MyPlugin plugin) {
+
@Override
this.plugin = plugin; // Store the plugin in situations where you need it.
+
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
}
+
        if (sender instanceof Player) {
 +
            Player player = (Player) sender;
 +
            // do something
 +
        } else {
 +
            sender.sendMessage("You must be a player!");
 +
            return false;
 +
        }
 +
            // do something
 +
        return false;
 +
}
  
@Override
+
==== 检测自变量个数 ====
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 
// implementation exactly as before...
 
}
 
}
 
</source></blockquote>
 
Notice how we send a reference of the main plugin object to '''MyPluginCommandExecutor'''. This allows us easy access to the main plugin objects's methods if we need to.
 
  
By doing this, we can better organise our code - if the main <code>'''onCommand()'''</code> method is large and complex, it can be split into submethods without cluttering up the plugin's main class.  
+
别老是认为玩家都能打对正确数量的自变量.  
  
Note that if your plugin has multiple commands, you will need set the command executor for each command individually.
+
@Override
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 +
        if (args.length > 4) {
 +
            sender.sendMessage("Too many arguments!");
 +
            return false;
 +
        }
 +
        if (args.length < 2) {
 +
            sender.sendMessage("Not enough arguments!");
 +
            return false;
 +
        }
 +
}
  
=== Writing a safe onCommand ===
+
==== 在获取玩家之前先检测他们是否在线 ====
When writing an onCommand, it's important that you don't assume any information, such as the sender being a Player. Things to keep in mind:
 
  
==== Make sure the sender is a Player before casting ====
+
有时你想通过命令发出者打出的ID来获取其他玩家,请确保他们在线再说!
Using simple code like this makes it possible:
 
<blockquote><source lang="java">
 
@Override
 
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 
if (sender instanceof Player) {
 
          Player player = (Player) sender;
 
          // do something
 
        } else {
 
          sender.sendMessage("You must be a player!");
 
          return false;
 
        }
 
        // do something
 
        return false;
 
}</source> </blockquote>
 
  
==== Check the arguments length ====
+
@Override
Don't always assume the sender typed the correct amount of arguments.
+
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
<blockquote><source lang="java">
+
        Player target = (Bukkit.getServer().getPlayer(args[0]));
@Override
+
        if (target == null) {
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
+
            sender.sendMessage(args[0] + " is not online!");
if (args.length > 4) {
+
            return false;
          sender.sendMessage("Too many arguments!");
+
        }
          return false;
+
        return false;
        }
+
}
        if (args.length < 2) {
 
          sender.sendMessage("Not enough arguments!");
 
          return false;
 
        }
 
}</source> </blockquote>
 
  
==== Check if a Player is online before getting them ====
+
如果你想对离线玩家进行操作,<code>'''OfflinePlayer'''</code>类提供了基础操作的方法.
Sometimes you want to get another player by the name entered by the player. Always make sure the player is online!
 
<blockquote><source lang="java">
 
@Override
 
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 
Player target = (Bukkit.getServer().getPlayer(args[0]));
 
        if (target == null) {
 
          sender.sendMessage(args[0] + " is not online!");
 
          return false;
 
        }
 
        return false;
 
}</source> </blockquote>
 
If you need to modify a Player currently not online, the <code>'''OfflinePlayer'''</code> class provides basic manipulation methods.
 
  
== Plugin Configuration/Settings ==
+
== 插件的配置/设置 ==
The Bukkit API provides a convenient way for plugins to manage user configurable settings. Additionally it also serves as an easy way to store data.
+
Bukkit API 为插件提供了一种简便的方法,用来管理用户自定义的设置。同时,通过这种方法也可以储存数据。
:: ''Please see: [[Configuration API Reference]]''
+
:: ''详情请查看: [[Configuration API Reference]]''
  
== Permissions ==
+
== 权限 ==
  
With the new Bukkit API for permissions, they couldn't be easier. To find out if a player has a particular permission use the following:  
+
利用新的Bukkit API来设置权限是再容易不过的事情. 检测玩家是否拥有权限,可以使用以下代码:  
 
<blockquote><source lang="java">if (player.hasPermission("some.pointless.permission")) {
 
<blockquote><source lang="java">if (player.hasPermission("some.pointless.permission")) {
   //Do something
+
   //执行此段代码(有权限)
 
} else {
 
} else {
   //Do something else
+
   //执行此段代码(无权限)
 
}</source> </blockquote>  
 
}</source> </blockquote>  
You can also find if a permission has been set or not (equivalent to Java's '''null''') with the following function:  
+
你也可以检测一个权限节点是否被定义过,(等效于 Java 语言中的'''null'''),使用下面的方法:  
 
<blockquote><source lang="java">boolean isPermissionSet(String name)</source></blockquote>  
 
<blockquote><source lang="java">boolean isPermissionSet(String name)</source></blockquote>  
You may be wondering why there aren't any groups. The answer to that is because they aren't really needed. Previously one of the main uses for groups was to format chat messages. That however can be done just as easily with permissions. Inside your chat plugin's config you would define associations between permissions and prefixes. For example the permission "someChat.prefix.admin" would correspond to the prefix [Admin]. Whenever a player speaks with that permission their name will be prefixed with [Admin].  
+
你或许对此抱有疑惑:为什么这段代码中没有任何用户群的信息?因为它们并不是必需的. 之前这段代码想要实现的主要功能之一就是根据用户群格式化聊天信息. 但其实使用权限就可以很容易地完成这项工作.你可以在聊天插件的配置文件中定义权限与前缀的对应关系. 举例来说,"someChat.prefix.admin" 权限可以对应前缀[Admin].当拥有此权限的玩家发出聊天信息时 ,聊天前缀便是[Admin].  
  
Another common usage might be to send a message to all users within a group. Again however this can be done with permissions with the following:  
+
另一个想要实现的主要功能则是给所有拥有权限的在线用户发一条信息.你可以这样做:  
 
<blockquote><source lang="java">for (Player player: Bukkit.getServer().getOnlinePlayers()) {
 
<blockquote><source lang="java">for (Player player: Bukkit.getServer().getOnlinePlayers()) {
 
     if (player.hasPermission("send.receive.message")) {
 
     if (player.hasPermission("send.receive.message")) {
Line 446: Line 677:
 
     }
 
     }
 
}</source> </blockquote>  
 
}</source> </blockquote>  
Finally you may be asking, well how do I set and organise player's permissions if there are no groups? Although the bukkit API doesn't provide groups itself, you must install a permission provider plugin such as permissionsBukkit to manage the groups for you. '''This API provides the interface, not the implementation.'''  
+
看到这里你可能会问,若是没有权限组我该如何配置玩家的 [[权限]] ?既然 [[Bukkit]] API 本身并没有提供用户组的预设, 你必须通过安装一款权限组插件,像 [[permissionsBukkit]]  这样的插件来帮助你配置用户群. '''API 只是提供接口而不是提供已实现的类.'''  
 
 
=== Configuring your permissions  ===
 
  
If you want more control over your permissions, for example default values or children then you should consider adding them to your ''[[plugin.yml]]''. This is completely optional, however it is advised. Below is an example permissions config that would be appended to the end of your existing ''plugin.yml'':  
+
=== 配置权限 ===
 +
{{Hide|标题=译者注 |内容= 其一,以下代码中部分内容将以''//文字''翻译,使用时请不要加入其中内容,仅做教程中的翻译参考;
 +
其二,default:true在下面翻译为全员默认拥有该权限,default:false则翻译为全员默认没有该权限,请注意理解。 }}
 +
如果你想要更多地控制你的权限,例如设置权限的默认值或是子权限节点,不如将权限节点加入你的 ''[[plugin.yml]]''文件.这并不是必须的,但我们推荐你这样做. 下面是一个实例,显示了当你将权限添加在''plugin.yml''文件末尾时的样子:  
 
<blockquote><code><source lang="yaml">permissions:
 
<blockquote><code><source lang="yaml">permissions:
 
     doorman.*:
 
     doorman.*:
         description: Gives access to all doorman commands
+
         description: Gives access to all doorman commands //给予使用doorman所有命令的权限
         children:
+
         children: //子权限节点
 
             doorman.kick: true
 
             doorman.kick: true
 
             doorman.ban: true
 
             doorman.ban: true
Line 460: Line 692:
 
             doorman.denied: false
 
             doorman.denied: false
 
     doorman.kick:
 
     doorman.kick:
         description: Allows you to kick a user
+
         description: Allows you to kick a user //允许你踢出一名用户
         default: op
+
         default: op //OP默认拥有该权限
 
     doorman.ban:
 
     doorman.ban:
         description: Allows you to ban a user
+
         description: Allows you to ban a user //允许你封禁一名用户
         default: op
+
         default: op //OP默认拥有该权限
 
     doorman.knock:
 
     doorman.knock:
         description: Knocks on the door!
+
         description: Knocks on the door! //敲门
         default: true
+
         default: true //全员默认拥有该权限
 
     doorman.denied:
 
     doorman.denied:
         description: Prevents this user from entering the door</source> </code> </blockquote>  
+
         description: Prevents this user from entering the door //禁止拥有该权限节点的用户进入门内</source> </code> </blockquote>  
Firstly, each permission your plugin uses is defined as a child node of the ''permissions'' node. Each permission can then optionally have a description, a default value, and children.  
+
值得注意的是,你的插件使用的每个权限都被定义为 ''permissions'' 这个节点的子权限节点. 每个权限都能够拥有一个描述,默认值以及子权限节点。
 
+
==== 默认值 ====
==== Defaults ====
 
  
By default when a permission isn't defined for a player&nbsp;''hasPermission''&nbsp;will return false. Inside your plugin.yml you can change this by setting the default node to be one of four values:  
+
当一个权限没有明确是否为玩家所拥有&nbsp;''hasPermission''<ref>这是一个检测玩家是否拥有指定权限节点的方法</ref>&nbsp;将返回false<ref>该方法返回的boolean值</ref>. 在你的[[plugin.yml]] you can change this by setting the default node to be one of four values:  
  
*'''true''' - The permission will be true by default.  
+
*'''true''' - 全员默认拥有该权限.  
*'''false''' - The permission will by false by default.  
+
*'''false''' - 全员默认没有该权限.  
*'''op''' - If the player is an op then this will be true.  
+
*'''op''' - 如果玩家是一名OP<ref>Operator,服务器管理员</ref>,那么他将拥有该权限.  
*'''not op''' - If the player is not an op then this will be true.
+
*'''not op''' - 如果玩家不是一名OP,那么他将拥有该权限.
  
==== Children ====
+
==== 子权限 ====
  
Before now you will probably be used to the * permission to automatically assign all sub permissions. This has changed with the bukkit API and you can now define the child permissions. This allows for a lot more flexibility. Below is an example of how you do this:  
+
在这之前你可能已经习惯了通过权限节点自动分配所有的子权限<ref>原句:Before now you will probably be used to the * permission to automatically assign all sub permissions.此处的sub permissions应是同上文“你的插件使用的每个权限都被定义为 permissions 这个节点的子权限节点.”相照应</ref>. 现在这种方法已经随着bukkit API<ref>应用程序编程接口(Application Programming Interface)</ref>的更新而改变,现在你可以自己定义子权限了. 这将使得插件具有更多的灵活性. 下面是一个例子:  
 
<blockquote><source lang="yaml">permissions:
 
<blockquote><source lang="yaml">permissions:
 
     doorman.*:
 
     doorman.*:
         description: Gives access to all doorman commands
+
         description: Gives access to all doorman commands //翻译见上
 
         children:
 
         children:
 
             doorman.kick: true
 
             doorman.kick: true
Line 492: Line 723:
 
             doorman.knock: true
 
             doorman.knock: true
 
             doorman.denied: false</source> </blockquote>  
 
             doorman.denied: false</source> </blockquote>  
Here the ''doorman.*'' permission has several child permissions assigned to it. The way child permissions work is when ''doorman.*'' is set to true, the child permissions are set to their values defined in the ''plugin.yml''. If however ''doorman.*'' was set to false then all child permissions would be inverted.
+
在这里,''doorman.*'' 权限拥有几个子权限并分配在其下. 子权限工作的方式就是当''doorman.*'' 权限节点被设置为true时,子权限也会随之设置为 ''plugin.yml''中的默认值. 然而当''doorman.*'' 被设置为false时,所有子权限都会设置为与默认值相反的值(true->false,false->true).
 +
<references />
  
=== Setting your own permissions ===
+
=== 设置你自己的权限 ===
  
If you wish to know about developing your own permissions plugins (Ones that actually set permissions) then check out the tutorial on&nbsp;[[Developing a permissions plugin]].
+
如果你希望想知道更多有关开发你自己的权限插件的事情(即是设置权限的管理类插件) ,那么请前往查看教程&nbsp;[[Developing a permissions plugin]].
  
== Scheduling Tasks and Background Tasks ==
+
== 计划任务与后台任务 ==
  
Currently, Minecraft servers operate nearly all of the game logic in one thread, so each individual task that happens in the game needs to be kept very short. A complicated piece of code in your plugin has the potential to cause huge delays and lag spikes to the game logic, if not handled properly.  
+
目前, Minecraft服务器都使用一个线程进行所有逻辑操作, 所以每个发生在游戏中的逻辑运算必须要很短. 如果处理不好的话, 复杂的代码可能会导致你的服务器逻辑运算有极大的延迟和滞后.  
  
Luckily, Bukkit has support for scheduling code in your plugin. You can submit a Runnable task to occur once in the future, or on a recurring basis, or you can spin off a whole new independent thread that can perform lengthy tasks in parallel with the game logic.  
+
幸运的是, Bukkit正在帮助你安排你的插件源代码. 你可以提交一个在未来能够运行一次的可运行任务, 或者一个基础的循环, 或者你可以拆分出一个新的与游戏逻辑并行的线程, 用来执行过于冗长的任务.
  
There is a separate [[Scheduler Programming]] tutorial which introduces the Scheduler, and gives more information on using it to schedule synchronous tasks, and on kicking off asynchronous tasks in Bukkit.  
+
这里 [[Scheduler Programming]] 单独写出了计划程序的教程, 并给出了更多你可以用来安排同步任务的信息, 还有启动Bukkit的异步进程.
  
== Block Manipulation<br>  ==
+
== 方块操控<br>  ==
  
The easiest way to create blocks is to get an existing block and modify it. For example, if you want to change the block that is located five blocks above you, you would first have to get your current location, add five to your current y-coordinate, and then change it. For example:
+
这是一个很容易的方法创建、获取或修改一个方块, 例如当你想要修改往你头上数第五格的方块时, 首先你需要做的是获取你当前的坐标点, 然后往你当前的坐标点的Y轴加上五, 获取目标方块后我们便能够很容易地修改它了, 例子如下:
 
<blockquote><source lang="java">
 
<blockquote><source lang="java">
 
@EventHandler
 
@EventHandler
 
public void onPlayerMove(PlayerMoveEvent event) {
 
public void onPlayerMove(PlayerMoveEvent event) {
     // Get the player's location.
+
     // 得到玩家当前的坐标.
 
     Location loc = event.getPlayer().getLocation();
 
     Location loc = event.getPlayer().getLocation();
     // Sets loc to five above where it used to be. Note that this doesn't change the player's position.
+
     // 设置目标坐标为玩家头顶 5 格处.
 
     loc.setY(loc.getY() + 5);
 
     loc.setY(loc.getY() + 5);
     // Gets the block at the new location.
+
     // 得到目标坐标的方块
 
     Block b = loc.getBlock();
 
     Block b = loc.getBlock();
     // Sets the block to type id 1 (stone).
+
     // 将目标坐标的方块更改为石头 (stone).
 
     b.setType(Material.STONE);
 
     b.setType(Material.STONE);
 
}</source> </blockquote>  
 
}</source> </blockquote>  
The above code gets the player's location, gets the block five above it, and sets it to stone. Note that once you have a <code>Block</code>, there are other things you can do besides set its type. Consult the JavaDocs for more information.
+
上面的代码获取了玩家的所在位置,获取头上的第五个方块,并且放上一个石头(stone)。 Note that once you have a <code>Block</code>, there are other things you can do besides set its type. 你可以查阅JavaDoc获取更多的东西。
  
You can use a similar concept to generate buildings and individual blocks programmatically through the use of algorithms. For example, to generate a solid cube, you could use nested <code>for</code> loops to loop over an entire cube and fill it in.
+
你可以用这样相似的观念去生成一些建筑,并且还可以用编程的方式通过算法生成一些独特的方块(这里指由很多个方块拼在一起的四四方方的方块)。比如,去生成一个实心的方块建筑,你可能在生成建筑的时候还需要用到<code>for</code>循环,去一遍遍循环来生成这些建筑,填满这些建筑等。
 +
下面,是一个在指定坐标生成一个实心的方块建筑的实例方法:
 
<blockquote><source lang="java">
 
<blockquote><source lang="java">
 
public void generateCube(Location loc, int length) {
 
public void generateCube(Location loc, int length) {
     // Set one corner of the cube to the given location.
+
     // 放置这个方块最中间的方块并且获取这个方块的坐标。
 
     // Uses getBlockN() instead of getN() to avoid casting to an int later.
 
     // Uses getBlockN() instead of getN() to avoid casting to an int later.
 
     int x1 = loc.getBlockX();  
 
     int x1 = loc.getBlockX();  
Line 547: Line 780:
 
                 // Get the block that we are currently looping over.
 
                 // Get the block that we are currently looping over.
 
                 Block currentBlock = world.getBlockAt(xPoint, yPoint, zPoint);
 
                 Block currentBlock = world.getBlockAt(xPoint, yPoint, zPoint);
                 // Set the block to type 57 (Diamond block!)
+
                 // 设置方块ID为57号(钻石块!)
                 currentBlock.setType(Material.DIAMOND_BLOCK);
+
                 currentBlock.setType(Material.DIAMOND_BLOCK); //这里下面会提到
 
             }
 
             }
 
         }
 
         }
 
     }
 
     }
 
}</source></blockquote>  
 
}</source></blockquote>  
This method will construct a 3D cube or cuboid with the given length and starting point. As for deleting blocks simply follow the same method for creating them but set the ID to 0 (air).<br>  
+
这个方法将会在程序给予这个方法高度和起始坐标(最中间的方块的坐标)时,构建一个3D的方块建筑。简单的删除方块也可以使用这个方法的思路实现,但是,但是设置方块的时候,记得把上面标注会提到的那行,设置为物品ID为0的方块(空气方块)<br>  
 
<blockquote></blockquote>
 
<blockquote></blockquote>
  
== (Player) Inventory Manipulation ==
+
== (玩家)修改背包 ==
  
This section mostly covers player inventory manipulation, but the same applies to chest inventory manipulation as well if you find out how to get a chest's inventory&nbsp;:P. Here is a simple example of inventory manipulation:  
+
这个部分大部分讲的是修改玩家的背包。但是在某些情况下,修改箱子里的内容与此相同,当然得在你能找到获取箱子里内容的方法才可以。&nbsp;:P. 这是一个修改玩家背包的一个例子:  
 
<blockquote><source lang="java">public void onPlayerJoin(PlayerJoinEvent evt) {
 
<blockquote><source lang="java">public void onPlayerJoin(PlayerJoinEvent evt) {
     Player player = evt.getPlayer(); // The player who joined
+
     Player player = evt.getPlayer(); // 当玩家加入游戏
     PlayerInventory inventory = player.getInventory(); // The player's inventory
+
     PlayerInventory inventory = player.getInventory(); // 获取玩家背包列表
     ItemStack itemstack = new ItemStack(Material.DIAMOND, 64); // A stack of diamonds
+
     ItemStack itemstack = new ItemStack(Material.DIAMOND, 64); // 生成一组钻石
 
          
 
          
 
     if (inventory.contains(itemstack)) {
 
     if (inventory.contains(itemstack)) {
         inventory.addItem(itemstack); // Adds a stack of diamonds to the player's inventory
+
         inventory.addItem(itemstack); // 将一组钻石放到玩家的背包里
         player.sendMessage("Welcome! You seem to be reeeally rich, so we gave you some more diamonds!");
+
         player.sendMessage("Wow!你看上去很土豪啊!"); //向玩家发送消息("Wow!你看上去很土豪啊!")
 
     }
 
     }
 
}</source> </blockquote>  
 
}</source> </blockquote>  
So inside onPlayerJoin we first make a few variables to make our job easier: player, inventory and itemstack. Inventory is the player's inventory and itemstack is a ItemStack that has 64 diamonds. After that we check if the player's inventory contains a stack of diamonds. If the player has a stack of diamonds, we give him/her another stack with inventory.addItem(itemstack) and send a message. So inventory manipulation isn't actually that hard, if we wanted we could remove the stack of diamonds by simply replacing inventory.addItem(itemstack) with inventory.remove(itemstack) and change the message a little bit. Hopefully this helped!
+
所以,上面代码里面的 onPlayerJoin 方法中,我们首先构建了一些变量<ref>或许应该是实例化的对象?不过原文中是variables,故此处直译 </ref>,让我们的工作变得更容易一些。变量分别是: player, inventory and itemstack。 inventory 变量是玩家的背包内容,itemstack是那64个钻石的物品栈。然后我们判断了玩家是不是有一组钻石。如果有,我们就用inventory.addItem(itemstack)方法再给玩家一组钻石,并且给他发送一个消息“Wow!你看上去很土豪啊!”。 所以修改背包并不像我们想象的那么难,如果我们想实现让玩家进入游戏,检测到一组钻石后删掉这组钻石,只需要把代码里的inventory.addItem(itemstack)替换成inventory.remove(itemstack)。或许你还需要把“Wow!你看上去很土豪啊!”改成“Wow!你看上去好穷啊!”2333333。
 +
 
 +
== 控制物品 ==
 +
 
 +
当你需要写一个对物品们做出行为的代码时,你要使用ItemStack类来查阅有关那个栈的所有设置信息。
 +
当然,你也可以干脆搞出一个特别的背包~ 
 +
 
 +
=== 物品容器GUI ===
 +
你可以搞出一个类似于箱子的容器,只不过不是实体化的,它叫Inventory  。
 +
 
 +
你可以到 https://hub.spigotmc.org/javadocs/bukkit/  
 +
查找org.bukkit.inventory包了解详情
 +
 
 +
构建方法:
 +
Inventory 变量 = Bukkit.createInventory(InventoryHolder ,int ,String);
 +
Inventory 变量 = Bukkit.createInventory(InventoryHolder,InventoryType ,String);
 +
 
 +
所有者可以是null
 +
 
 +
大小有两种,数字(int)或类型(InventoryType),需要注意的是,使用类型时,需要使用getDefaultSize()方法获取默认的大小。
 +
 
 +
数字是9的倍数,不超过54,出来的是像箱子界面,如果超过54则...呵呵呵呵。
 +
 
 +
类型是特殊的容器,如工作台界面,具体根据名称而定。
 +
 
 +
类型列表(图片)
 +
 
 +
[[文件:InvType.jpg]]
 +
 
 +
后面的String是你箱子的名字(标题)
 +
 
 +
代码:
 +
 
 +
<source lang="java">
 +
    Inventory inv1 = Bukkit.createInventory(null,InventoryType.CHEST , "seesaw_233");
 +
    Inventory inv2 = Bukkit.createInventory(null,54 , "§2 !seesaw!");
 +
</source>
 +
 
 +
inv1是普通小箱子界面,黑字体标题: seesaw_233
 +
 
 +
inv2是大箱子界面,绿字体标题:  !seesaw! 
 +
 
 +
颜色代码(§)列表(图):
 +
 
 +
[[文件:MinecraftColorCode.png]]
 +
 
 +
让玩家关闭 容器/背包 的方法:
 +
 +
closeInventory()
  
== Item Manipulation ==
+
让玩家打开指定容器的方法:
  
When dealing with items in the code, you use the ItemStack class for looking up and setting all information on that stack.
+
openInventory(容器变量)
  
=== Enchantments ===
+
容器变量 是你指定的Inventory变量
  
To enchant an item you must first know the [http://www.minecraftwiki.net/wiki/Data_values Item Code] and the [http://www.minecraftwiki.net/wiki/Talk:Enchanting#Effect_IDs Effect ID].  Enchantments themselves cannot be instantiated (new Enchantment() won't work) because they're abstract, so you must use an EnchantmentWrapper. If you want to enchant items that can't be enchanted inside normal SMP, use addUnsafeEnchantment() instead of addEnchantment()
+
代码范例:
 +
 
 +
<source lang="java">
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args )
 +
{
 +
if(cmd.getName().equalsIgnoreCase("cg") )
 +
{
 +
    if(args.length ==0)
 +
    {   
 +
    sender.sendMessage("§4 未知命令");
 +
    }
 +
    if(args.length ==1)
 +
    {
 +
        if (sender instanceof Player)
 +
        {
 +
            Player p = (Player)sender;           
 +
            if(args[0].equalsIgnoreCase("open"))
 +
            {
 +
                p.closeInventory();
 +
                p.openInventory(inv1);
 +
           
 +
            }   
 +
        } else{ sender.sendMessage("§4 只有玩家才可使用该命令"); }
 +
    }
 +
}
 +
return true;
 +
}
 +
</source>
 +
 
 +
=== 物品添加 ===
 +
 
 +
有了容器,没有物品怎么行?
 +
我们需要new出一个物品~
 +
 
 +
ItemStack 变量 = new ItemStack(类型 ,数量) ;
 +
 
 +
类型同样可以是数字ID(int)或原版类型(Material)
 +
 
 +
类型太多了,请到 https://hub.spigotmc.org/javadocs/bukkit/
 +
 
 +
查看org.bukkit.Material类的静态方法 2333333333333333333~
 +
 
 +
<source lang="java">
 +
ItemStack myitem = new ItemStack(Material.DIAMOND_PICKAXE ,1) ;
 +
</source>
 +
 
 +
lol~
 +
 
 +
=== 附魔属性 ===
 +
 
 +
你想给一个物品附魔,你首先需要了解[http://www.minecraftwiki.net/wiki/Data_values Item Code][http://www.minecraftwiki.net/wiki/Talk:Enchanting#Effect_IDs Effect ID].  Enchantments他们自己不能例示(new Enchantment() won't work) 因为他们是抽象的。所以你必须要用一个EnchantmentWrapper。如果你想去附魔物品,还不想在默认的SMP里附魔,可以使用addEnchantment()里面的addUnsafeEnchantment()
  
 
<source lang="java">
 
<source lang="java">
Line 586: Line 917:
 
ItemStack myItem = new ItemStack(itemCode);  //new item of item code
 
ItemStack myItem = new ItemStack(itemCode);  //new item of item code
 
Enchantment myEnchantment = new EnchantmentWrapper(effectId);  //new enchantment of effect id
 
Enchantment myEnchantment = new EnchantmentWrapper(effectId);  //new enchantment of effect id
myItem.addEnchantment(myEnchantment, enchantmentLevel);  //enchant the item
+
myItem.addEnchantment(myEnchantment, enchantmentLevel);  //给物品附魔
 
</source>
 
</source>
  
=== ItemMeta ===
+
=== 物品数据 ===
You can set the display name of an item by doing this.
+
您可以通过这样做来设置物品的显示名称。
 
<source lang="java">
 
<source lang="java">
String myDisplayName = "Awesome Sword"; //use the displayname you want here
+
String myDisplayName = "Awesome Sword"; //这里是保存了你想要显示的名字
 
   
 
   
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //your item
+
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //你要更改的方块
ItemMeta im = myItem.getItemMeta(); //get the itemmeta of the item
+
ItemMeta im = myItem.getItemMeta(); //得到方块的元数据
im.setDisplayName(myDisplayName); //set the displayname
+
im.setDisplayName(myDisplayName); //设置它显示的名字
myItem.setItemMeta(im); //give the item the new itemmeta
+
myItem.setItemMeta(im); //保存更改
 
  </source>
 
  </source>
You can also set the lores of an item. The lores are the small annotations on an item, like "+5 attack damage" on a stone sword.
+
你也可以设置一个物品的lore。 lore是物品的小注释, "+5 攻击伤害" 在一个石剑上.
 
<source lang="java">
 
<source lang="java">
 
List<String> lores = new ArrayList<String>();
 
List<String> lores = new ArrayList<String>();
lores.add("Example lore", "this one comes on line 2");
+
lores.add("123");  //第一行lore
 
+
lores.add("abc") //这条lore在123之后
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //your item
+
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //你的物品
 
ItemMeta im = myItem.getItemMeta(); //get the itemmeta of the item again
 
ItemMeta im = myItem.getItemMeta(); //get the itemmeta of the item again
 
im.setLore(lores); //add the lores of course
 
im.setLore(lores); //add the lores of course
Line 610: Line 941:
 
</source>
 
</source>
  
== Maps, and Sets, and Lists, Oh My! ==
+
更多关于lore的设置和它所依靠的ArrayList参见  [[ArrayList Reference]]
 +
 
 +
=== 容器控制 ===
 +
 
 +
容器不控制咋行呢,不能让人乱来。
 +
 
 +
这次要扯到监听器,先放前置代码:
 +
 
 +
<source lang="java">
 +
public void onInventoryClick(InventoryClickEvent event)
 +
</source>
 +
 
 +
<source lang="java">
 +
Inventory inv = Bukkit.createInventory(null,InventoryType.CHEST , "hello");
 +
</source>
 +
 
 +
<source lang="java">
 +
Player p = (Player)event.getWhoClicked();
 +
</source>
 +
 
 +
我们先要判断操作者是不是玩家(真的有非玩家操作物品的事)
 +
 
 +
<source lang="java">
 +
if(event.getWhoClicked() instanceof Player == false) { return;}    // 如果操作对象不是玩家则返回
 +
</source>
 +
 
 +
然后看TA是不是在操作我们指定的容器(用容器标题判定)
 +
 
 +
<source lang="java">
 +
if (event.getInventory().getTitle().equalsIgnoreCase("hello") )
 +
</source>
 +
 
 +
我们可以使用 getRawSlot() 来判断TA打开的是什么位置。
 +
 
 +
<source lang="java">
 +
if(event.getRawSlot() == 0 )
 +
</source>
 +
 
 +
需要知道的是,数组是从0开始计数的,左上角第一个格子的位置为0
 +
 
 +
具体如图所示
 +
 
 +
[[文件:InvNumber.jpg]]
 +
 
 +
如果不想让TA拿走物品,使用:
 +
 
 +
<source lang="java">
 +
event.setCancelled(true)
 +
</source>
 +
 
 +
但是这会有延迟,并且如果在拿走的一瞬间关闭界面可能会刷走物品
 +
 
 +
所以可以先关闭界面再打开容器
 +
 
 +
<source lang="java">
 +
p.closeInventory();
 +
p.openInventory(inv);
 +
</source>
 +
 
 +
代码范例:
 +
 
 +
<source lang="java">
 +
public void onInventoryClick(InventoryClickEvent event)
 +
{
 +
  {
 +
  if (event.getWhoClicked() instanceof Player == false) { return;}
 +
  Player p = (Player)event.getWhoClicked();
 +
  if (event.getInventory().getTitle().equalsIgnoreCase(" §2 !DC! ") )
 +
    {
 +
    event.setCancelled(true);
 +
    p.updateInventory();     
 +
        if(event.getRawSlot() == 0 )
 +
        {
 +
            p.closeInventory();
 +
            p.openInventory(inv);   
 +
        } 
 +
    }
 +
  }
 +
}
 +
</source>
 +
 
 +
版权所有,违者不究~
  
Besides the Map/HashMap classes, Java offers many other data structures. They offer these different classes because there are times when a Map is not the most appropriate. Here's a separate page for discussing [[Java data structure classes]] in more detail.
+
== Maps, and Sets, and Lists 天呐! <ref>标题中Map,Set,List均为Java类集中的接口,故不翻译</ref> ==
  
=== HashMaps and How to Use Them  ===
+
除了Map类和HashMap类,Java中还提供了其它的数据结构类集。 
<big>
 
'''Keep in mind to never use a player in a hashmap!'''
 
You need to use Strings instead. So use "p.getName()" to add, remove or check if a list contains a player.
 
Saving a player as an object causes huge memory leaks.
 
</big>
 
  
When making a plugin you will get to a point where just using single variables to state an event has happened or a condition has been met will be insufficient, due to more than one player performing that action/event.
+
他们提供这些不同的类,是因为有些时候,使用Map类并不合适。<nowiki>[[Java data structure classes]]</nowiki>在这个条目中,有对Java数据框架类集更深入的讲述。
  
This was the problem I had with one of my old plugins, Zones, now improved and re-named to Regions. I was getting most of these errors because I didn't consider how the plugin would behave on an actual server with more than one on at any given time. I was using a single boolean variable to check whether players were in the region or not and obviously this wouldn't work as the values for each individual player need to be separate. So if one player was in a region and one was out the variable would constantly be changing which could/would/did cause numerous errors.
+
=== HashMaps概览以及如何使用此接口  ===
  
A HashMap is an excellent way of doing this. A HashMap is a way of mapping/assigning a value to a key. You could set up the HashMap so that the key is a player and the value could be anything you want, however the useful things with HashMaps is that one key can only contain one value and there can be no duplicate keys. So say for example I put "adam" as the key and assigned a value of "a" to it. That would work as intended, but then say afterwards I wanted to assign the value of "b" to key "adam" I would be able to and would get no errors but the value of "a" assigned to key "adam" in the HashMap would be overwritten because HashMaps cannot contain duplicate values.
 
  
==== Defining a HashMap  ====
+
{{模板:待修正}}
<blockquote><source lang="java">public Map<KeyType, DataType> HashMapName = new HashMap<>(); //Example syntax
+
记住永远不要在一个HashMap直接使用Player玩家!
  
 +
应在HashMap中使用String字符串,使用 "p.getName()" 来添加,移除或是检查列表中是否含有某个玩家.
 +
 +
将玩家当作object储存在HashMap中会造成大量内存泄漏(就是费机器资源).
 +
 +
当做一个插件时,你会得到一个点,只使用单变量事件发生或条件已经满足不了由于多个玩家执行的动作/事件。 
 +
 +
这是我的问题,我有一个旧的插件,Zones,现在被改进,重新命名为Regions 。我得到了这些错误的大部分,因为我没有考虑插件会如何在一个实际的服务器上的行为,在任何给定的时间超过一个。我过去使用一个独立的布尔值变量来检测玩家是否在选区中,显然当每个玩家的值需要被区分开来时这个方法将不能正常工作。所以如果一个玩家在选区内而另一个在选区外,那么这个变量将会一直保持改变,因此也会造成许多错误。
 +
 +
HashMap是这样做的一个好方法。HashMap是一种映射/分配的一个关键值。你可以设置HashMap,关键是玩家的值可以做你想做的事情,但与HashMap的有用的东西是一个key的只能包含一个值,不能有重复的key。比如说,我把“adam”作为key,并赋予它“a”的值。那工作的打算,但后来说,后来我想指定值“B”为重点的“adam”我能会没有错误,但价值”“分配到关键的“adam”在HashMap将覆盖因为与哈希图不包含重复的值。
 +
 +
 +
==== 定义一个 HashMap  ====
 +
 +
<source lang="java">
 +
public Map<KeyType, DataType> HashMapName = new HashMap<>(); //Example syntax
 
// Example Declaration
 
// Example Declaration
 
public Map<String, Boolean> pluginEnabled = new HashMap<>();
 
public Map<String, Boolean> pluginEnabled = new HashMap<>();
 
public Map<String, Boolean> isGodMode = new HashMap<>();
 
public Map<String, Boolean> isGodMode = new HashMap<>();
</source> </blockquote>
+
</source>
Keep that code in mind because we will be using it for the rest of the tutorial on HashMaps. So, for example lets create a simple function which will toggle whether the plugin has been enabled or not. Firstly, inside your on command function which I explained earlier you will need to create a function to send the player name to the function and adjust the players state accordingly.
 
  
So inside on command you'll need this, the function name can be different but for the sake of simplicity it's best if you keep it the same.
+
记住代码因为我们会使用它的教程的其余部分在与哈希图。因此,例如,我们可以创建一个简单的函数,它将切换插件是否已启用或不启用。首先,在你的命令功能中,我解释了你需要创建一个函数,将玩家的名字发送到函数,并相应地调整玩家的状态。
<blockquote><source lang="java">Player player = (Player) sender;
+
 
togglePluginState(player);</source> </blockquote>
+
所以在命令的内部,你需要这个,函数名可以是不同的,但为了简单,它是最好的,如果你保持不变。
This code above will cast the value of sender to player and pass that argument to the function togglePluginState(). But now we need to create our togglePluginState() function.
+
 
<blockquote><source lang="java">public void togglePluginState(Player player) {
+
public boolean onCommand(Commandsender sender, Command cmd, String label, String args[])
    // Notice how we use the player name as the key here,
+
 
    // not the player object
+
<source lang="java">
    String playerName = player.getName();
+
Player player = (Player) sender;
    if (pluginEnabled.containsKey(playerName)) {
+
togglePluginState(player);
        if (pluginEnabled.get(playerName)) {
+
</source>
            pluginEnabled.put(playerName, false);
+
 
            player.sendMessage("Plugin disabled");
+
这上面的代码将发送到玩家的值传递给函数的参数togglepluginstate()
         } else {
+
 
            pluginEnabled.put(playerName, true);
+
但现在我们需要建立我们的togglepluginstate()功能。
            player.sendMessage("Plugin enabled");
+
 
        }
+
<source lang="java">
     } else {
+
public void togglePluginState(Player player)  
        pluginEnabled.put(playerName, true); //If you want plugin disabled by default change this value to false.
+
{
        player.sendMessage("Plugin enabled");
+
  // 请注意我们如何使用这个玩家名为这里的 key
    }
+
  // 不是玩家对象
}</source> </blockquote>
+
  String playerName = player.getName();  
Now, what this code is doing is checking if the HashMap first contains the key player, so if it has been put into the HashMap, if it is then we check the value of the HashMap key by get(player); if this is true then set value to false and send the player a message, else if the value is false then do the opposite, set the value to true and send a message again. But if the HashMap does not contain the key player then we can assume that this is their first run/use so we change the default value and add the player to the HashMap.
+
 
 +
    if (pluginEnabled.containsKey(playerName))  
 +
    {
 +
 
 +
        if (pluginEnabled.get(playerName))  
 +
        {
 +
            pluginEnabled.put(playerName, false);
 +
            player.sendMessage("Plugin disabled");
 +
        }
 +
         else  
 +
        {
 +
            pluginEnabled.put(playerName, true);
 +
            player.sendMessage("Plugin enabled");
 +
        }
 +
    }  
 +
     else  
 +
    {
 +
        pluginEnabled.put(playerName, true);  
 +
        //如果你想要的插件默认情况下为禁用更改此值为 false
 +
        player.sendMessage("Plugin enabled");
 +
 
 +
    }
 +
 
 +
}
 +
</source>
 +
 
 +
现在,这个代码是做个检查,如果HashMap首先包含的关键玩家,所以如果它被放入HashMap,如果那时候我们检查HashMap关键值获得(玩家);如果这是真的然后设定值假送玩家一个消息,如果这个值是false,然后做相反,设定值为true,发送一个消息了。但如果HashMap不包含关键的玩家,那么我们可以认为这是他们的第一次 运行/使用 所以我们改变默认值,增加玩家的HashMap
  
==== More Ideas for HashMaps ====
+
==== More Ideas for HashMaps  ====
  
A HashMap (or really any kind of Map in Java) is an association. It allows quick and efficient lookup of some sort of '''value''', given a unique '''key'''. Anywhere this happens in your code, a Map may be your solution.
+
一个HashMap(或是任何一种Map在Java)是一个关联系统。它可以快速、高效地查找某种值,赋予独特的key。在任何情况下,这会发生在您的代码,地图可能是您的解决方案。
  
Here are a few other ideas which are ideally suited to using Maps. As you will see, it doesn't have to be data that you store per player, but can be any kind of data that needs to be "translated" from one form to another.
+
这里有一些其他的想法非常适合使用map。正如你所看到的,它不必是你存储的数据或你的玩家,但可以是任何类型的数据,需要“翻译”从一个形式到另一个。
  
===== Data Value Lookups =====
+
===== Data Value Lookups  =====
  
<blockquote><source lang="java">
+
<source lang="java">
 
public Map<String, Integer> wool_colors = new HashMap<>();
 
public Map<String, Integer> wool_colors = new HashMap<>();
 
 
// Run this on plugin startup (ideally reading from a file instead of copied out row by row):
 
// Run this on plugin startup (ideally reading from a file instead of copied out row by row):
 
wool_colors.put("orange", 1);
 
wool_colors.put("orange", 1);
 
wool_colors.put("magenta", 2);
 
wool_colors.put("magenta", 2);
 
wool_colors.put("light blue", 3);
 
wool_colors.put("light blue", 3);
  ..
 
 
wool_colors.put("black", 15);
 
wool_colors.put("black", 15);
 +
// Run this in response to user commands - turn "green" into 13
  
// Run this in response to user commands - turn "green" into 13
 
 
int datavalue = 0;
 
int datavalue = 0;
if (wool_colors.containsKey(argument)) {
+
public boolean onCommand(Commandsender sender, Command cmd, String label, String args[])
     datavalue = wool_colors.get(argument);
+
    if (wool_colors.containsKey(argument))  
} else {
+
     { datavalue = wool_colors.get(argument); }
     try { datavalue = Integer.parseInt(argument); }
+
    else  
     catch (Exception e) {}
+
    {
 +
        try  
 +
        { datavalue = Integer.parseInt(argument); }
 +
        catch (Exception e) {}
 
}
 
}
</source> </blockquote>
+
</source>
 +
 
 +
==== Saving/Loading a HashMap  ====
 +
 
 +
一旦你知道如何使用与哈希图,你可能想知道如何保存和加载的HashMap数据。
  
==== Saving/Loading a HashMap  ====
+
*您不希望管理员手动编辑数据
  
Once you know how to work with HashMaps, you probably want to know how to save and load the HashMap data. Saving and loading HashMap data is appropriate if
+
* 你需要以二进制格式保存数据
  
*you don't want an administrator to edit the data manually
+
*你想避免解析块名称 和/或 其他物体从任意文本上
  
*you need to save data in binary format (too complex to organize for YAML)
+
这是很简单的方法如何保存任何HashMap。你可以替换HashMap ,amp ,String, Integer;任何你想要的HashMap类型。让我们举个例子让HashMap代码保存它:
  
*you want to avoid parsing block names and/or other objects from freeform text
+
<source lang="java">
 +
HashMap<String, Integer> mapToSave = new HashMap<String,Integer>();
  
This is very simple way how to save any HashMap. You can replace HashMap&lt;String, Integer&gt; with any type of HashMap you want. Let's take an example HashMap with code to save it:
+
public void save(HashMap<String, Integer> map, String path)  
<blockquote><source lang="java">
+
{
        HashMap<String, Integer> mapToSave = new HashMap<String,Integer>();
+
    try  
        public void save(HashMap<String, Integer> map, String path) {
+
    {
try {
+
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
+
        oos.writeObject(map);
oos.writeObject(map);
+
        oos.flush();
oos.flush();
+
        oos.close();
oos.close();
+
    }
//Handle I/O exceptions
+
    catch(Exception e) // Hand a IOexceptions
} catch(Exception e) {
+
    { e.printStackTrace(); }
e.printStackTrace();
 
}
 
 
}
 
}
 +
// ...
 +
save(mapToSave, getDataFolder() + File.separator + "example.bin");
 +
</source>
  
// ...
+
你可以看到它真的很容易。加载工作非常相似但我们使用对象输入流而不是对象,而是个FileInputStream,代替writeobject() 让我们返回HashMap。
  
save(mapToSave, getDataFolder() + File.separator + "example.bin");</source></blockquote>
+
<source lang="java">
You can see it's really easy. Loading works very very similar but we use ObjectInputStream instead of ObjectOutputStream ,FileInputStream instead of FileOutputStream,readObject() instead of writeObject() and we return the HashMap.
+
public HashMap<String, Integer> load(String path)  
<blockquote><source lang="java">public HashMap<String, Integer> load(String path) {
+
{
try {
+
    try  
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
+
    {
Object result = ois.readObject();
+
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
//you can feel free to cast result to HashMap<String, Integer> if you know there's that HashMap in the file
+
        Object result = ois.readObject();
return (HashMap<String, Integer>)result;
+
        // 如果你知道这文件中的HashMap ,你可以随意投结果HashMap <字符串,整数>
} catch(Exception e) {
+
        return (HashMap<String, Integer>)result;
e.printStackTrace();
+
    }  
}
+
    catch(Exception e)  
 +
    { e.printStackTrace(); }
 
}
 
}
  
Line 732: Line 1,186:
  
 
String path = getDataFolder() + File.separator + "example.bin";
 
String path = getDataFolder() + File.separator + "example.bin";
 +
 
File file = new File(path);
 
File file = new File(path);
  
if (file.exists()) { // check if file exists before loading to avoid errors!
+
if (file.exists()) // 检查是否有文档
loadedMap = load(path);
+
{ loadedMap  = load(path); }
}</source></blockquote>
+
</source>
You can use this "API" for saving/loading HashMaps, ArrayLists, and all Objects which implement Serializable or Externalizable interface.
+
 
<blockquote><source lang="java">/** SLAPI = Saving/Loading API
+
你可以使用这个“API”保存/加载与哈希图,数组列表,并执行序列化或外部接口的所有对象。
* API for Saving and Loading Objects.
+
 
* Everyone has permission to include this code in their plugins as they wish :)
+
<source lang="java">
* @author Tomsik68<tomsik68@gmail.com>
+
/** SLAPI = Saving/Loading API
*/
+
 
 +
 * API for Saving and Loading Objects.
 +
 
 +
 * Everyone has permission to include this code in their plugins as they wish :)
 +
 
 +
 * @author Tomsik68<tomsik68@gmail.com>
 +
 
 +
 */
 
public class SLAPI
 
public class SLAPI
 
{
 
{
public static <T extends Object> void save(T obj,String path) throws Exception
+
    public static <T extends Object> void save(T obj,String path) throws Exception
{
+
    {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
+
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
oos.writeObject(obj);
+
    oos.writeObject(obj);
oos.flush();
+
    oos.flush();
oos.close();
+
    oos.close();
}
+
    }
public static <T extends Object> T load(String path) throws Exception
+
 
{
+
    public static <T extends Object> T load(String path) throws Exception
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
+
    {
T result = (T)ois.readObject();
+
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
ois.close();
+
    T result = (T)ois.readObject();
return result;
+
    ois.close();
}
+
    return result;
}</source> </blockquote>
+
    }
Example implementation of this API: '''I'm skipping some part of code in this source'''  
+
}
<blockquote><source lang="java">public class Example extends JavaPlugin {
+
</source> 
private ArrayList<Object> list = new ArrayList<>();
+
 
 +
Example implementation of this API: <nowiki>'''</nowiki>I'm skipping some part of code in this source<nowiki>'''</nowiki> 
 +
 
 +
<source lang="java">
 +
public class Example extends JavaPlugin  
 +
{
 +
private ArrayList<Object> list = new ArrayList<>();
  
@Override
+
    @Override
public void onEnable() {
+
    public void onEnable()  
            try {
+
    {
list = SLAPI.load("example.bin");
+
       try  
            } catch(Exception e) {
+
        { list = SLAPI.load("example.bin"); }
                //handle the exception
+
        catch(Exception e) // handle the exception
                e.printStackTrace();
+
        { e.printStackTrace(); }
            }
+
    }
}
+
 +
    @Override
 +
    public void onDisable()
 +
    {
 +
        try
 +
        { SLAPI.save(list,"example.bin"); }  
 +
        catch(Exception e)
 +
        { e.printStackTrace(); }
 +
    }
 +
}
 +
</source>
  
@Override
+
Note #1: 这将未修改的几乎所有知名Java类型如整型,字符串,HashMap。它还将为一些Bukkit类型以及工作。如果你在写你自己的数据对象类,你可能想利用这一技术保存它们的状态,你应该阅读关于Java的序列化或外部接口。可外化和序列化的唯一区别是,自动将所有的,序列化的类的域和试图序列化,而外部的允许你定义的阅读和写作的对象的方法。它很容易添加到您的代码,它会使您的数据持久性非常小的工作需要。没有更多的解析!
public void onDisable() {
 
            try {
 
SLAPI.save(list,"example.bin");
 
            } catch(Exception e) {
 
                e.printStackTrace();
 
            }
 
}
 
}</source> </blockquote>
 
Note #1: This will work un-modified with almost all well-known Java types like Integer, String, HashMap. It will also work for some Bukkit types as well. If you're writing your own data object classes, and you may want to save their state using this technique, you should read about Java's Serializable or Externalizable interface. The only difference between Externalizable and Serializable is, that Serializable automatically takes all of class's fields and tries to serialize them, while Externalizable allows you to define method for reading and writing the Object. It's easy to add to your code, and it will make your data persistent with very little work required. No more parsing!
 
  
Note #2: This API doesn't support changes. Once you change something in the class, data files saved with older version of your plugin won't load correctly.
+
Note #2: 该API不支持变化。一旦你改变了类中的东西,你的插件的旧版本保存的数据文件将无法正确加载。 
  
 
===== Tips & Examples =====
 
===== Tips & Examples =====
  
1.) Simplify your save structure
+
1. 简化你的储蓄结构
  
Try to use as much simple types as possible. E.g. if you want to save player, save their UUID instead. If you want to save world, save its UUID. If you want to save location, save x,y,z world UUID. DO NOT DIRECTLY SAVE BUKKIT TYPES!
+
让要被保存的数据越简单越好, 例如: 如果你要保存一个Player对象,你可以保存该对象的UUID/Name来代替; 如果你想要保存Localtion对象, 你可以保存它的x,y,z以及World对象UUID. '''不要直接保存Bukkit的原始类型'''
  
2.) Save version number along with data
+
2. 保存版本号以及数据
  
 
You should always remember, that you don't know what you'll be saving in the same file tomorrow. Will you ever migrate this file because of newer version of your plugin, bukkit, or minecraft? You don't know!
 
You should always remember, that you don't know what you'll be saving in the same file tomorrow. Will you ever migrate this file because of newer version of your plugin, bukkit, or minecraft? You don't know!
  
3.) Migrate older files
+
3. 迁移旧文件
  
 
If your plugin finds older version of some file, it should update the file accordingly and change version number.
 
If your plugin finds older version of some file, it should update the file accordingly and change version number.
  
== Metadata ==
+
== 元数据 ==
  
Bukkit is trying to make plugin development as easy as possible, so HashMaps with key of type Player, Entity, World or even a Block were replaced by Metadata. Metadata is some kind of alternative to HashMap. It allows you to add custom "fields" to Players, Entities, Worlds and Blocks. These things are all members of Metadatable class(check [http://jd.bukkit.org/doxygen/de/d59/interfaceorg_1_1bukkit_1_1metadata_1_1MetadataValue.html#ab49975fe013a0626dd29d3b85c63a82f])It works very simply. Everything that is Metadatable holds its own HashMap of Metadata which you have access to. That means, for example, if you're creating an economy plugin, you would need a HashMap of Player and Float or Double. With Metadata, you don't have to! You just attach to player new metadata value, and that's it!<br>  
+
Bukkit正在尝试尽可能的让制作插件变得更简单,所以带有一个键值例如Player, Entity, World,甚至是一个block的HashMaps都可以用元数据取缔掉。元数据是一些替代HashMap的方式。他可以允许你去自定义"fields"给Players, Entities, Worlds and Blocks.这些东西都是元数据类的一个成员(详见[http://jd.bukkit.org/doxygen/de/d59/interfaceorg_1_1bukkit_1_1metadata_1_1MetadataValue.html#ab49975fe013a0626dd29d3b85c63a82f])It works very simply. Everything that is Metadatable holds its own HashMap of Metadata which you have access to. That means, for example, if you're creating an economy plugin, you would need a HashMap of Player and Float or Double. 如果用了元数据,你不必去做! You just attach to player new metadata , and that's it!<br>  
  
=== Why to use Metadata ===
+
=== 为何要去使用元数据 ===
  
 
* Metadata is all handled by Bukkit, which makes it a very good alternative to HashMaps.  
 
* Metadata is all handled by Bukkit, which makes it a very good alternative to HashMaps.  
* Metadata can be used to share information between plugins.
+
* 元数据可以用来跟其他插件分享信息与数据。
  
=== Why not use Metadata<br>  ===
+
=== 为什么不去使用元数据<br>  ===
  
 
* Slightly more difficult to get the value.
 
* Slightly more difficult to get the value.
 
* It is not saved on shutdown (but then again, neither are any Maps that you create).
 
* It is not saved on shutdown (but then again, neither are any Maps that you create).
  
=== Getting &amp; Setting Metadata ===
+
=== 获取 &amp; 设置元数据 ===
  
 
<source lang="java">
 
<source lang="java">
Line 855: Line 1,324:
 
The coding for plugins accessing MySQL is mostly the same as tiny SQLite or mega-sized Oracle, with only small differences in syntax here or there. But the administration has room to grow. You may want to set up accounts and privileges inside your MySQL setup. You may want to set up SQL scripts that organize your backups and rollback to previous states.  
 
The coding for plugins accessing MySQL is mostly the same as tiny SQLite or mega-sized Oracle, with only small differences in syntax here or there. But the administration has room to grow. You may want to set up accounts and privileges inside your MySQL setup. You may want to set up SQL scripts that organize your backups and rollback to previous states.  
  
== Deploying your Plugin  ==
+
== 简易定时器 ==
 +
{{Main|Bukkit/插件开发教程/定时器编程}}
 +
要定时当然是用 BukkitScheduler
 +
 
 +
要定时就和long脱不了干系
  
Once you have written your plugin, how do you get it from a collection of source files into a working jar file that can be installed on a server? First, set up a CraftBukkit server on your local machine. To do this, visit the wiki page on [http://wiki.bukkit.org/Setting_up_a_server Setting up a server]. Next you have to export your plugin to a .jar so that you can run it on your new server. To do this in Eclipse, right-click the project and click ''Run as > Maven install'':
+
long是一种整数数据类型,数值后面要加 <big>L</big> :
  
[[Image:Maveninstall.png]]
+
<source lang="java">
 +
public long a = 233L ;
 +
</source>
  
In the future, when you make code changes to your plugin, you want to delete the previous JAR by right-clicking the project and clicking ''Run as > Maven clean'' before doing the above. If you're having issues when building your plugin, check if your Java Development Kit (JDK) is properly installed and review [[Setting Up Your Workspace]]. You may need to configure your JDK manually if you see a JDK-related error in the console, as Eclipse may not have detected it correctly. Go to ''Window -> Preferences'', and go to ''Java -> Installed JREs''. Add the latest JDK you've installed as a JRE, tick that one, and untick the active one that was giving you issues:
+
Java的时间单位是1000L一秒
  
[[Image:Jrelocation.png]]
+
Bukkit的时间单位是20L一秒
  
If your project built successfully, the JAR file is now under the ''target'' folder in your project's folder under your Eclipse workspace. The JAR file you have exported should now be a working plugin!&nbsp;Assuming of course that there are no errors in your code or your plugin.yml file. You can now drop the jar file you have exported into your Bukkit server's "plugins"&nbsp;folder, reload or relaunch the server, and test away! In order to connect to a server running locally on your computer, simply put "localhost"&nbsp;as the IP&nbsp;address of the server in Minecraft multiplayer. If you run into errors that you can't solve for yourself, try visiting the [http://forums.bukkit.org/forums/plugin-development.5/ plugin development forum], asking in the [http://wiki.bukkit.org/IRC bukkitdev IRC channel], or re-reading this wiki. Once you have a useful working plugin, consider submitting your project to [http://dev.bukkit.org/ dev.bukkit] for consumption by the Bukkit community. From the wizard above, you can see that the JAR file will be by default a compressed archive (JARs are based on the ZIP archive format). As such, it does not make sense to put your JAR into a ZIP archive when uploading to BukkitDev and will only increase the file size. Further, config files can be placed within the JAR and copied into the plugin's data folder if the configuration file does not exist. There is usually no good reason for packaging JAR files into another archive.
+
还要扯到Bukkit的线程系统上
  
== Importing other plugins ==
+
Bukkit服务器是使用一条线程运行的,所以在插件里随便搞java线程是非常危险的
You may wish to edit another plugin that has the source available. If that plugin has a ''pom.xml'' in its folder (most of the popular ones, for example WorldEdit and Essentials, do), you can import it as a project by selecting ''File -> Import'', and then opening the ''Maven'' folder and selecting ''Existing Maven Projects'':
 
  
[[Image:Importmaven.png]]
+
说白了,当你在插件里用这条代码时:
  
Then select the folder that the ''pom.xml'' is in, and the project should be on your sidebar. Edit it and compile it like you usually would.
+
Thread.sleep(5*1000L);
 +
  // 你认为是插件“睡”五秒
  
== Tips and Tricks  ==
+
整个服务器会停止五秒...
  
=== Setting a Player on Fire ===
+
所以要用Bukkit提供的东西
  
The Bukkit API is capable of a lot of cool stuff. Here are some code snippets for some nice effects!
+
这里将会使用BukkitRunnable定时
  
The following code allows a player to set another player on fire. Running a command like '''/ignite Notch''' would cause Notch to be set on fire!
 
 
<source lang="java">
 
<source lang="java">
@Override
+
new BukkitRunnable()
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
+
{
     // Uses equalsIgnoreCase() over equals() to accept "ignite" and "IgNiTe."
+
 
     if (cmd.getName().equalsIgnoreCase("ignite")) {
+
     int time = 60;  // 六十秒
         // Make sure that the player specified exactly one argument (the name of the player to ignite).
+
 
         if (args.length != 1) {
+
    @Override
            // When onCommand() returns false, the help message associated with that command is displayed.
+
    public void run()  
            return false;
+
     {
 +
         if(time == 0)
 +
         {
 +
          cancel()// 终止线程
 +
            return;
 
         }
 
         }
 +
        // your code ...
 +
    }
 +
               
 +
}.runTaskTimer(this, 0L, 20L);
 +
          // 插件主类  延时  定时
 +
</source>
 +
 +
== 随机器 ==
 +
 +
这玩意经常出现在娱乐性插件中,主要用它来抽某个玩家
 +
 +
Random实现的随机算法是伪随机,也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。
 +
 +
但是相同种子数的Random对象,相同次数生成的随机数字是完全相同的。这点在生成多个随机数字时需要特别注意。
 +
 +
接下来是使用普通jar运行,所以会有大量 <big>static</big> 关键字,请注意
 +
 +
<source lang="java">
 +
 +
    public static void say(Object the) //输出
 +
    {
 +
        System.out.print(the);
 +
    }
 +
 +
</source>
 +
 +
用Random有两种方法
 +
 +
public static Random rd = new Random();
 +
rd.*
 +
 +
(new Random()).*
 +
 +
接下来用第二种来讲
 +
 +
Random在很多地方都能学到,这里只举插件常用的int作为例子
 +
 +
(new Random()).nextInt(整数);
 +
 +
整数就是上限,随机范围从 0 ~ 整数-1
  
        // Make sure the sender is a player.
+
每一个随机数的几率都是相等的,也就是说 0123456789他们的几率都是 10%
        if (!(sender instanceof Player)) {
 
            sender.sendMessage("Only players can set other players on fire.");
 
            sender.sendMessage("This is an arbitrary requirement for demonstration purposes only.");
 
            return true;
 
        }
 
  
        // Get the player who should be set on fire. Remember that indecies start with 0, not 1.
+
但如果要随机范围最低不是0呢
        Player target = Bukkit.getServer().getPlayer(args[0]);
 
  
        // Make sure the player is online.
+
放个丧心病狂的代码:
        if (target == null) {
 
            sender.sendMessage(args[0] + " is not currently online.");
 
            return true;
 
        }
 
  
        // Sets the player on fire for 1,000 ticks (there are ~20 ticks in second, so 50 seconds total).
+
<source lang="java">
        target.setFireTicks(1000);
+
    public static void ran(int start ,int to)
        return true;
+
    {
 +
        say( (new Random()).nextInt(start)+to +" ");
 +
        say( (new Random()).nextInt(start)+to +" ");
 +
        say( (new Random()).nextInt(start)+to +" ");
 +
        say( (new Random()).nextInt(start)+to +" ");
 +
        say( (new Random()).nextInt(start)+to +" ");
 +
        say( (new Random()).nextInt(start)+to +" ");
 +
        say( (new Random()).nextInt(start)+to +" ");
 +
        say( (new Random()).nextInt(start)+to +" ");
 +
        say( (new Random()).nextInt(start)+to +" ");
 +
        say( (new Random()).nextInt(start)+to +" ");  
 
     }
 
     }
     return false;
+
</source>
 +
 
 +
<source lang="java">
 +
public static void main(String[] args)
 +
{ ran(5,2); }
 +
</source>
 +
 
 +
取值范围是 2 ~ 2+(5-1)
 +
 
 +
弄一方程式(设 start=x ,to=y )
 +
x ~ x+(y-1)
 +
 
 +
结果就是(一行一次结果):
 +
6 5 6 6 3 3 6 6 5 6
 +
4 3 2 4 5 2 2 3 6 2
 +
6 3 3 5 4 2 5 2 2 2
 +
 
 +
那怎么随机玩家呢,这就要扯到ArrayList了
 +
 
 +
ArrayList可以直接储存Player 所以...
 +
 
 +
看看HotPotato(烫手山芋)是怎么挑中某个倒霉的玩家的吧
 +
 
 +
<source lang="java">
 +
public void giveRandomPotato()
 +
{
 +
if (getAlivePlayers().size() <= 1)  // 如果只剩一个玩家
 +
return;
 +
GamePlayer gp = (GamePlayer)getAlivePlayers().get((new Random()).nextInt(getAlivePlayers().size() - 1));  // 就是这里
 +
gp.getPlayer().getInventory().setHelmet(new ItemStack(Material.TNT));  // 呵呵
 +
gp.getPlayer().getInventory().addItem(new ItemStack[] {
 +
ItemUtil.potato() 
 +
});
 +
potatoPlayer = gp.getPlayer();
 +
GamePlayer gamePlayer;
 +
for (Iterator iterator = gamePlayers.iterator(); iterator.hasNext(); MessageUtil.sendTextMessage(gamePlayer.getPlayer(), "newPotato", gp.getPlayer().getName()))
 +
gamePlayer = (GamePlayer)iterator.next();
 +
 
 +
new PotatoTimer(this);  // 另一个定时器(Boom!)
 +
}
 +
</source>
 +
 
 +
<source lang="java">
 +
public List getAlivePlayers()
 +
{
 +
List alive = new ArrayList();
 +
for (Iterator iterator = gamePlayers.iterator(); iterator.hasNext();)
 +
{     // 遍历列表中的玩家
 +
GamePlayer gamePlayer = (GamePlayer)iterator.next();
 +
if (gamePlayer.isAlive())
 +
alive.add(gamePlayer);
 +
}
 +
 
 +
return alive;
 
}
 
}
 
</source>
 
</source>
  
=== Killing the player ===
+
但如果要抽取多个玩家呢?
 +
 
 +
可用方法太多,时间成本太贵,举个不是例子的例子。
 +
 
 +
  其实是懒 ~
  
To keep with the theme, here's a way to kill the player.
+
<source lang="java">
  
Use this for your onCommand method:
+
public class Support
<blockquote><source lang="java">
+
{
@Override
+
    public List allplayer;
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
+
    public List allplayerCopy; // 拷贝
    if (cmd.getName().equalsIgnoreCase("KillPlayer")) {
+
    public List hider;
         Player target = sender.getServer().getPlayer(args[0]);
+
    public List seeker;
        // Make sure the player is online.
+
    public Random rd; // 随机器
         if (target == null) {
+
    public Support()
             sender.sendMessage(args[0] + " is not currently online.");
+
    {
             return true;
+
        allplayer = new ArrayList()
 +
        {{
 +
            add("player01");
 +
            add("player02");
 +
            add("player03");
 +
            add("player04");
 +
            add("player05");
 +
            add("player06");
 +
            add("player07");
 +
            add("player08");
 +
            add("player09");
 +
            add("player10");
 +
        }};
 +
        allplayerCopy = new ArrayList();
 +
        allplayerCopy.clear();
 +
        allplayerCopy.addAll(allplayer);
 +
        hider = new ArrayList();
 +
        seeker = new ArrayList();
 +
         rd = new Random();
 +
        ran(3);
 +
        System.out.print("hider: "+ hider + "  ");
 +
        System.out.print("seeker: "+ seeker);
 +
    }
 +
   
 +
    public void ran(int amount)
 +
    {
 +
        int ta; // 数量
 +
        Object player ;  // 模拟玩家
 +
         if(allplayerCopy.size() <= 1)
 +
        { return; }
 +
        for (ta = amount;ta > 0; ta--)
 +
        {
 +
             player = allplayerCopy.get(rd.nextInt(allplayerCopy.size() )); // 抽取一个玩家
 +
            seeker.add(player);
 +
             allplayerCopy.remove(player);  
 
         }
 
         }
         target.setHealth(0);  
+
         hider.clear();
 +
        hider.addAll(allplayerCopy);
 +
        allplayerCopy.clear();
 
     }
 
     }
     return false;
+
      
}</source></blockquote>
+
 
Here is an extension to that, that will kill the player with an explosion:  
+
}
<blockquote><source lang="java">
+
 
float explosionPower = 4F; //This is the explosion power - TNT explosions are 4F by default
+
</source>
Player target = sender.getWorld().getPlayer(args[0]);
+
 
target.getWorld().createExplosion(target.getLocation(), explosionPower);
+
结果:
target.setHealth(0);
+
 
</source> </blockquote>
+
hider: [player01, player02, player03, player05, player06, player08, player10]
 +
seeker: [player09, player07, player04]
 +
 
 +
hider: [player02, player05, player06, player07, player08, player09, player10] 
 +
seeker: [player04, player03, player01]
 +
 
 +
hider: [player01, player03, player04, player06, player07, player09, player10]  
 +
seeker: [player08, player02, player05]
 +
 +
不要问我10号模拟玩家(String)怎么回事,他人品好~
  
=== Creating a Fake Explosion  ===
+
如果要搞几率呢?
  
This code produces the TNT/Creeper Visual and Audio effects. However, no explosion damage is dealt to surrounding entities or blocks. This is useful for nerfing explosions while still keeping the aesthetics of them.
+
<source lang="java">
<blockquote><source lang="java">
 
@EventHandler
 
public void onExplosionPrime(ExplosionPrimeEvent event) {
 
    Entity entity = event.getEntity();
 
  
    // If the event is about primed TNT (TNT that is about to explode), then do something
+
if((new Random().nextInt(100)+1) < 50){
    if (entity instanceof TNTPrimed) {
+
//50%的几率
        entity.getWorld().createExplosion(entity.getLocation(), 0);
+
}else{
    }
+
//另外50%
 
}
 
}
</source></blockquote>
 
  
=== Hiding a Player From Another Player  ===
+
</source>
 +
 
 +
== 部署你的插件  ==
 +
 
 +
=== Eclipse 构建方法 ===
 +
 
 +
总之你写好的你的插件,如何把它变成一个可安装在服务端的jar文件呢?
 +
 +
首先我们需要建立一个水桶服务器。 [http://wiki.bukkit.org/Setting_up_a_server 在这里] 能找到开服相关信息.接下来你需要把插件导出为一个.jar文件,
 +
 +
这样你就能在你的服务器里运行它。 在Eclipse里, 右键工程,点击''Run as > Maven install''即可:
 +
 
 +
[[Image:Maveninstall.png]]
 +
 
 +
将来呢, 当你对插件进行了改动后,在做上述操作之前,如果你想删除之前的.jar文件,你可以右键工程,点击 ''Run as > Maven clean'' 。
 +
 +
如果你在编译插件时遇到了问题,  请检查你的JDK是否正确安装, 并浏览[[建立工作区]]条目。
 +
 +
如果你在Eclipse控制台看到了和JDK有关的错误,你可能需要手动调整JDK,因为Eclipse的检测可能发生了错误。
 +
 +
点击''Window -> Preferences'', 然后是''Java -> Installed JREs''。选中你最近安装的JDK作为Java运行环境(JRE)并添加, 并取消掉原来那个报错的JDK:
 +
 
 +
[[Image:Jrelocation.png]]
 +
 
 +
如果你的工程成功被编译,JAR文件会生产在 ''target'' 目录下,具体在Eclipse的工作空间里名字为工程名的文件夹中。
 +
 +
这个JAR文件就是一个能正常工作的Bukkit插件了。当然前提是你的plugin.yml里没出差错。
 +
 +
你可以把这个jar文件丢到服务器的plugins文件夹里,重载或重启服务器,就可以测试你的新插件啦。
 +
 +
为了连接上使用你自己的电脑开的服务器 ,在多人游戏的ip里填上你的本地ip即可。
 +
 +
如果出现了错误而你又不能解决的话, 试试这里[http://forums.bukkit.org/forums/plugin-development.5/ plugin development forum],
 +
 +
在这里提问[http://wiki.bukkit.org/IRC bukkitdev IRC channel],或者重新阅读wiki。
 +
 +
当你做完了一个实用的插件,可以考虑把它发表在这里[http://dev.bukkit.org/ dev.bukkit](为了Bukkit社区的访问量)。
 +
 +
以上可知,JAR格式是默认压缩生成的(JAR文件基于ZIP文件的格式)。这么说来,在上传插件到Bukkit上时把JAR文件压缩成ZIP只会增加文件体积罢了。
 +
 +
而且呢, 放在JAR中的config文件可以在检测不到插件目录(插件自动生成的文件夹,名字与插件名相同,里面是该插件的配置)下的config文件时自动生成。
 +
 +
所以我们一般不把jar文件转换成其他格式。
 +
 
 +
=== NetBeanIDE 构建方法 ===
 +
 
 +
[[文件:NetBeansIDE-build.jpg]]
 +
 
 +
然后到项目文件所在地打开dist文件夹就能看到了
 +
 
 +
== 导入其它插件 ==
 +
你可能会希望编辑其它那些有源代码的插件. 如果插件在它的目录下有''pom.xml''(大多数知名插件,例如WorldEdit和Essentials都有),你可以通过点击''File -> Import''来把它作为一个工程导入, 然后打开''Maven''项并选择''Existing Maven Projects'':
 +
 
 +
[[Image:Importmaven.png]]
 +
 
 +
然后选择''pom.xml''所在的目录,这个工程就应该出现在你的侧边栏里了。就像平常一样编辑并编译它吧。
 +
 
 +
== 小贴士  ==
 +
一 Bukkit API有很多碉堡的功能。以下是实现一些功能的代码片段,并不是完整代码,请注意用import导入Bukkit相关的库
 +
=== 使玩家着火 ===
 +
 
 +
下面的代码运行一个玩家使另一个玩家着火。通过类似于'''/ignite Notch'''这样的指令,这个指令会让 Notch 着火。
 +
 
 +
<source lang="java">
 +
 
 +
@Override
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 +
    // 使用 equalsIgnoreCase() 来忽视大小写
 +
    if (cmd.getName().equalsIgnoreCase("ignite")) {
 +
        // 确保玩家只输入了一个自变量(要被点着的玩家名).
 +
        if (args.length != 1) {
 +
            // 当return false时,该指令的使用信息会显示出来。
 +
            return false;
 +
        }
 +
 +
        // 确保命令发出者是个玩家.
 +
        if (!(sender instanceof Player)) {
 +
            sender.sendMessage("Only players can set other players on fire.");
 +
            sender.sendMessage("This is an arbitrary requirement for demonstration purposes only.");
 +
            return true;
 +
        }
 +
 +
        // 获取那个要被点着的玩家. 请注意下标起始是0,不是1.
 +
        Player target = Bukkit.getServer().getPlayer(args[0]);
 +
 +
        // 确保这个玩家在线。
 +
        if (target == null) {
 +
            sender.sendMessage(args[0] + " is not currently online.");
 +
            return true;
 +
        }
 +
 +
        // 让这个玩家着火1000 ticks (一秒钟大概有20ticks,所以总共是50秒).
 +
        target.setFireTicks(1000);
 +
        return true;
 +
    }
 +
    return false;
 +
}
 +
 
 +
</source>
 +
 
 +
=== 干掉玩家  ===
 +
 
 +
和标题一样.这里是要干掉某个玩家。
 +
 
 +
在你的onCommand方法里使用这个:
 +
 
 +
<source lang="java">
 +
 
 +
@Override
 +
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
 +
    if (cmd.getName().equalsIgnoreCase("KillPlayer")) {
 +
        Player target = sender.getServer().getPlayer(args[0]);
 +
          // 确保玩家在线.
 +
        if (target == null) {
 +
            sender.sendMessage(args[0] + " is not currently online.");
 +
            return true;
 +
        }
 +
        target.setHealth(0);
 +
    }
 +
    return false;
 +
}
 +
 
 +
</source>
 +
 
 +
这里有个小扩展,会使用一个爆炸来杀死玩家:
 +
 
 +
<source lang="java">
 +
 
 +
float explosionPower = 4F; //爆炸等级 - TNT的爆炸等级默认是4F
 +
Player target = sender.getWorld().getPlayer(args[0]);
 +
target.getWorld().createExplosion(target.getLocation(), explosionPower);
 +
target.setHealth(0);
 +
 
 +
</source>
 +
 
 +
=== 创造一个假爆炸  ===
 +
 
 +
这段代码会生产一个看起来和听起来与TNT/苦力怕产生的爆炸一样的效果。但是不会对实体和方块产生破坏。 在保留爆炸的艺术的同时又削弱了爆炸的威力,真是太有用了
 +
<source lang='java'>
 +
@EventHandler
 +
public void onExplosionPrime(ExplosionPrimeEvent event) {
 +
    Entity entity = event.getEntity();
 +
 +
    // 如果事件和点燃的TNT有关,执行一些代码(又略)
 +
    if (entity instanceof TNTPrimed) {
 +
        entity.getWorld().createExplosion(entity.getLocation(), 0);
 +
    }
 +
}
 +
</source>
 +
 
 +
=== 使一个玩家对另外一个玩家隐形 ===
  
This will hide the player who used this command from a specified player. Everyone else will be able to see the player.
+
通过输入指令,能让指令执行者对特定的玩家隐形。 然而其他人仍然能看到指令执行者。
  
 
<source lang="java">
 
<source lang="java">
@Override
+
 
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
+
@Override
    if (cmd.getName().equalsIgnoreCase("HideMe") && args.length == 1) {
+
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        if (!(sender instanceof Player)) {
+
    if (cmd.getName().equalsIgnoreCase("HideMe") && args.length == 1) {
            sender.sendMessage("Only players can use this command!");
+
        if (!(sender instanceof Player)) {
            return true;
+
            sender.sendMessage("Only players can use this command!");
        }
+
            return true;
        // After checking to make sure that the sender is a Player, we can safely case it to one.
+
        }
 +
        // 检测指令执行者是玩家后,我们便可以安全的进行强制转换类操作。
 
         Player s = (Player) sender;
 
         Player s = (Player) sender;
 
+
         // Gets the player who shouldn't see the sender.
+
         // 获取命令执行者指定的玩家
 
         Player target = Bukkit.getServer().getPlayer(args[0]);
 
         Player target = Bukkit.getServer().getPlayer(args[0]);
 
         if (target == null) {
 
         if (target == null) {
Line 979: Line 1,745:
 
             return true;
 
             return true;
 
         }
 
         }
         // Hides a given Player (s) from someone (target).
+
         // s target 的视野中隐藏起来.
 
         target.hidePlayer(s);
 
         target.hidePlayer(s);
 
         return true;
 
         return true;
 
     }
 
     }
 
     return false;
 
     return false;
}</source>
+
}
 +
 
 +
</source>
 +
 
 +
=== 在玩家准星所指处生产闪电 ===
 +
 
 +
如下代码运行手持鱼竿的玩家通过点击来生成闪电 (当然要用准星瞄准)。 真是个简单好玩的恶作剧(。
 +
 
 +
<source lang="java">
 +
@EventHandler
 +
public void onPlayerInteractBlock(PlayerInteractEvent event) {
 +
    Player player = event.getPlayer();
 +
    if (player.getItemInHand().getType() == Material.FISHING_ROD) {
 +
        // 在给定坐标中生成一道闪电. 在本例中, 这个坐标是玩家准星瞄准的地方.
 +
        // 只能指向200格以内的坐标.
 +
        player.getWorld().strikeLightning(player.getTargetBlock(null, 200).getLocation());
 +
    }
 +
}
 +
 
 +
</source>
 +
 
 +
=== 玩家摔不死 ===
 +
 
 +
<source lang="java">
 +
@EventHandler
 +
public void onEntityDamage(EntityDamageEvent event)
 +
    {
 +
        Entity target = event.getEntity();
 +
org.bukkit.event.entity.EntityDamageEvent.DamageCause cause = event.getCause();
 +
            if (target instanceof Player)
 +
                {
 +
                if (cause == org.bukkit.event.entity.EntityDamageEvent.DamageCause.FALL )
 +
{
 +
event.setCancelled(true);
 +
}
 +
                }
 +
</source>
  
=== Spawn Lightning Bolt Where Player is Looking ===
+
=== 非OP玩家不能破坏方块 ===
  
The code below allows any player with a fishing rod to create a lightning strike by clicking (and aiming somewhere). It's a simple and funny trick.
 
 
<source lang="java">
 
<source lang="java">
 
@EventHandler
 
@EventHandler
public void onPlayerInteractBlock(PlayerInteractEvent event) {
+
public void onBreakBlock(BlockBreakEvent e) {     //BlockBreakEvent是参数类型,这里是BukkitAPI的事件,e是参数,用来代表本次事件
    Player player = event.getPlayer();
+
    Player p = e.getPlayer();                     //获取本次事件的实体,entity就是对象名,代表破坏方块事件的实体
    if (player.getItemInHand().getType() == Material.FISHING_ROD) {
+
    if (!p.isOp()) {                             //检测用感叹号,如果玩家不是OP就执行花括号内的代码
        // Creates a bolt of lightning at a given location. In this case, that location is where the player is looking.
+
    e.setCancelled(true);                     //取消这个破坏方块事件
        // Can only create lightning up to 200 blocks away.
+
    }
        player.getWorld().strikeLightning(player.getTargetBlock(null, 200).getLocation());
+
}
    }
+
</source>
}</source>
+
 
 +
=== 自动格式 ===
 +
 
 +
Eclipse提供了将你的代码自动转成Oracle规定的格式, 修正了不符合常规的缩进, 空格等等。 在侧边栏选中你的项目,然后选择''Source -> Format''即可对其进行更改。
 +
 
 +
=== 代码集锦 ===
 +
 
 +
看现成的总好过绞尽脑汁~
 +
 
 +
如果你想在动手做一个实用插件时不想死一堆脑细胞,这里是你的好去处
 +
 
 +
警告: 其中黑科技出没!
  
=== Automatically formatting your code ===
+
点我点我 --->  [[插件代码集中营]]
  
Eclipse provides functionality to automatically format your code to Oracle conventions, fixing unconventional indentations, spacing, and such. Simply select your project in the sidebar, and then select ''Source -> Format''.
+
为伸手党而生~
  
== Request Section ==
+
== 插件需求专区 ==
  
 
http://forums.bukkit.org/forums/plugin-requests.13/
 
http://forums.bukkit.org/forums/plugin-requests.13/
  
== Example Files and Templates ==
+
== 范例文件和样板 ==
  
 
*[https://github.com/Bukkit/SamplePlugin/ Bukkit/SamplePlugin on GitHub]
 
*[https://github.com/Bukkit/SamplePlugin/ Bukkit/SamplePlugin on GitHub]
Line 1,024: Line 1,836:
  
  
[[Category:教程]]
+
[[Category:开发教程]]

Latest revision as of 14:46, 19 March 2020

Icon-info.png
本条目已有一定量的内容,但仍需完善

欢迎参与本条目的完善工作

你可以从以下几个方面入手

  • 参阅格式化手册,并对该页面进行相应格式排版工作;
  • 日常检查是否内容有更新版本并更新该页面;
  • 修复该页面中已出现/潜在的问题
本条目对应原文已经或正在更新。
Icon-info.png
近期请前往这里关注,查看新的版本并更新至本条目。

本页面英文原文内容来源 [1]

Contents

介绍

这篇内容丰富的教程旨在帮助你学会如何开发Bukkit插件,以及部分实例的基本概述.但由于Bukkit涉及Minecraft中的几乎所有元素,所以此教程并未包含Bukkit中所有的内容,你需要发挥自己的想象和随机应变.

学习Java

理解这些教程需要具备Java这门编程语言的基本知识.如果你刚开始学习Java或者需要重温一下相关知识,下面有一个并不完整的网站列表可供参考.当然如果你是初学者并且不那么急于求成的话仍然建议你购买些入门书籍,以便更好的让Java和Bukkit听我们的命令办事.

Oracle's Java Tutorials - 官方教程

https://docs.oracle.com/javase/8/docs/api/ JavaSE的Javadocs

若须完全理解教程,你需要掌握这些内容:

  • Java的基本语法及相关数据处理(例如int,float,String等),包括标识符,基本数据类型,数组等;
  • Java的基本语句,包括if-else,switch,for,while,do-while等;
  • Java的面向对象思想,基类子类接口的概念和他们的相关用法;
  • Java的多文件(包)以及访问修饰符;
  • Java中容器的定义及使用.

开发环境

在编写一个插件前 (或学习Java前) 你需要配置一个开发环境,为了更方便的开发,你需要一个IDE.它可以帮助你更好的管理项目文件和编译调试代码.

Integrated Development Environment,集成开发环境

接下来的教程中有关于Eclipse的操作指南。

更多信息,请参见 建立工作区

必须下载Java开发者使用的Eclipse构建版本, 不是 Java EE开发者的版本。 Java EE 开发者的版本不包含此教程需要的Maven支持。

或者使用更加方便快捷的NetBeans编写插件,当然你也可以使用Idea,但很抱歉此教程暂时不包含Idea的相关教程.

NetBeanIDE-FristSee.jpg

下载地址:

https://netbeans.org/downloads/index.html

下载指定的安装包

NetBeanIDE-download.jpg

当然安装前需要JDK,下载网站:

http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

打开安装程序后点击

下一步 > 我接受许可证协议中的条款 > 下一步
要不要Juit你来定 > 下一步 > 安装

了解BukkitAPI和学看它的Javadocs

结构与Java文档

工欲善其事,必先利其器 我们写的是Bukkit插件,当然要先熟悉他啦.

注意,要区分CraftBukkit和Bukkit,前者是服务端,而后者是一套API接口

众所周知Minecraft的官方服务端是不支持任何插件的,但CraftBukkit不同于官服的地方是他封装了一套独有的接口以及为插件提供了相关的初始化代码,使我们的插件能够被加载,这就需要我们明白Bukkit初始化流程,以及如何使用相关接口,那么问题来了,怎么了解它们呢?

反编译去看那些上万行的代码!?

诶你还别说,如果没有Javadocs的话我们确实需要这样做。

Javadocs 是一种关于程序的帮助文档,帮助开发者更了解程序的构造

所幸的是Bukkit有Javadocs ,在 https://hub.spigotmc.org/javadocs/bukkit/


如果你看不懂英文,那么https://docs.windit.net/Chinese_BukkitAPI/应该可以帮到你


全景图

BukkitAPI-Javadocs-1.jpg

BukkitAPI-Javadocs-2.jpg

BukkitAPI-Javadocs-3.jpg


---

解压一个服务端(Craftbukkit/Spigot)查看

BukkitAPI-see-1.jpg

进入org文件夹 (在Java中这叫)


BukkitAPI-see-2.jpg

看,BukkitAPI就在这里,这是一个Spigotmc服务端,所以也可以看到spigotAPI

Spigotmc是依靠BukkitAPI的,所以spigotAPI分量很轻(自己动手去看就知道了)


BukkitAPI-see-3.jpg

橙色框内的就是包,红框(及以下)是编译好的class文件

和全景图连起来看看,是不是一一对应。

额...那个...说好的class呢?

点击Javadocs的 org.bukkit 超链接

BukkitAPI-Javadocs-4.jpg

就在这里

全景图拍摄的是Overview ,它显示的只是包和它的分支而已

拿 org.bukkit.block 和 org.bukkit.block.bander 举例

block

BukkitAPI-Javadocs-5.jpg

BukkitAPI-see-4.jpg


block.bannder

BukkitAPI-Javadocs-6.jpg

BukkitAPI-see-5.jpg

一般情况下,许多人说的 去看API 实际意思是看BukkitAPI的Javadocs

other

building …

新建插件开发项目

创建一个新的项目

Eclipse

在开始工作之前,你需要先在Eclipse中配置好工作区和文件. 打开Eclipse,然后依次点击File -> New -> Project:来创建一个新的项目.

Newproject.png

现在,打开Maven文件夹, 然后选择Maven Project.点击next,之后在下一个菜单中选择Create a simple project, 再次点击Next: 如果你看不到Maven文件夹, 那么你需要下载m2eclipse

Newproject2.png

现在,你需要给你的组用户命名,就像下面这样:

  • 如果你拥有一个域名,package则填写逆转的域名地址.
    • 例如: i-am-a-bukkit-developer.com 你的package地址即是com.i_am_a_bukkit_developer source
    • 避免使用一个不属于你自己的域名.
  • 没有域名? 这里有几种方法可供选择。
    1. 在资源管理站点创建一个用户,比如GitHub或是sourceforge
      • 对于使用GitHub的用户, 请参照这里的说明 之后你将获得一个子域名,所以你的package地址是io.github.<username>
    2. 使用你的邮箱. 例如: <username>@gmail.com格式的邮箱应输入com.gmail.<username>
    3. 最不济的方法.: 使用独特的组名命名方式,这应当是你最后的解决方法。

以下几个地址不能作为package中的地址前缀:

  • org.bukkit
  • net.bukkit
  • com.bukkit
  • net.minecraft

完成基础的组名以后,你需要在最后加上插件的名称. 在这里用 GitHub页面作为讲解的实例. 如果你创建了一个名为 TestPlugin的插件,那么完整的组名是io.github.<username>, 你的工程名也是 TestPlugin. 至于版本,默认即可,稍后可以修改。 完成向导:

Newproject3.png

如果这是你首次使用Eclipse, 点击右上角的"X" 来关闭Welcome提示页面. 现在,你的窗口视图看起来就像下面的图片这样:

Eclipsemain.png

点击工程名称右边的箭头来进行下一步,现在我们正式开始。

Netbeans

先创建项目

Nb1.jpg

TNb2.jpg

接下来更改主类中的内容

Nb3.jpg

当一个类继承Javaplugin后 ,它就是插件的主类

不允许其他类继承主类。

添加Bukkit API

Eclipse

在开发插件之前,你需要添加 Bukkit API库文件到你的项目,作为一个dependency, 你也可以添加其他你想实用的API.

找到项目文件夹中部的pom.xml并双击进行编辑. 点击pom.xml中部的 tab,你将会看到下图所示内容:

Pomeditor.png

如果你想使用Java 6以上版本的语言特性,你需要指定搭建项目的Java版本.复制并粘贴以下内容(设定项目只能使用Java 7及以下的版本)到 </project>之前:

   <build>
      <plugins>
          <plugin>
              <groupId>org.apache.maven.plugins</groupId>
              <artifactId>maven-compiler-plugin</artifactId>
              <configuration>
                  <source>1.7</source>
                  <target>1.7</target>
              </configuration>
          </plugin>
      </plugins>
   </build>

你或许想使用其他版本,例如 1.8 使用Java 8. 请注意 根据MCStats数据统计, 大多数服主选择了Java 7, 所以使用Java 8会使许多服务器无法运行你的插件. 如果你使用Java 1.7的特性, Eclipse将会在报错代码"error"中建议你更改语言版本.同意即可.

在位于代码中段的</project>之前粘贴以下内容(这段代码告诉Eclipse关于Bukkit's repository的地址):

   <repositories>
       <repository>
           <id>spigot-repo</id>
           <url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
       </repository>
   </repositories>

然后继续在 </project>前粘贴以下内容 (这段代码告诉Eclipse搭建插件的Bukkit版本):

   <dependencies>
       <dependency>
           <groupId>org.bukkit</groupId>
           <artifactId>bukkit</artifactId>
           <version>1.7.2-R0.3</version>
           <type>jar</type>
           <scope>provided</scope>
       </dependency>
   </dependencies>

你也可以修改这里的Bukkit版本.你可以在here这里通过查看 maven-metadata.xml文件下的版本列表来获取可用的服务端版本号.

译者注
官方网站上的仓库地址丢失无法访问,更换为新地址,经测试可以使用。

当你完成上述步骤后,pom.xml中的内容应该是这样:

Finishedpom.png

通过菜单 File -> Save或者按住 Ctrl + S 来保存pom.xml文件 . 之后, 右键项目标题,然后依次选择 Maven -> Update Project.

NetBeans

这个相对简单,先创建库

NetBeansIDE-lib.jpg

NetBeansIDE-lib-setin.jpg

然后添加<库类路径>,一般是服务端核心文件

NetBeansIDE-lib-use.jpg

由此完成设置

Bukkit Java文档

如果你曾经使用过Eclipse和Java,你会知道当你将鼠标准心移至class或者 method中的代码部分时,一个含有相关文档内容的黄色小框会弹出来. 这就是(Java的)Java文档。

你可以Oracle website 在线获取它.。Bukkit同样拥有文档,这些文档内容包含了关于API中method和class的有用描述。

你可以在找到它 (Beta版的Java文档在找, 新开发出来的Java文档在  

) 。为了在Eclipse里使用Java文档,这样的话当你的鼠标停留在Bukkit的class和method上时,

(Bukkit的)Java文档就会冒出来。  

JavaDoc in spigot Click Here

首先右键在侧边栏里的"Maven Dependencies"下面的Bukkit jar,选择"Properties".选中窗口左边的Javadoc Location, 并粘贴如下网址 

http://jd.bukkit.org/ (或者上面的测试版/最新版 Java文档链接也好) 到"Javadoc URL"下面的输入框内. 就像这样: 

Bukkitjavadocs.png

点击 "Validate",然后再点击"OK"就完成了.现在Bukkit Javadocs已经连接到Bukkit提供的资源,同时你也可以通过Eclipse来获得帮助类文档信息了.

创建一个包

现在你需要创建一个包,它将储存所有我们需要使用的Java类文件. 右键展开src/main/java折叠并选择 New > Package: 

Newpackage.png

包名的话,先写上你的组名,然后加个点,再写上你的小写Artifact ID。举例, 如果你的组名是io.github.name,你的artifact名是 TestPlugin。

你的包名就应该是io.github.name.testplugin.

创建插件的类文件

既然我们已经建立好了我们的项目,我们接下来可以开始添加类文件以及制作插件了. 插件的主类指的是拓展(extends)JavaPlugin的类文件. 在你的插件中只能有一个类文件拓展JavaPlugin,无论直接还是间接. 先创建你的主类文件并保持名称与插件名一致是个很好的习惯.右键你之前新建的包,并选择  New > Class.你将会创建一个新的类文件,就像下面这样

package {$GroupName}.{$ArtifactName};

import org.bukkit.plugin.java.JavaPlugin;

public final class {$ArtifactName} extends JavaPlugin {
   
}

警告: 插件绝对不应该调用自己的构造函数并实例化

创建plugin.yml

你已经创建了项目和主类,如果你想要Bukkit能够加载你的插件,你还必须创建 plugin.yml 文件. 这个文件包含有基础的插件信息,如果缺失这个文件,你的插件也将会失效. 此时你需要右键src/main/resources(如果服务器无法识别,请右键你的项目再进行创建). 选择New > File. 命名为"plugin.yml" 并右键完成新文件的创建.Eclipse会打开默认的文本编辑窗口来显示plugin.yml文件中空白的内容并提供编辑. (Hint: 如果你想要使得你的工作空间更加规整,关闭文本编辑器并将plugin.yml 文件拖到主工作空间(拖到文件目录的右边) 之后你就可以在eclipse中编辑文件了.)

有三个基础的内容需要在plugin.yml写明.

name: 插件名称.
main: 插件主类的完整,合法名称 .
version: 插件当前版本号.

最简单的 plugin.yml 文件内就像这样 :

name: {插件名称}
main: {包名}.{主类}   (主类不包含yml后缀,主类就是XXX.Class文件,主类写XXX)
version: {版本号}
PS:插件的包名经常会包括插件的名称,所以看到这个的时候不要感到惊讶。
   你的主类是否是你的插件名取决于你之前的命名方式,时刻记住这一点很重要。

更多例子, 请看#Example_Files_and_Templates

此时你的插件已经可以被Bukkit加载了,同时服务端日志也会开始记录你的插件. 不过它现在什么用都没有!

onEnable()和onDisable()方法

这些方法将在插件启用与卸载时生效. 默认情况下,你的插件在被加载时会调用这些方法,所以你可以在这里注册你需要用到的事件和提供一些调试信息. onEnable() 方法会在插件启用时被调用, 需要包含初始化插件的逻辑语句. onDisable() 方法会在插件卸载时被调用,需要包含清理(clean up)插件的逻辑语句和连接声明(associated state)。此外,插件可以重写 onLoad() 方法来在插件载入时执行附加的逻辑语句。

onEnable()和onDisable()方法介绍

在前面的章节中,我们在主类创建了onEnable()onDisable() 方法. 此时,这些代码看起来就像下面这样

package {$TopLevelDomain}.{$Domain}.{$PluginName};    //包名.类名  例如asd.ddd.XXX  XXX后不用写.Class
import org.bukkit.plugin.java.JavaPlugin;  //导入BukkitAPI的重要库

public final class {$PluginName} extends JavaPlugin {
    @Override  //这里是java的一种注解,用来检测下面onEnable是否被重写
    public void onEnable() {
        // 插件载入时要执行的代码(略)
    }
    
    @Override
    public void onDisable() {
        // 插件卸载时要执行的代码(略)
    }
}

这些方法已经创建,但目前它们还没有任何作用. 注意: 不需要添加代码来实现输出信息"{$PluginName} has been enabled!" ,因为bukkit会自动输出此类信息 有关更多事件的信息请查看 这里.

发送提示信息

插件能够输出信息至控制台与服务器日志。这是通过调用插件记录器里正确的方法来实现的。首先我们必须调用 getLogger() 方法来检索该插件的记录器,

然后我们就可以开始记录信息了。

我们通过调用onEnable()方法可以实现输出信息。 我们需要在onEnable()方法下面插入如下代码:

getLogger().info("onEnable has been invoked!");

onDisable()方法里面,你也可以做类似的事情,不过要改改输出信息。

此时你的主类文件就像这样:

package {$TopLevelDomain}.{$Domain}.{$PluginName};

import org.bukkit.plugin.java.JavaPlugin;

public final class {$PluginName} extends JavaPlugin {
        @Override
        public void onEnable() {
            getLogger().info("onEnable has been invoked!");
        }

        @Override
        public void onDisable() {
        getLogger().info("onDisable has been invoked!");
        }
}

防止重载后插件失效

你需要知道的是,插件并不只会伴随服务器关闭、启动而重载,你的插件也可能会被其他插件启用、关闭,或是在服务器运行时使用/reload命令. 假设服务器启动之后一会儿后,你的插件才被启用,这时便是危险的,因为服务器中可能已经有玩家在线、额外的世界被加载以及很多预想不到的不同之处(与"插件同服务器同时加载"的情况相比较).

举个例子来说:

  • 你有一个用来储存HashMap中的玩家登入信息的插件
  • 你希望该信息对所有玩家都可见
  • 一名管理员使用/reload命令
  • 你的插件被卸载,所有储存的数据都丢失了
  • 你的插件被再次启用时,一些玩家已经在线
  • 这些玩家在HashMap并没有储存的信息
  • 你试图重新获得他们的信息,但是失败了!

为了插件重载以后能够正常工作, 你需要在插件启用时加载所有已经在线的玩家的信息并将之储存在HashMap中.

for (Player player : Bukkit.getServer().getOnlinePlayers()) {
    playerList.put(player.getName(), playerData(player));
}

监听器

监听器是一种方法被调用来对事件作出反应的类.所有的监听器使用 org.bukkit.event.Listener接口.更多有关监听器创建的细节,

请查看: Event API Reference

指令

onCommand() 方法

现在你已经知道如何注册一个事件并做出响应,但是如果你只是想要在命令输入之后做出响应呢? 你可以使用 onCommand方法.

这个代码的作用是当玩家输入“/”时,监听该操作并执行相关语句"/" . 举个例子来说.输入 "/do something" 将会执行 onCommand方法.

此种情况下,因为没有特定的行为被编程,所以并不会发生任何事情。
请避免使用和bukkit所提供的指令重名的指令, 然后深思你的指令名的唯一性。 

例如.指令"give"已经被好几个插件使用了, 如果你执行了另外一个"give"指令, 你的插件将会和这些插件冲突。
你必须在插件的"plugin.yml"注册你的指令 否则这个方法将不会被触发。
onCommand方法必须返回一个布尔值(true或false)。 

如果返回值是true,你不会看到什么明显的事情发生。 

但如果返回值是false,则会返回你的plugin.yml里的'usage:property'然后发送给命令使用者. 

当使用 onCommand方法时, 你需要填写4个参数.

*CommandSender sender - 发送命令的对象

*Command cmd - 被执行的指令

*String commandLabel - 被执行指令的别名

*String[] args - 该指令的自变量数组。

例如.指令 /hello abc def 中, abc 会被存放进args[0]中, def 被存放进args[1]中。

设置命令

@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
    if (cmd.getName().equalsIgnoreCase("basic")) { // 如果玩家输入了/basic则执行如下内容...
    // 所需要执行的事(此处略)
        return true;
    } //如果以上内容成功执行,则返回true 
    // 如果执行失败,则返回false.
    return false;
}
每当使用onCommand方法时, 在每个方法后面都加上一个 return false; 是一个惯例。 

返回false会显示在plugin.yml里设置的usage信息 (看下面). 用这种方法的话,一旦执行出错便会显示usage信息。
每当方法return一个值的时候,这个方法就会退出,所以return true的时候,在它下面的代码就会被跳过, 

除非return语句在一个if的嵌套中,或者类似的嵌套情况。 
.equalsIgnoreCase("basic")代表忽略英文大小写. 在这种情况下,字符串"BAsIc" 和 "BasiC"和 basic相同,代码会被照常执行。

和以前一样,加两行import在你的java文件头

import org.bukkit.command.Command;

import org.bukkit.command.CommandSender;

在plugin.yml中添加你的指令

你需要添加你的指令到 plugin.yml 文件里. 如下是指令/basic添加到 plugin.yml的例子(请在plugin.yml的末尾添加如下代码):

commands:
   basic:
      description: This is a demo command.
      usage: /<command> [player]
      permission: <plugin name>.basic
      permission-message: You don't have <permission>
  • basic - 指令名称
  • description - 指令描述
  • usage - 在oncommand方法return false后显示的用法提示。尽量简洁, 使别人能够理解指令是什么以及如何使用它。
  • permission - 使用该命令所需的权限节点。
  • permission-message - 当玩家使用了这个指令而没有权限时输出的信息。


PS:yml文件使用两个空格作为制表符,使用tab键输入制表符会导致错误。

控制台指令vs玩家指令

你可能注意到了上文的CommandSender sender参数. CommandSender 是个Bukkit接口,

它有两个(对插件编写者)有用的子类:PlayerConsoleCommandSender. 
所以当你编写插件的时候, 确定这个指令从控制台发出后能完全正常工作是十分必要的,

那些只能由在线玩家执行的指令 只能 由一个在线玩家执行。

有的插件在判断命令执 行对象不是玩家时仅仅用return进行处理 (比如当有人从控制台发出指令), 

即便这些指令能够在控制台完美运行(比如改变天气的指令)。

一个判断sender的方法:

@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        if (cmd.getName().equalsIgnoreCase("basic")) { //如果玩家输入了/basic则执行如下内容...
            // 所需要执行的事(此处略)
            return true;
        } else if (cmd.getName().equalsIgnoreCase("basic2")) {
            if (!(sender instanceof Player)) { //如果sender与Player类不匹配
                sender.sendMessage("这个指令只能让玩家使用。");
            } else {
                Player player = (Player) sender;
                   // 所需要执行的事(此处略)
            }
                return true;
        }
        return false;
}
在这个例子里,指令basic能由任何人发出 - 一个在线的玩家, 或者服务器后台的op. 

但是指令basic2只能由一个在线的玩家发出. 
大体上说,你应该允许尽可能多的指令在控制台和玩家聊天框里都正常执行. 

需要在线玩家执行的指令可以使用以上例子中的机制来检测CommandSender是一个玩家. 

很多指令广泛依靠于一些玩家的属性(例如可以被tp或被给予物品等等)。
如果你想更进一步, 你可以对你的指令的自变量做一些额外的检测, 例如传送指令在提供玩家ID的情况下(也只有在这种情况下)可以在控制台被执行。

使用独立的 CommandExecutor class

以上的例子必须把 onCommand() 方法放在插件的主类里。

对于小插件来说,这是极好的,但如果你要写一个大点的插件,把你的 onCommand()方法放在独立的类里将会很有意义。

幸运的是,这并不难: 
  • 在你的包里创建一个新类. 命名为MyPluginCommandExecutor之类的 (当然,要把MyPlugin替换成你制作的插件的名字)。 这个类 必须 实现Bukkit 的CommandExecutor接口。
  • 在你的插件的onEnable()方法中,你需要实例化你创建的命令执行类(CommandExecutor class), 然后做一个像下面一样的调用:getCommand("basic").setExecutor(myExecutor);
其中"basic"是我们需要操控的指令, myExecutor 是我们创建的实例.

实例:

MyPlugin.java (插件的主类):

@Override
public void onEnable() {
        // 如果你没有在plugins.yml注册过指令的话,此处会抛出空指针异常!
        this.getCommand("basic").setExecutor(new MyPluginCommandExecutor(this));
}
MyPluginCommandExecutor.java(独立CommandExecutor类): 
public class MyPluginCommandExecutor implements CommandExecutor {
        private final MyPlugin plugin;

        public MyPluginCommandExecutor(MyPlugin plugin) {
            this.plugin = plugin; // Store the plugin in situations where you need it.
        }

        @Override
        public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
            // 和以前一样执行就好…
        }
}
请注意我们是如何把主类里的对象指向MyPluginCommandExecutor的. 这允许我们轻易的使用主类对象的方法(如果我们需要的话)。
这么做的话, 我们便能更好的组织代码 - 如果 onCommand() 方法又大又复杂, 它就能被划分为子类,从而不使插件的主类显得凌乱。
PS:如果你的插件含有多个指令的话, 你需要为每一个指令单独设立一个commandexecutor.

写一个安全的指令

当写一个指令的时候,别进行任何假设(很重要), 例如假设执行者一定是个玩家. 请牢记以下原则:

在命令执行前确定发出者是玩家

使用简单的例子来进行检测:

@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        if (sender instanceof Player) {
            Player player = (Player) sender;
            // do something
        } else {
            sender.sendMessage("You must be a player!");
            return false;
        }
            // do something
        return false;
}

检测自变量个数

别老是认为玩家都能打对正确数量的自变量. 
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        if (args.length > 4) {
            sender.sendMessage("Too many arguments!");
            return false;
        } 
        if (args.length < 2) {
            sender.sendMessage("Not enough arguments!");
            return false;
        }
}

在获取玩家之前先检测他们是否在线

有时你想通过命令发出者打出的ID来获取其他玩家,请确保他们在线再说!
@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
        Player target = (Bukkit.getServer().getPlayer(args[0]));
        if (target == null) {
            sender.sendMessage(args[0] + " is not online!");
            return false;
        }
        return false;
}
如果你想对离线玩家进行操作,OfflinePlayer类提供了基础操作的方法.

插件的配置/设置

Bukkit API 为插件提供了一种简便的方法,用来管理用户自定义的设置。同时,通过这种方法也可以储存数据。

详情请查看: Configuration API Reference

权限

利用新的Bukkit API来设置权限是再容易不过的事情. 检测玩家是否拥有权限,可以使用以下代码:

if (player.hasPermission("some.pointless.permission")) {
   //执行此段代码(有权限)
} else {
   //执行此段代码(无权限)
}

你也可以检测一个权限节点是否被定义过,(等效于 Java 语言中的null),使用下面的方法:

boolean isPermissionSet(String name)

你或许对此抱有疑惑:为什么这段代码中没有任何用户群的信息?因为它们并不是必需的. 之前这段代码想要实现的主要功能之一就是根据用户群格式化聊天信息. 但其实使用权限就可以很容易地完成这项工作.你可以在聊天插件的配置文件中定义权限与前缀的对应关系. 举例来说,"someChat.prefix.admin" 权限可以对应前缀[Admin].当拥有此权限的玩家发出聊天信息时 ,聊天前缀便是[Admin].

另一个想要实现的主要功能则是给所有拥有权限的在线用户发一条信息.你可以这样做:

for (Player player: Bukkit.getServer().getOnlinePlayers()) {
    if (player.hasPermission("send.receive.message")) {
        player.sendMessage("You were sent a message");
    }
}

看到这里你可能会问,若是没有权限组我该如何配置玩家的 权限 ?既然 Bukkit API 本身并没有提供用户组的预设, 你必须通过安装一款权限组插件,像 permissionsBukkit 这样的插件来帮助你配置用户群. API 只是提供接口而不是提供已实现的类.

配置权限

译者注
其一,以下代码中部分内容将以//文字翻译,使用时请不要加入其中内容,仅做教程中的翻译参考;

其二,default:true在下面翻译为全员默认拥有该权限,default:false则翻译为全员默认没有该权限,请注意理解。

如果你想要更多地控制你的权限,例如设置权限的默认值或是子权限节点,不如将权限节点加入你的 plugin.yml文件.这并不是必须的,但我们推荐你这样做. 下面是一个实例,显示了当你将权限添加在plugin.yml文件末尾时的样子:

permissions:
    doorman.*:
        description: Gives access to all doorman commands //给予使用doorman所有命令的权限
        children:  //子权限节点
            doorman.kick: true
            doorman.ban: true
            doorman.knock: true
            doorman.denied: false
    doorman.kick:
        description: Allows you to kick a user  //允许你踢出一名用户
        default: op  //OP默认拥有该权限
    doorman.ban:
        description: Allows you to ban a user  //允许你封禁一名用户
        default: op //OP默认拥有该权限
    doorman.knock:
        description: Knocks on the door! //敲门
        default: true //全员默认拥有该权限
    doorman.denied:
        description: Prevents this user from entering the door  //禁止拥有该权限节点的用户进入门内

值得注意的是,你的插件使用的每个权限都被定义为 permissions 这个节点的子权限节点. 每个权限都能够拥有一个描述,默认值以及子权限节点。

默认值

当一个权限没有明确是否为玩家所拥有 ,hasPermission[1] 将返回false[2]. 在你的plugin.yml you can change this by setting the default node to be one of four values:

  • true - 全员默认拥有该权限.
  • false - 全员默认没有该权限.
  • op - 如果玩家是一名OP[3],那么他将拥有该权限.
  • not op - 如果玩家不是一名OP,那么他将拥有该权限.

子权限

在这之前你可能已经习惯了通过权限节点自动分配所有的子权限[4]. 现在这种方法已经随着bukkit API[5]的更新而改变,现在你可以自己定义子权限了. 这将使得插件具有更多的灵活性. 下面是一个例子:

permissions:
    doorman.*:
        description: Gives access to all doorman commands  //翻译见上
        children:
            doorman.kick: true
            doorman.ban: true
            doorman.knock: true
            doorman.denied: false

在这里,doorman.* 权限拥有几个子权限并分配在其下. 子权限工作的方式就是当doorman.* 权限节点被设置为true时,子权限也会随之设置为 plugin.yml中的默认值. 然而当doorman.* 被设置为false时,所有子权限都会设置为与默认值相反的值(true->false,false->true).

  1. 这是一个检测玩家是否拥有指定权限节点的方法
  2. 该方法返回的boolean值
  3. Operator,服务器管理员
  4. 原句:Before now you will probably be used to the * permission to automatically assign all sub permissions.此处的sub permissions应是同上文“你的插件使用的每个权限都被定义为 permissions 这个节点的子权限节点.”相照应
  5. 应用程序编程接口(Application Programming Interface)

设置你自己的权限

如果你希望想知道更多有关开发你自己的权限插件的事情(即是设置权限的管理类插件) ,那么请前往查看教程 Developing a permissions plugin.

计划任务与后台任务

目前, Minecraft服务器都使用一个线程进行所有逻辑操作, 所以每个发生在游戏中的逻辑运算必须要很短. 如果处理不好的话, 复杂的代码可能会导致你的服务器逻辑运算有极大的延迟和滞后.

幸运的是, Bukkit正在帮助你安排你的插件源代码. 你可以提交一个在未来能够运行一次的可运行任务, 或者一个基础的循环, 或者你可以拆分出一个新的与游戏逻辑并行的线程, 用来执行过于冗长的任务.

这里 Scheduler Programming 单独写出了计划程序的教程, 并给出了更多你可以用来安排同步任务的信息, 还有启动Bukkit的异步进程.

方块操控

这是一个很容易的方法创建、获取或修改一个方块, 例如当你想要修改往你头上数第五格的方块时, 首先你需要做的是获取你当前的坐标点, 然后往你当前的坐标点的Y轴加上五, 获取目标方块后我们便能够很容易地修改它了, 例子如下:

@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
    // 得到玩家当前的坐标.
    Location loc = event.getPlayer().getLocation();
    // 设置目标坐标为玩家头顶 5 格处.
    loc.setY(loc.getY() + 5);
    // 得到目标坐标的方块
    Block b = loc.getBlock();
    // 将目标坐标的方块更改为石头 (stone).
    b.setType(Material.STONE);
}

上面的代码获取了玩家的所在位置,获取头上的第五个方块,并且放上一个石头(stone)。 Note that once you have a Block, there are other things you can do besides set its type. 你可以查阅JavaDoc获取更多的东西。

你可以用这样相似的观念去生成一些建筑,并且还可以用编程的方式通过算法生成一些独特的方块(这里指由很多个方块拼在一起的四四方方的方块)。比如,去生成一个实心的方块建筑,你可能在生成建筑的时候还需要用到for循环,去一遍遍循环来生成这些建筑,填满这些建筑等。 下面,是一个在指定坐标生成一个实心的方块建筑的实例方法:

public void generateCube(Location loc, int length) {
    // 放置这个方块最中间的方块并且获取这个方块的坐标。
    // Uses getBlockN() instead of getN() to avoid casting to an int later.
    int x1 = loc.getBlockX(); 
    int y1 = loc.getBlockY();
    int z1 = loc.getBlockZ();

    // Figure out the opposite corner of the cube by taking the corner and adding length to all coordinates.
    int x2 = x1 + length;
    int y2 = y1 + length;
    int z2 = z1 + length;

    World world = loc.getWorld();

    // Loop over the cube in the x dimension.
    for (int xPoint = x1; xPoint <= x2; xPoint++) { 
        // Loop over the cube in the y dimension.
        for (int yPoint = y1; yPoint <= y2; yPoint++) {
            // Loop over the cube in the z dimension.
            for (int zPoint = z1; zPoint <= z2; zPoint++) {
                // Get the block that we are currently looping over.
                Block currentBlock = world.getBlockAt(xPoint, yPoint, zPoint);
                // 设置方块ID为57号(钻石块!)
                currentBlock.setType(Material.DIAMOND_BLOCK); //这里下面会提到
            }
        }
    }
}

这个方法将会在程序给予这个方法高度和起始坐标(最中间的方块的坐标)时,构建一个3D的方块建筑。简单的删除方块也可以使用这个方法的思路实现,但是,但是设置方块的时候,记得把上面标注会提到的那行,设置为物品ID为0的方块(空气方块)。

(玩家)修改背包

这个部分大部分讲的是修改玩家的背包。但是在某些情况下,修改箱子里的内容与此相同,当然得在你能找到获取箱子里内容的方法才可以。 :P. 这是一个修改玩家背包的一个例子:

public void onPlayerJoin(PlayerJoinEvent evt) {
    Player player = evt.getPlayer(); // 当玩家加入游戏
    PlayerInventory inventory = player.getInventory(); // 获取玩家背包列表
    ItemStack itemstack = new ItemStack(Material.DIAMOND, 64); // 生成一组钻石
        
    if (inventory.contains(itemstack)) {
        inventory.addItem(itemstack); // 将一组钻石放到玩家的背包里
        player.sendMessage("Wow!你看上去很土豪啊!"); //向玩家发送消息("Wow!你看上去很土豪啊!")
    }
}

所以,上面代码里面的 onPlayerJoin 方法中,我们首先构建了一些变量[1],让我们的工作变得更容易一些。变量分别是: player, inventory and itemstack。 inventory 变量是玩家的背包内容,itemstack是那64个钻石的物品栈。然后我们判断了玩家是不是有一组钻石。如果有,我们就用inventory.addItem(itemstack)方法再给玩家一组钻石,并且给他发送一个消息“Wow!你看上去很土豪啊!”。 所以修改背包并不像我们想象的那么难,如果我们想实现让玩家进入游戏,检测到一组钻石后删掉这组钻石,只需要把代码里的inventory.addItem(itemstack)替换成inventory.remove(itemstack)。或许你还需要把“Wow!你看上去很土豪啊!”改成“Wow!你看上去好穷啊!”2333333。

控制物品

当你需要写一个对物品们做出行为的代码时,你要使用ItemStack类来查阅有关那个栈的所有设置信息。 当然,你也可以干脆搞出一个特别的背包~

物品容器GUI

你可以搞出一个类似于箱子的容器,只不过不是实体化的,它叫Inventory 。

你可以到 https://hub.spigotmc.org/javadocs/bukkit/ 查找org.bukkit.inventory包了解详情

构建方法:

Inventory 变量 = Bukkit.createInventory(InventoryHolder ,int ,String);
Inventory 变量 = Bukkit.createInventory(InventoryHolder,InventoryType ,String);

所有者可以是null

大小有两种,数字(int)或类型(InventoryType),需要注意的是,使用类型时,需要使用getDefaultSize()方法获取默认的大小。

数字是9的倍数,不超过54,出来的是像箱子界面,如果超过54则...呵呵呵呵。

类型是特殊的容器,如工作台界面,具体根据名称而定。

类型列表(图片):

InvType.jpg

后面的String是你箱子的名字(标题)

代码:

    Inventory inv1 = Bukkit.createInventory(null,InventoryType.CHEST , "seesaw_233");
    Inventory inv2 = Bukkit.createInventory(null,54 , "§2 !seesaw!");

inv1是普通小箱子界面,黑字体标题: seesaw_233

inv2是大箱子界面,绿字体标题:  !seesaw!

颜色代码(§)列表(图):

MinecraftColorCode.png

让玩家关闭 容器/背包 的方法:

closeInventory()

让玩家打开指定容器的方法:

openInventory(容器变量)

容器变量 是你指定的Inventory变量

代码范例:

public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args ) 
{
 if(cmd.getName().equalsIgnoreCase("cg") )
 {
     if(args.length ==0)
     {     
     sender.sendMessage("§4 未知命令");
     }
    if(args.length ==1)
    {
        if (sender instanceof Player)
        {
            Player p = (Player)sender;            
            if(args[0].equalsIgnoreCase("open"))
            {
                p.closeInventory();
                p.openInventory(inv1);
            
            }    
        } else{ sender.sendMessage("§4 只有玩家才可使用该命令"); } 
    }
 } 
 return true;
}

物品添加

有了容器,没有物品怎么行? 我们需要new出一个物品~

ItemStack 变量 = new ItemStack(类型 ,数量) ; 

类型同样可以是数字ID(int)或原版类型(Material)

类型太多了,请到 https://hub.spigotmc.org/javadocs/bukkit/

查看org.bukkit.Material类的静态方法 2333333333333333333~

ItemStack myitem = new ItemStack(Material.DIAMOND_PICKAXE ,1) ;

lol~

附魔属性

你想给一个物品附魔,你首先需要了解Item CodeEffect ID. Enchantments他们自己不能例示(new Enchantment() won't work) 因为他们是抽象的。所以你必须要用一个EnchantmentWrapper。如果你想去附魔物品,还不想在默认的SMP里附魔,可以使用addEnchantment()里面的addUnsafeEnchantment()

int itemCode = 280;  //use the item code you want here
int effectId = 20;  //use the enchantment code you want here
int enchantmentLevel = 100;

ItemStack myItem = new ItemStack(itemCode);  //new item of item code
Enchantment myEnchantment = new EnchantmentWrapper(effectId);  //new enchantment of effect id
myItem.addEnchantment(myEnchantment, enchantmentLevel);  //给物品附魔

物品数据

您可以通过这样做来设置物品的显示名称。

String myDisplayName = "Awesome Sword"; //这里是保存了你想要显示的名字
 
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //你要更改的方块
ItemMeta im = myItem.getItemMeta(); //得到方块的元数据
im.setDisplayName(myDisplayName); //设置它显示的名字
myItem.setItemMeta(im); //保存更改

你也可以设置一个物品的lore。 lore是物品的小注释, 像 "+5 攻击伤害" 在一个石剑上.

List<String> lores = new ArrayList<String>();
lores.add("123");  //第一行lore
lores.add("abc")  //这条lore在123之后
ItemStack myItem = new ItemStack(Material.DIAMOND_SWORD);  //你的物品
ItemMeta im = myItem.getItemMeta(); //get the itemmeta of the item again
im.setLore(lores); //add the lores of course
myItem.setItemMeta(im); //give the item the new itemmeta

更多关于lore的设置和它所依靠的ArrayList参见 ArrayList Reference

容器控制

容器不控制咋行呢,不能让人乱来。

这次要扯到监听器,先放前置代码:

public void onInventoryClick(InventoryClickEvent event)
Inventory inv = Bukkit.createInventory(null,InventoryType.CHEST , "hello");
Player p = (Player)event.getWhoClicked();

我们先要判断操作者是不是玩家(真的有非玩家操作物品的事)

if(event.getWhoClicked() instanceof Player == false) { return;}    // 如果操作对象不是玩家则返回

然后看TA是不是在操作我们指定的容器(用容器标题判定)

if (event.getInventory().getTitle().equalsIgnoreCase("hello") )

我们可以使用 getRawSlot() 来判断TA打开的是什么位置。

if(event.getRawSlot() == 0 )

需要知道的是,数组是从0开始计数的,左上角第一个格子的位置为0

具体如图所示

InvNumber.jpg

如果不想让TA拿走物品,使用:

event.setCancelled(true)

但是这会有延迟,并且如果在拿走的一瞬间关闭界面可能会刷走物品

所以可以先关闭界面再打开容器

p.closeInventory();
p.openInventory(inv);

代码范例:

public void onInventoryClick(InventoryClickEvent event) 
{
  {
  if (event.getWhoClicked() instanceof Player == false) { return;} 
  Player p = (Player)event.getWhoClicked();
  if (event.getInventory().getTitle().equalsIgnoreCase(" §2 !DC! ") ) 
     {
     event.setCancelled(true);
     p.updateInventory();       
         if(event.getRawSlot() == 0 )
         { 
             p.closeInventory();
             p.openInventory(inv);    
         }  
     }
  }
}

版权所有,违者不究~

Maps, and Sets, and Lists 天呐! [2] 

除了Map类和HashMap类,Java中还提供了其它的数据结构类集。 

他们提供这些不同的类,是因为有些时候,使用Map类并不合适。[[Java data structure classes]]在这个条目中,有对Java数据框架类集更深入的讲述。

HashMaps概览以及如何使用此接口  

Icon-info.png
本页面内容存在一些问题,请协助修正。
  • 点击此处开始编辑;
  • 此条目可能违反百科条例,需要整改内容;
  • 此条目内容过于精简或是缺少规范的排版;
  • 此条目模板出现问题(包括但不限于模板显示错误、缺失基本内容模板),需要查看并修正;
  • 如当前页面已经没有需要修正的内容,请删去待修正模板;
  • 如添加本模板一周后仍存在本模板,请前往互助客栈发帖告知相关负责人。

记住永远不要在一个HashMap直接使用Player玩家!

应在HashMap中使用String字符串,使用 "p.getName()" 来添加,移除或是检查列表中是否含有某个玩家.

将玩家当作object储存在HashMap中会造成大量内存泄漏(就是费机器资源).

当做一个插件时,你会得到一个点,只使用单变量事件发生或条件已经满足不了由于多个玩家执行的动作/事件。 

这是我的问题,我有一个旧的插件,Zones,现在被改进,重新命名为Regions 。我得到了这些错误的大部分,因为我没有考虑插件会如何在一个实际的服务器上的行为,在任何给定的时间超过一个。我过去使用一个独立的布尔值变量来检测玩家是否在选区中,显然当每个玩家的值需要被区分开来时这个方法将不能正常工作。所以如果一个玩家在选区内而另一个在选区外,那么这个变量将会一直保持改变,因此也会造成许多错误。

HashMap是这样做的一个好方法。HashMap是一种映射/分配的一个关键值。你可以设置HashMap,关键是玩家的值可以做你想做的事情,但与HashMap的有用的东西是一个key的只能包含一个值,不能有重复的key。比如说,我把“adam”作为key,并赋予它“a”的值。那工作的打算,但后来说,后来我想指定值“B”为重点的“adam”我能会没有错误,但价值”“分配到关键的“adam”在HashMap将覆盖因为与哈希图不包含重复的值。


定义一个 HashMap  

public Map<KeyType, DataType> HashMapName = new HashMap<>(); //Example syntax
// Example Declaration
public Map<String, Boolean> pluginEnabled = new HashMap<>();
public Map<String, Boolean> isGodMode = new HashMap<>();

记住代码因为我们会使用它的教程的其余部分在与哈希图。因此,例如,我们可以创建一个简单的函数,它将切换插件是否已启用或不启用。首先,在你的命令功能中,我解释了你需要创建一个函数,将玩家的名字发送到函数,并相应地调整玩家的状态。

所以在命令的内部,你需要这个,函数名可以是不同的,但为了简单,它是最好的,如果你保持不变。

public boolean onCommand(Commandsender sender, Command cmd, String label, String args[])
Player player = (Player) sender;
togglePluginState(player);

这上面的代码将发送到玩家的值传递给函数的参数togglepluginstate()

但现在我们需要建立我们的togglepluginstate()功能。

public void togglePluginState(Player player) 
{
  // 请注意我们如何使用这个玩家名为这里的 key
  // 不是玩家对象
  String playerName = player.getName(); 

    if (pluginEnabled.containsKey(playerName)) 
    {

        if (pluginEnabled.get(playerName)) 
        {
            pluginEnabled.put(playerName, false);
            player.sendMessage("Plugin disabled");
        } 
        else 
        {
            pluginEnabled.put(playerName, true);
            player.sendMessage("Plugin enabled");
        }
    } 
    else 
    {
        pluginEnabled.put(playerName, true); 
        //如果你想要的插件默认情况下为禁用更改此值为 false
        player.sendMessage("Plugin enabled");

    }

}

现在,这个代码是做个检查,如果HashMap首先包含的关键玩家,所以如果它被放入HashMap,如果那时候我们检查HashMap关键值获得(玩家);如果这是真的然后设定值假送玩家一个消息,如果这个值是false,然后做相反,设定值为true,发送一个消息了。但如果HashMap不包含关键的玩家,那么我们可以认为这是他们的第一次 运行/使用 所以我们改变默认值,增加玩家的HashMap

More Ideas for HashMaps  

一个HashMap(或是任何一种Map在Java)是一个关联系统。它可以快速、高效地查找某种值,赋予独特的key。在任何情况下,这会发生在您的代码,地图可能是您的解决方案。

这里有一些其他的想法非常适合使用map。正如你所看到的,它不必是你存储的数据或你的玩家,但可以是任何类型的数据,需要“翻译”从一个形式到另一个。

Data Value Lookups  
public Map<String, Integer> wool_colors = new HashMap<>();
// Run this on plugin startup (ideally reading from a file instead of copied out row by row):
wool_colors.put("orange", 1);
wool_colors.put("magenta", 2);
wool_colors.put("light blue", 3);
wool_colors.put("black", 15);
// Run this in response to user commands - turn "green" into 13

int datavalue = 0;
public boolean onCommand(Commandsender sender, Command cmd, String label, String args[])
    if (wool_colors.containsKey(argument)) 
    { datavalue = wool_colors.get(argument); } 
    else 
    {
        try 
        { datavalue = Integer.parseInt(argument); }
        catch (Exception e) {}
}

Saving/Loading a HashMap  

一旦你知道如何使用与哈希图,你可能想知道如何保存和加载的HashMap数据。

  • 您不希望管理员手动编辑数据
  • 你需要以二进制格式保存数据
  • 你想避免解析块名称 和/或 其他物体从任意文本上

这是很简单的方法如何保存任何HashMap。你可以替换HashMap ,amp ,String, Integer;任何你想要的HashMap类型。让我们举个例子让HashMap代码保存它:

HashMap<String, Integer> mapToSave = new HashMap<String,Integer>();

public void save(HashMap<String, Integer> map, String path) 
{
    try 
    {
         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
         oos.writeObject(map);
         oos.flush();
        oos.close();
    } 
    catch(Exception e) // Hand a IOexceptions
    { e.printStackTrace(); }
}
// ...
save(mapToSave, getDataFolder() + File.separator + "example.bin");

你可以看到它真的很容易。加载工作非常相似但我们使用对象输入流而不是对象,而是个FileInputStream,代替writeobject() 让我们返回HashMap。

public HashMap<String, Integer> load(String path) 
{
    try 
    {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
        Object result = ois.readObject();
        // 如果你知道这文件中的HashMap ,你可以随意投结果HashMap <字符串,整数>
        return (HashMap<String, Integer>)result;
    } 
    catch(Exception e) 
    { e.printStackTrace(); }
}

// ...

HashMap<Integer, String> loadedMap;

String path = getDataFolder() + File.separator + "example.bin";

File file = new File(path);

if (file.exists()) // 检查是否有文档
{ loadedMap  = load(path); }

你可以使用这个“API”保存/加载与哈希图,数组列表,并执行序列化或外部接口的所有对象。

/** SLAPI = Saving/Loading API

 * API for Saving and Loading Objects.

 * Everyone has permission to include this code in their plugins as they wish :)

 * @author Tomsik68<tomsik68@gmail.com>

 */
public class SLAPI
{
    public static <T extends Object> void save(T obj,String path) throws Exception
    {
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
    oos.writeObject(obj);
    oos.flush();
    oos.close();
    }

    public static <T extends Object> T load(String path) throws Exception
    {
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
    T result = (T)ois.readObject();
    ois.close();
    return result;
    }
}

 

Example implementation of this API: '''I'm skipping some part of code in this source''' 

public class Example extends JavaPlugin 
{
private ArrayList<Object> list = new ArrayList<>();

    @Override
    public void onEnable() 
    {
        try 
        { list = SLAPI.load("example.bin"); } 
        catch(Exception e) // handle the exception
        { e.printStackTrace(); }
    }
 
    @Override
    public void onDisable()	
    {
        try 
        { SLAPI.save(list,"example.bin"); } 
        catch(Exception e) 
        { e.printStackTrace(); }
    }
}

Note #1: 这将未修改的几乎所有知名Java类型如整型,字符串,HashMap。它还将为一些Bukkit类型以及工作。如果你在写你自己的数据对象类,你可能想利用这一技术保存它们的状态,你应该阅读关于Java的序列化或外部接口。可外化和序列化的唯一区别是,自动将所有的,序列化的类的域和试图序列化,而外部的允许你定义的阅读和写作的对象的方法。它很容易添加到您的代码,它会使您的数据持久性非常小的工作需要。没有更多的解析!

Note #2: 该API不支持变化。一旦你改变了类中的东西,你的插件的旧版本保存的数据文件将无法正确加载。 

Tips & Examples

1. 简化你的储蓄结构

让要被保存的数据越简单越好, 例如: 如果你要保存一个Player对象,你可以保存该对象的UUID/Name来代替; 如果你想要保存Localtion对象, 你可以保存它的x,y,z以及World对象UUID. 不要直接保存Bukkit的原始类型

2. 保存版本号以及数据

You should always remember, that you don't know what you'll be saving in the same file tomorrow. Will you ever migrate this file because of newer version of your plugin, bukkit, or minecraft? You don't know!

3. 迁移旧文件

If your plugin finds older version of some file, it should update the file accordingly and change version number.

元数据

Bukkit正在尝试尽可能的让制作插件变得更简单,所以带有一个键值例如Player, Entity, World,甚至是一个block的HashMaps都可以用元数据取缔掉。元数据是一些替代HashMap的方式。他可以允许你去自定义"fields"给Players, Entities, Worlds and Blocks.这些东西都是元数据类的一个成员(详见[2])It works very simply. Everything that is Metadatable holds its own HashMap of Metadata which you have access to. That means, for example, if you're creating an economy plugin, you would need a HashMap of Player and Float or Double. 如果用了元数据,你不必去做! You just attach to player new metadata 值, and that's it!

为何要去使用元数据

  • Metadata is all handled by Bukkit, which makes it a very good alternative to HashMaps.
  • 元数据可以用来跟其他插件分享信息与数据。

为什么不去使用元数据

  • Slightly more difficult to get the value.
  • It is not saved on shutdown (but then again, neither are any Maps that you create).

获取 & 设置元数据

public void setMetadata(Metadatable object, String key, Object value, Plugin plugin) {
  object.setMetadata(key, new FixedMetadataValue(plugin,value));
}

public Object getMetadata(Metadatable object, String key, Plugin plugin) {
  List<MetadataValue> values = object.getMetadata(key);  
  for (MetadataValue value : values) {
     // Plugins are singleton objects, so using == is safe here
     if (value.getOwningPlugin() == plugin) {
        return value.value();
     }
  }
  return null;
}

Note: If you're manipulating with numbers, booleans or strings, use convenient method to get the result. For example, you can use asInt(), asString() or asBoolean() instead of value to find out the value.

Databases

Sometimes flat files aren't enough for what your looking to do, this is where databases come in. The most common database engines available on Linux/Mac/Windows machines typically run on some flavor of SQL (Structured Query Language).

Software offering SQL allow you to create databases with columns and header to identify to contents of each cell. Think of it as a spreadsheet on steroids, where every column you set up in your database can enforce rules to ensure integrity. Apart from being more organised than a simple custom data file, SQL provides faster access and better searching than flat files.

The SQL standard helps applications like Bukkit implement database storage for their data in a consistent way. Unfortunately, there's more than one SQL-ready database engine, and each has minor differences in how to configure and use it. Which one you choose may depend on your particular needs. (Some plugins even offer configurable options to connect to multiple database engines!)

SQLite

Alta189 has written a fantastic SQLite tutorial which I suggest you watch if you're interested in using SQL in your plugins, included with the tutorials is a handy library you can download and import to make using SQL easier. Once you have watched these video tutorials I would suggest you go and learn some SQL syntax, it's very straightforward and shouldn't take you long to pick up. SQL Tutorials @W3Schools and @1Keydata.

SQLite is great for very simple databases, because there's no server concerns to set up. Just make a few calls to create a new database and table. It's easy to back up: just copy the whole database file in one go. SQLite is a little bit weaker at data integrity, flexibility in data types, and it may not be something you would want to trust for huge databases of millions of rows. But for a new plugin in development, it's often easiest and fastest to get the SQL basics squared away with SQLite, even if you "graduate" to a more server-class database engine later.

MySQL

Another popular SQL database engine is called MySQL. It is closer to server-grade than SQLite, where many popular companies or websites depend on it for millions of webpage hits every day. With that security comes a little bit steeper learning-curve, because MySQL has more tunable parameters and capabilities.

The coding for plugins accessing MySQL is mostly the same as tiny SQLite or mega-sized Oracle, with only small differences in syntax here or there. But the administration has room to grow. You may want to set up accounts and privileges inside your MySQL setup. You may want to set up SQL scripts that organize your backups and rollback to previous states.

简易定时器

主条目:Bukkit/插件开发教程/定时器编程

要定时当然是用 BukkitScheduler

要定时就和long脱不了干系

long是一种整数数据类型,数值后面要加 L 如:

 public long a = 233L ;

Java的时间单位是1000L一秒

Bukkit的时间单位是20L一秒

还要扯到Bukkit的线程系统上

Bukkit服务器是使用一条线程运行的,所以在插件里随便搞java线程是非常危险的

说白了,当你在插件里用这条代码时:

Thread.sleep(5*1000L);
 // 你认为是插件“睡”五秒

整个服务器会停止五秒...

所以要用Bukkit提供的东西

这里将会使用BukkitRunnable定时

new BukkitRunnable()
{

    int time = 60;  // 六十秒

    @Override
    public void run() 
    {
        if(time == 0)
        {
           cancel();  // 终止线程
             return;
        }
        // your code ...
    }
                
}.runTaskTimer(this, 0L, 20L);
          // 插件主类  延时  定时

随机器

这玩意经常出现在娱乐性插件中,主要用它来抽某个玩家

Random实现的随机算法是伪随机,也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。

但是相同种子数的Random对象,相同次数生成的随机数字是完全相同的。这点在生成多个随机数字时需要特别注意。

接下来是使用普通jar运行,所以会有大量 static 关键字,请注意

     public static void say(Object the) //输出
    {
        System.out.print(the);
    }

用Random有两种方法

public static Random rd = new Random();
rd.*
(new Random()).*

接下来用第二种来讲

Random在很多地方都能学到,这里只举插件常用的int作为例子

(new Random()).nextInt(整数);

整数就是上限,随机范围从 0 ~ 整数-1

每一个随机数的几率都是相等的,也就是说 0123456789他们的几率都是 10%

但如果要随机范围最低不是0呢

放个丧心病狂的代码:

    public static void ran(int start ,int to)
    {
         say( (new Random()).nextInt(start)+to +" "); 
         say( (new Random()).nextInt(start)+to +" ");
         say( (new Random()).nextInt(start)+to +" ");
         say( (new Random()).nextInt(start)+to +" ");
         say( (new Random()).nextInt(start)+to +" ");
         say( (new Random()).nextInt(start)+to +" ");
         say( (new Random()).nextInt(start)+to +" ");
         say( (new Random()).nextInt(start)+to +" ");
         say( (new Random()).nextInt(start)+to +" ");
         say( (new Random()).nextInt(start)+to +" ");   
    }
public static void main(String[] args) 
{ ran(5,2); }

取值范围是 2 ~ 2+(5-1)

弄一方程式(设 start=x ,to=y )

x ~ x+(y-1)

结果就是(一行一次结果):

6 5 6 6 3 3 6 6 5 6
4 3 2 4 5 2 2 3 6 2
6 3 3 5 4 2 5 2 2 2

那怎么随机玩家呢,这就要扯到ArrayList了

ArrayList可以直接储存Player 所以...

看看HotPotato(烫手山芋)是怎么挑中某个倒霉的玩家的吧

public void giveRandomPotato()
{
	if (getAlivePlayers().size() <= 1)  // 如果只剩一个玩家
		return;
	GamePlayer gp = (GamePlayer)getAlivePlayers().get((new Random()).nextInt(getAlivePlayers().size() - 1));  // 就是这里 
	gp.getPlayer().getInventory().setHelmet(new ItemStack(Material.TNT));  // 呵呵
	gp.getPlayer().getInventory().addItem(new ItemStack[] {
		ItemUtil.potato()  
	});
	potatoPlayer = gp.getPlayer();
	GamePlayer gamePlayer;
	for (Iterator iterator = gamePlayers.iterator(); iterator.hasNext(); MessageUtil.sendTextMessage(gamePlayer.getPlayer(), "newPotato", gp.getPlayer().getName()))
	gamePlayer = (GamePlayer)iterator.next();

	new PotatoTimer(this);  // 另一个定时器(Boom!)
	}
public List getAlivePlayers()
{
	List alive = new ArrayList();
	for (Iterator iterator = gamePlayers.iterator(); iterator.hasNext();)
	{     // 遍历列表中的玩家
		GamePlayer gamePlayer = (GamePlayer)iterator.next();
		if (gamePlayer.isAlive())
			alive.add(gamePlayer);
	}

	return alive;
}

但如果要抽取多个玩家呢?

可用方法太多,时间成本太贵,举个不是例子的例子。

其实是懒 ~
public class Support 
{
    public List allplayer; 
    public List allplayerCopy; // 拷贝
    public List hider;
    public List seeker;
    public Random rd; // 随机器 
    public Support()
    {
        allplayer = new ArrayList()
        {{
            add("player01");
            add("player02");
            add("player03");
            add("player04");
            add("player05");
            add("player06");
            add("player07");
            add("player08");
            add("player09");
            add("player10");
        }};
        allplayerCopy = new ArrayList();
         allplayerCopy.clear();
         allplayerCopy.addAll(allplayer);
        hider = new ArrayList();
        seeker = new ArrayList();
        rd = new Random();
        ran(3);
        System.out.print("hider: "+ hider + "   ");
        System.out.print("seeker: "+ seeker);
    }
    
    public void ran(int amount)
    {
        int ta; // 数量
        Object player ;  // 模拟玩家
        if(allplayerCopy.size() <= 1) 
        { return; }
        for (ta = amount;ta > 0; ta--)
        {
            player = allplayerCopy.get(rd.nextInt(allplayerCopy.size() )); // 抽取一个玩家
            seeker.add(player);
            allplayerCopy.remove(player); 
        }
        hider.clear();
        hider.addAll(allplayerCopy);
        allplayerCopy.clear();
    }
    

}

结果:

hider: [player01, player02, player03, player05, player06, player08, player10] 
seeker: [player09, player07, player04]
hider: [player02, player05, player06, player07, player08, player09, player10]   
seeker: [player04, player03, player01]
hider: [player01, player03, player04, player06, player07, player09, player10]   
seeker: [player08, player02, player05]

不要问我10号模拟玩家(String)怎么回事,他人品好~

如果要搞几率呢?

 if((new Random().nextInt(100)+1) < 50){
//50%的几率
}else{
//另外50%
}

部署你的插件

Eclipse 构建方法

总之你写好的你的插件,如何把它变成一个可安装在服务端的jar文件呢?

首先我们需要建立一个水桶服务器。 在这里 能找到开服相关信息.接下来你需要把插件导出为一个.jar文件,

这样你就能在你的服务器里运行它。 在Eclipse里, 右键工程,点击Run as > Maven install即可:

Maveninstall.png

将来呢, 当你对插件进行了改动后,在做上述操作之前,如果你想删除之前的.jar文件,你可以右键工程,点击 Run as > Maven clean 。 

如果你在编译插件时遇到了问题,  请检查你的JDK是否正确安装, 并浏览建立工作区条目。

如果你在Eclipse控制台看到了和JDK有关的错误,你可能需要手动调整JDK,因为Eclipse的检测可能发生了错误。 

点击Window -> Preferences, 然后是Java -> Installed JREs。选中你最近安装的JDK作为Java运行环境(JRE)并添加, 并取消掉原来那个报错的JDK:

Jrelocation.png

如果你的工程成功被编译,JAR文件会生产在 target 目录下,具体在Eclipse的工作空间里名字为工程名的文件夹中。

这个JAR文件就是一个能正常工作的Bukkit插件了。当然前提是你的plugin.yml里没出差错。

你可以把这个jar文件丢到服务器的plugins文件夹里,重载或重启服务器,就可以测试你的新插件啦。

为了连接上使用你自己的电脑开的服务器 ,在多人游戏的ip里填上你的本地ip即可。

如果出现了错误而你又不能解决的话, 试试这里plugin development forum, 

在这里提问bukkitdev IRC channel,或者重新阅读wiki。

当你做完了一个实用的插件,可以考虑把它发表在这里dev.bukkit(为了Bukkit社区的访问量)。

以上可知,JAR格式是默认压缩生成的(JAR文件基于ZIP文件的格式)。这么说来,在上传插件到Bukkit上时把JAR文件压缩成ZIP只会增加文件体积罢了。

而且呢, 放在JAR中的config文件可以在检测不到插件目录(插件自动生成的文件夹,名字与插件名相同,里面是该插件的配置)下的config文件时自动生成。

所以我们一般不把jar文件转换成其他格式。 

NetBeanIDE 构建方法

NetBeansIDE-build.jpg

然后到项目文件所在地打开dist文件夹就能看到了

导入其它插件

你可能会希望编辑其它那些有源代码的插件. 如果插件在它的目录下有pom.xml(大多数知名插件,例如WorldEdit和Essentials都有),你可以通过点击File -> Import来把它作为一个工程导入, 然后打开Maven项并选择Existing Maven Projects:

Importmaven.png

然后选择pom.xml所在的目录,这个工程就应该出现在你的侧边栏里了。就像平常一样编辑并编译它吧。

小贴士

一 Bukkit API有很多碉堡的功能。以下是实现一些功能的代码片段,并不是完整代码,请注意用import导入Bukkit相关的库

使玩家着火

下面的代码运行一个玩家使另一个玩家着火。通过类似于/ignite Notch这样的指令,这个指令会让 Notch 着火。

 @Override
 public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
     // 使用 equalsIgnoreCase() 来忽视大小写
     if (cmd.getName().equalsIgnoreCase("ignite")) {
         // 确保玩家只输入了一个自变量(要被点着的玩家名).
         if (args.length != 1) {
             // 当return false时,该指令的使用信息会显示出来。
             return false;
         }
 
         // 确保命令发出者是个玩家.
         if (!(sender instanceof Player)) {
             sender.sendMessage("Only players can set other players on fire.");
             sender.sendMessage("This is an arbitrary requirement for demonstration purposes only.");
             return true;
         }
 
         // 获取那个要被点着的玩家. 请注意下标起始是0,不是1.
         Player target = Bukkit.getServer().getPlayer(args[0]);
 
         // 确保这个玩家在线。
         if (target == null) {
             sender.sendMessage(args[0] + " is not currently online.");
             return true;
         }
 
         // 让这个玩家着火1000 ticks (一秒钟大概有20ticks,所以总共是50秒).
         target.setFireTicks(1000);
         return true;
     }
     return false;
 }

干掉玩家

和标题一样.这里是要干掉某个玩家。 

在你的onCommand方法里使用这个:

 @Override
 public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
     if (cmd.getName().equalsIgnoreCase("KillPlayer")) {
         Player target = sender.getServer().getPlayer(args[0]);
          // 确保玩家在线.
         if (target == null) {
             sender.sendMessage(args[0] + " is not currently online.");
             return true;
         }
         target.setHealth(0); 
     }
     return false;
 }

这里有个小扩展,会使用一个爆炸来杀死玩家:

 float explosionPower = 4F; //爆炸等级 - TNT的爆炸等级默认是4F
 Player target = sender.getWorld().getPlayer(args[0]);
 target.getWorld().createExplosion(target.getLocation(), explosionPower);
 target.setHealth(0);

创造一个假爆炸

这段代码会生产一个看起来和听起来与TNT/苦力怕产生的爆炸一样的效果。但是不会对实体和方块产生破坏。 在保留爆炸的艺术的同时又削弱了爆炸的威力,真是太有用了

 @EventHandler
 public void onExplosionPrime(ExplosionPrimeEvent event) {	
     Entity entity = event.getEntity();
 
     // 如果事件和点燃的TNT有关,执行一些代码(又略)
     if (entity instanceof TNTPrimed) {
         entity.getWorld().createExplosion(entity.getLocation(), 0);
     }
 }

使一个玩家对另外一个玩家隐形

通过输入指令,能让指令执行者对特定的玩家隐形。 然而其他人仍然能看到指令执行者。
 @Override
 public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
     if (cmd.getName().equalsIgnoreCase("HideMe") && args.length == 1) {
         if (!(sender instanceof Player)) {
             sender.sendMessage("Only players can use this command!");
             return true;
         }
         // 检测指令执行者是玩家后,我们便可以安全的进行强制转换类操作。
        Player s = (Player) sender;
 
        // 获取命令执行者指定的玩家
        Player target = Bukkit.getServer().getPlayer(args[0]);
        if (target == null) {
            sender.sendMessage("Player " + args[0] + " is not online.");
            return true;
        }
        // 将 s 从 target 的视野中隐藏起来.
        target.hidePlayer(s);
        return true;
    }
    return false;
 }

在玩家准星所指处生产闪电

如下代码运行手持鱼竿的玩家通过点击来生成闪电 (当然要用准星瞄准)。 真是个简单好玩的恶作剧(。

 @EventHandler
 public void onPlayerInteractBlock(PlayerInteractEvent event) {
     Player player = event.getPlayer();
     if (player.getItemInHand().getType() == Material.FISHING_ROD) {
         // 在给定坐标中生成一道闪电. 在本例中, 这个坐标是玩家准星瞄准的地方.
         // 只能指向200格以内的坐标.
         player.getWorld().strikeLightning(player.getTargetBlock(null, 200).getLocation());
     }
 }

玩家摔不死

 @EventHandler
public void onEntityDamage(EntityDamageEvent event)
    {
        Entity target = event.getEntity();
	org.bukkit.event.entity.EntityDamageEvent.DamageCause cause = event.getCause();		
            if (target instanceof Player)
                {
                if (cause == org.bukkit.event.entity.EntityDamageEvent.DamageCause.FALL )
			{
				event.setCancelled(true);
			}
                }

非OP玩家不能破坏方块

@EventHandler
	public void onBreakBlock(BlockBreakEvent e) {      //BlockBreakEvent是参数类型,这里是BukkitAPI的事件,e是参数,用来代表本次事件
	     Player p = e.getPlayer();                     //获取本次事件的实体,entity就是对象名,代表破坏方块事件的实体
	     if (!p.isOp()) {                              //检测用感叹号,如果玩家不是OP就执行花括号内的代码
	    	 e.setCancelled(true);                     //取消这个破坏方块事件
	     }
	 }

自动格式

Eclipse提供了将你的代码自动转成Oracle规定的格式, 修正了不符合常规的缩进, 空格等等。 在侧边栏选中你的项目,然后选择Source -> Format即可对其进行更改。

代码集锦

看现成的总好过绞尽脑汁~

如果你想在动手做一个实用插件时不想死一堆脑细胞,这里是你的好去处

警告: 其中黑科技出没!

点我点我 ---> 插件代码集中营

为伸手党而生~

插件需求专区

http://forums.bukkit.org/forums/plugin-requests.13/

范例文件和样板


If you have any more questions on this matter, don't hesitate to visit the BukkitDev IRC channel and ask!

  1. 或许应该是实例化的对象?不过原文中是variables,故此处直译
  2. 标题中Map,Set,List均为Java类集中的接口,故不翻译