【教程】【汉化】如何制作CraftBukkit插件 - 基础插件教程

插件教程
原帖地址:http://wiki.bukkit.org/Plugin_Tutorial

**注意:虽然本教程内容不多,但是由于译者时间不多效率较低并且有其他教程与本文关联,因此看到仍是英文的篇章请不要奇怪~
**本教程首发位置:http://bbs.epicwork.net/thread-964-1-1.html。新的汉化内容将在那里更新。

楼主 Jessefjxm_why  发布于 2012-11-21 23:15:00 +0800 CST  

目录
· 1 介绍
· 2 学习 Java
o 2.1 Java 视频教材
o 2.2 Java 书面教程
· 3 开发环境
· 4 开始一个插件项目
o 4.1 创建项目
o 4.2 查阅 Bukkit API
o 4.3 Bukkit的Java注释文档
o 4.4 创建一个包
o 4.5 创建插件的类
o 4.6 创建 plugin.yml 文件
· 5 onEnable() 和 onDisable() 方法
o 5.1 onEnable()和onDisable() 的介绍
o 5.2 记录信息
· 6 监 听器
· 7 命令
o 7.1 onCommand() 方法
§ 7.1.1 创建指令
o 7.2 将你的指令添加到 Plugin.yml 中
o 7.3 控制台指令 和 玩家指令
o 7.4 创建单独的 CommandExecutor(命令执行) 类
o 7.5 编写安全的 onCommand 方法
§ 7.5.1 在执行指令前确保发送者为玩家
§ 7.5.2 检查参数数量
§ 7.5.3 确保指定的玩家在线
· 8 插件配置/设置
· 9 权限
o 9.1 配置你的权限
§ 9.1.1 默认权限
§ 9.1.2 子权限
o 9.2 创建属于你自己的权限
· 10 调度任务和后台任务
· 11 编辑方块
· 12 编辑(玩家) 包裹
· 13 编辑物品
o 13.1 附魔
· 14 图, 设置, 列表, 老天!
o 14.1哈希图和使用方法
§ 14.1.1 定义哈希图
§ 14.1.2哈希图的更多创意
§ 14.1.2.1 查阅物品ID
§ 14.1.3 存储/载入哈希图
· 15 元数据
o 15.1 为什么要用元数据
o 15.2 为什么不用元数据
o 15.3 使用与配置元数据
· 16 数据库
o 16.1 SQLite
o 16.2 MySQL
· 17 配置你的插件
· 18 小技巧
o 18.1 点燃玩家
o 18.2 杀死玩家
o 18.3 创造爆炸
o 18.4 让某人从其他人眼中消失
o 18.5 玩家点击触发闪电
· 19 请求来源
· 20 范例与模板

楼主 Jessefjxm_why  发布于 2012-11-21 23:16:00 +0800 CST  

介绍
本教程旨在帮助你开始Bukkit插件开发之路。它没法揭示Bukkit所有的可能玩法,只能讲述基础用法的概要。教材从保证你掌握Java开始,到使用IDE创建工作空间,再到介绍大部分Bukkit插件的必备功能。

学习Java

本教程需要对Java这门高级编程语言有一定的掌握.如果你对它一无所知或了解甚少,那你就得看看下面的内容了。他们会帮你不少忙!(很遗憾都是英文的,可能还得翻墙)
Java视频教程
§ iTechRemix 非常基础的 Bukkit 插件教程,作者:iTechRemix。
§ Thenewboston -有很多精彩的视频。
§JavaVideoTutes.com -各方面内容都有。
Java书面教程
§ Oracle Documentation (目前拥有Java的公司)
§Java2s.com -几乎所有有关Java的内容都在这里
§Java101 -深入的书面教程

开发环境

在编写插件(或学习Java)之前,你需要先建立起一个开发环境,它包括但不仅限于IDE(集成开发环境)。IDE是一个帮助你编译和检测你的插件的软件。Java有三个著名的IDE:Eclipse,Netbeans,和 IntelliJ IDEA。Eclipse在Bukkit开发者中最受欢迎,而IntelliJ 在各行业领域广泛使用。如果你是Java新手,推荐你使用 Eclipse 作为IDE,因为接下来的教程里都以Eclipse 作为工具。
更详细的介绍,请查阅创建你的工作空间

楼主 Jessefjxm_why  发布于 2012-11-21 23:16:00 +0800 CST  


开始一个插件项目

创建项目
在开始之前,你需要在 Eclipse 里建立你的工作空间和文件。运行 Eclipse ,然后点击 File(文件)> New(新建)> Java Project(Java项目)创建一个新项目:

楼主 Jessefjxm_why  发布于 2012-11-21 23:16:00 +0800 CST  

给你的项目取个名字,然后运行这个新的项目,根据屏幕上的介绍进行配置。左边的Package Explorer(包浏览栏)里会出现一个文件夹,左键点击它旁边的小箭头会显示你的项目的内容。


查阅Bukkit API
在编写插件前,你需要向你的项目里以外部JAR的形式添加Bukkit API。你也可以添加其他你可能会用到的API。
最新版已编译的Bukkit API 可在这里找到: Bukkit API – 开发快照

点击左边Package Explorer(包浏览栏)内有着你的项目名称的文件夹并选择 Properties(选项). 在左边的方框内选择Java Build Path(Java生成路径) ,之后里面的内容就会变化。点击 Add External JARs(添加外部JAR) 并载入你下载的 Bukkit API.


楼主 Jessefjxm_why  发布于 2012-11-21 23:18:00 +0800 CST  

Bukkit的Java注释文档
如果你已经有Eclipse和Java的使用经验,你就会知道当鼠标放在类或者方法上面时,一个黄色的包含其说明文档的小盒子就会出现。这就是Javadoc(Java文档注释),在Oracle 官网 上也可以查阅到其资料。Bukkit也有包含了每个方法和类的有用描述的注释文档,你可以在这里查看它。为了让Ecilpse能载入注释信息以便我们能方便的查看Bukkit中类和方法的注释,你需要首先左键点击项目浏览器中"Referenced Libraries(参考库)"内的Bukkit jar,点击"Properties(选项)",选择左边的"Javadoc Location(Java注释文档位置)" ,然后复制http://jd.bukkit.org/apidocs/到"Javadoc URL(Java注释文档地址)"中。效果如下:


楼主 Jessefjxm_why  发布于 2012-11-21 23:19:00 +0800 CST  

点击validate(生效),然后点击OK。这样 Bukkit的Java注释文档就连接上了Bukkit源,在 Eclipse 内就能方便的查看注释了。

创建一个包现在你需要创建一个包来储存所有我们会用到的Java类文件。右键点击src文件夹,选择New(新建)> Package(包):


命名规则如下:
§ 如果你有一个域名,包的名称就是域名反过来。
§ 例如:i-am-a-bukkit-developer.com 那你的包名就会是com.i_am_a_bukkit_developer 源
§ 不要使用你不拥有的域名
§ 没有域名?看看这些:
1. 在github 或 sourceforge 这类存放源文件的网站上注册一个帐号
§ 对github,根据 这里面 的教材注册就能得到一个子域名,这样你的包名就是com.github.<username>
2. 使用你的邮箱,如:<username>@gmail.com –> com.gmail.<username>
3. 这是最不推荐的方法:使用任意独有的包名。
你的包名绝对不能是:
§ org.bukkit
§ net.bukkit
§ com.bukkit
§ net.minecraft
下一步就是加上插件名称。让我们再以github为例:如果你的插件名字叫"TestPlugin",你的完整包名就是 "com.github.<username>.testplugin"。

楼主 Jessefjxm_why  发布于 2012-11-21 23:19:00 +0800 CST  

创建插件的类
创建完项目或,我们就可以添加类并编写插件了。插件的主类(main class)必须要继承 JavaPlugin 类。你的插件里需要有且只能有一个类直接或间接继承 JavaPlugin 类。强烈建议先创建你的主类,并将其命名与插件名称一致。右键点击你之前创建的包,选择 New(新建)> Class(类)。你的类格式应如下:

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


importorg.bukkit.plugin.java.JavaPlugin;

publicfinalclass{$PluginName}extendsJavaPlugin {

}

创建plugin.yml你现在已经创建了你的项目和主文件。为了让bukkit能看到它,我们需要添加plugin.yml文件。它包含了插件的必备信息,离开它插件就无法工作。这次我们要右键点击项目文件夹,选择New(新建)> File(文件),命名为 "plugin.yml"后点击确定。 Eclipse 会在默认文档编辑器中打开一个空白的plugin.yml文件。(提示:如果你想让你的工作空间井井有条,关闭文本编辑器并把plugin.yml拖动到主工作空间右边,就能在eclipse中直接编辑了。)该文件中有三条信息必不可少:插件名称,插件主文件的完全限定名称,和插件的版本。最简单的plugin.yml文件如下图所示:
name:{$PluginName}
main:{$PackageName}.{$MainClass}
version:{$VersionNumber}

注意:插件的包的名字常常会包含插件的名字,因此在第二行末尾看到 <pluginname>.<pluginname> 字样时不必感到惊讶。
注意:你的主类是否要与插件名称一致取决于你之前的命名,注意其区分大小写。

现在你的插件可以被 Bukkit载入了,并且也会被记录下来。但它什么也干不了!

楼主 Jessefjxm_why  发布于 2012-11-21 23:20:00 +0800 CST  

onEnable()和 onDisable() 方法

当插件被启用和禁用时分别会调用这两个方法。默认情况下,插件被载入时会启用自身,这样你就能注册你的事件,并在这输出一些调试信息。 onEnable() 方法会在插件启用时被调用,其中应该含有建立插件的内容。 onDisable() 方法会在插件启用时被调用,其中应该含有清除插件和相关状态的内容。有些插件会重写 onLoad() 方法,以在载入时发挥作用。

onEnable()和onDisable()方法的介绍
在上文中创建的主类内创建 onEnable() 和 onDisable() 方法。形式如下:

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

importorg.bukkit.plugin.java.JavaPlugin;

publicfinalclass{$PluginName}extendsJavaPlugin {

@Override
publicvoidonEnable(){
//在这里添加插件被启用时需要做的事情
}

@Override
publicvoidonDisable() {
//在这里添加插件被禁用时需要做的事情
}
}
现在这两个方法被创建了,但是什么都没干。

记录信息
插件能通过正确调用其记录器方法来实现向控制台和服务器日志发送信息。我们需要调用 getLogger() 方法来触发与插件关联的记录器。然后我们就能开始记录了。
当 onEnable() 方法被调用时就会开始记录。将以下内容加入到 theonEnable() 方法中就能实现我们想干的事情:
getLogger().info("onEnable被调用!");
你还可以对onDisable()做相同的工作。记得确保信息已经改变。
现在你的主类应该看起来是这个样子:

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

importjava.util.logging.Logger;
importorg.bukkit.plugin.java.JavaPlugin;

publicfinalclass{$PluginName}extendsJavaPlugin {


publicvoidonEnable(){
getLogger().info("onEnable被调用!");
}

publicvoidonDisable(){
getLogger().info("onDisable被调用!");
}
}

监听器
监听 器是使用了org.bukkit.event.Listener接口,并且有回应相应事件的方法的类。更多细节,请查阅:事件的API 资料

指令

onCommand()方法
现在你已经能注册事件,并在他们发生的时候做点小动作。但如果你只想让事件被指令激发呢?看看onCommand事件吧。当有人输入指令的时候,就会调用onCommand方法。现在什么事情也不会发生,因为我们没有编写出任何行为。
避免使用与Bukkit自带命令相同名称的指令,同时也要好好想想你的指令是否足够独特。例如,"give" 指令在许多插件中都有出现,如果你想编写另外一个"give" 指令,你的插件就会和其他插件不兼容。你需要在插件的plugin.yml文件里注册你的指令,不然它们无法触发这个方法。


楼主 Jessefjxm_why  发布于 2012-11-21 23:20:00 +0800 CST  

onCommand方法需要有至少一个布尔型返回值——不论具体值。如果返回值为真,就不会有任何明显的效果;而如果返回值为假,插件就会向用户显示出自plugin.yml中的命令使用方法的信息。
使用onCommand方法时需要四个参数:
§CommandSendersender– 命令发送者
§Commandcmd– 具体命令
§StringcommandLabel– 所使用的命令缩写
§String[]args– 一组附加参数,例如:输入 /hello abc def ,则 abc 会被保存到 args[0] 中,def 保存到 args[1] 中

创建指令
publicbooleanonCommand(CommandSender sender,Command cmd, String label, String[] args){
if(cmd.getName().equalsIgnoreCase("basic")){ //如果玩家输入/basic就会发生以下事情...
//做某事
returntrue;
} //如果事件发生就会返回true
//没发生就会返回false
returnfalse;
}

编辑onCommand函数时,在函数末尾返回 false 值是一个好的举措。这样的话如果某一步出错了就能出现帮助信息。当返回一个值的时候函数就会结束,因此之后的语句都不会再执行,除非返回指令被包含在一个 if 之类的复合结构中。
语句.equalsIgnoreCase("basic")的意思是忽略大小写,如将"BAsIc" 和 "BasiC" 都视为basic而执行指令。

向Plugin.yml加入我们的指令
你需要把你的指令添加到plugin.yml文件中。在plugin.yml的末尾处加入以下内容:
commands:
basic:
description:这是一个测试用指令。
usage:/<指令> [玩家]
permission:<插件名称>.basic
permission-message:你没有<permission> 权限。
§basic– 指令名称。
§description– 指令描述。
§usage– 当onCommand方法返回false 时会出现的帮助信息。请描述清楚,以便其他人了解该指令的功能及使用方法。
§permission– 用于一些权限插件,以决定将哪些指令展示给玩家。
§permission-message– 当玩家输入指令却没有权限时就会得到该提示
注意:yml文件中以 2 个空格代替制表符(即/tab),注意区分以避免问题。

控制台指令和玩家指令
你可能已经注意到上文中的CommandSender sender这一参数。CommandSender是Bukkit的一个借口,它有两个有用的(对插件开发者而言)子类:Player和ConsoleCommandSender。
当你在编写插件时,保证能从控制台运行的指令能同时在玩家身上切实工作,以及玩家专用的指令仅能被登陆后的玩家使用,十分重要。有些插件仅简单地判断发送者是不是玩家(用于有人想在控制台上运行指令的情况),即使那些指令在控制台上一样能正常运行(比如改变服务器里的天气)。
一种判断的方法是:


楼主 Jessefjxm_why  发布于 2012-11-21 23:20:00 +0800 CST  

publicbooleanonCommand(CommandSender sender, Command cmd, String label, String[] args) {
if(cmd.getName().equalsIgnoreCase("basic")){ //如果玩家输入/basic就会发生以下事情...
//做某事...
returntrue;
}elseif(cmd.getName().equalsIgnoreCase("basic2")){
if(!(senderinstanceofPlayer)) {
sender.sendMessage("该指令仅能由玩家执行。");
}else{
Playerplayer =(Player) sender;
//做某事
}
returntrue;
}
returnfalse;
}

在这个例子中,basic命令可以被任何人使用——不论是登陆的玩家还是控制台上的管理员。但basic2只能由玩家使用。
通常来说,命令应该都能被两者正常使用。仅能被玩家使用的命令可以使用上文所述检查CommandSender是否为玩家的机制来进行校验。这类指令通常只能对玩家生效,如传送玩家的指令,给予某玩家物品的指令等。
如果你想让它功能更强大,可以加入对命令参数的额外检测,例如:在给定玩家名称的情况下也可以从控制台使用传送指令。

使用单独的CommandExecutor(命令执行)类
上文的范例仅在插件的主类中使用了onCommand()方法。对于小型插件来说问题不大,但如果你编写的是更具拓展性的插件,将onCommand()方法放到独立的类中是个好主意。实现起来并不困难:
§ 在插件的包中插件一个新的类,取个MyPluginCommandExecutor之类的名称(你不会真的把MyPlugin保留下来吧?)。该类必须使用BukkitCommandExecutor接口。
§ 在onEnable()方法中,需要创建一个你的新命令执行类的实例并调用它,如 getCommand("basic").setExecutor(myExecutor);,其中 ”basic” 是我们的指令的名称。 myExecutor 就是我们创建的实例。

空口无凭:

MyPlugin.java(the main plugin class):
@Override
publicvoidonEnable() {
// ...

//如果你没在plugin.yml 中定义指令,就会抛出一个NullPointException的错误!
getCommand("basic").setExecutor(newMyPluginCommandExecutor(this));

// ...
}
MyPluginCommandExecutor.java:
publicclassMyPluginCommandExecutorimplementsCommandExecutor {

privateMyPlugin plugin; //指向你的主类,如果你不需要主类中的方法可以不写

publicMyPluginCommandExecutor(MyPlugin plugin) {
this.plugin = plugin;
}

@Override
publicbooleanonCommand(CommandSendersender, Command cmd, String label, String[] args) {


楼主 Jessefjxm_why  发布于 2012-11-21 23:20:00 +0800 CST  
//和前面一样的内容...
}
}

注意到我们从主插件文件中传递了一个实例到MyPluginCommandExecutor中。这能让我们更方便地访问主类中的方法。
通过以上方法,我们能更好的组织起我们的代码——如果主onCommand()方法太长且复杂,我们就能将其分为多个子方法而不用担心撕裂主类。
注意:如果你的插件里有很多指令,你需要为每个指令编写独立的命令执行方法。

编写安全的onCommand方法
编写 onCommand 方法时,要考虑到方方面面。例如:

在执行指令前确保发送者为玩家
publicbooleanonCommand(CommandSender sender, Command cmd, String label, String[] args){
if(senderinstanceofPlayer) {
Player player = (Player) sender;
//执行事件
}else{
sender.sendMessage("该指令只能由玩家执行!");
returnfalse;
}
//执行事件
returnfalse;
}

检查参数数量
不能总指望指令发送者能正确的使用指令。
publicbooleanonCommand(CommandSender sender, Command cmd, String label, String[] args){
if(args.length > 4) {
sender.sendMessage("参数过多!");
returnfalse;
}
if(args.length < 2) {
sender.sendMessage("参数过少!");
returnfalse;
}
}

确保指定玩家在线
有时候你想取得某玩家输入的另一名玩家的名称。记得确保那名玩家在线!
publicbooleanonCommand(CommandSender sender, Command cmd, String label, String[] args){
Playertarget = (Bukkit.getServer().getPlayer(args[0]));
if(target ==null) {
sender.sendMessage(args[0] + " 未在线!");
returnfalse;
}
returnfalse;
}
如果你需要对离线玩家进行操作,OfflinePlayer类能给你提供基本操作方法。

楼主 Jessefjxm_why  发布于 2012-11-21 23:20:00 +0800 CST  

插件配置/设置

BukkitAPI 为插件提供了一套方便的用户配置文件管理方案,同时其也可作为一种简易的数据储存方式。
请查阅:配置文件 API 资料

权限

自从新版本Bukkit API 提供了对权限的支持,一切变得再简单不过。想确认某玩家是否有指定权限,只需:

if(player.hasPermission("some.pointless.permission")){
//执行事件
}else{
//执行其他事件
}

你也可以用以下函数来判断某权限是否存在 (不存在等价于 Java 中的null) :
booleanisPermissionSet(String name)
你可能对这里没有用户组的设定感到疑惑。答案是:(译者注:Bukkit开发组认为)它们没有存在价值。目前用户组的主要用途之一是添加聊天信息的格式,不过这通过权限系统同样可以做到。在你的聊天插件的配置文件内,你可以定义权限和前缀之间的联系,比如: "someChat.prefix.admin" 权限的效果是前缀: [Admin] 。拥有该权限的玩家在发言时名称前会自动添加上前缀 [Admin] 。
另一个常见用途是给某一用户组的所有玩家发送信息,不过这同样能通过权限系统完成:

for(Playerplayer: getServer().getOnlinePlayers()) {
if(player.hasPermission("send.recieve.message")){
player.sendMessage("你收到了一条信息");
}
}

最后你可能会问,没有用户组系统我怎么设置和管理玩家的权限呢?由于Bukkit API 自己没有提供用户组功能,你需要安装一个权限提供插件——如permissionsBukkit ——来管理你的用户组。注意:该API仅仅提供接口,而没有具体内容。

配置你的权限
如果你想更细致的控制权限,比如设置默认值或者子权限,你需要对 plugin.yml 进行更深入的研究了。可选,但我们强力推荐。下面是一个范例文件:
permissions:
doorman.*:
description:拥有全部doorman 指令的权限
children:
doorman.kick:true
doorman.ban:true
doorman.knock:true
doorman.denied:false
doorman.kick:
description:允许你踢出玩家
default:op
doorman.ban:
description:允许你封禁玩家
default:op
doorman.knock:
description:敲门!
default:true
doorman.denied:
description:不让该玩家进门
现在,你插件中的所有权限都被定义为了权限节点上的子节点。每个权限都可以有自己的描述,默认值,和子权限。

默认值
如果某玩家没有指定权限,hasPermission 就会返回false 值。在plugin.yml 里你可以把默认值设置为以下选项之一:
§true– 默认拥有权限
§false- 默认不拥有权限.
§op– 如果玩家是OP就拥有权限
§not op- 如果玩家不是OP就拥有权限


楼主 Jessefjxm_why  发布于 2012-11-21 23:25:00 +0800 CST  

子权限
你以前可能仅能用 * 来获取所有的权限。不过在Bukkit API 的帮助下,你可以添加和使用灵活性更高的子权限了。范例如下:
permissions:
doorman.*:
description:拥有全部doorman 指令的权限
children:
doorman.kick:true
doorman.ban:true
doorman.knock:true
doorman.denied:false
在这里 doorman.* 拥有多个子权限。当 whendoorman.* 被设置为真时,子权限就会被设置为 plugin.yml 中的默认值。如果howeverdoorman.* 被设置为假,子权限的值就会反转。

设置自己的权限
如果你想开发自己的权限插件(真正能设置权限),那就看看 开发一个权限插件这篇教程吧。

调度任务和后台任务

目前Minecraft的服务端将几乎所有程序集中在单个线程上,因此游戏里的每个独立任务都需要控制在非常短的时间内。插件中的一段未正确配置的复杂代码很可能导致服务端的巨大延迟。
幸运的是,Bukkit支持插件中的调度代码。你可以提交一个可运行任务以在未来运行,或进行循环,或创建一个独立的可执行较长任务的和服务端程序平行的线程。
调度器编程 教程介绍了调度器的概念,也告诉我们如果使用它来调度同步任务并剔除Bukkit中的异步任务。

编辑方块

创建方块的最简单方法就是取得一个现成的方块并编辑它。例如,如果你想编辑你头顶上方五格高处的方块,你需要先取得它。例如,作为对玩家移动事件的响应:

publicvoidonPlayerMove(PlayerMoveEvent evt) {
Locationloc = evt.getPlayer().getLocation();
Worldw = loc.getWorld();
loc.setY(loc.getY() + 5);
Blockb = w.getBlockAt(loc);
b.setTypeId(1);
}

当playerMove() 事件被触发时,玩家头上五格高处的方块就会变成石头。首先我们取得了玩家的位置,然后从位置中得到了世界名称。然后我们把坐标的高度加了5,这样我们就得到了目标的坐标和世界——我们能在其中创建方块变量以将方块放在指定位置。我们通过使用 w.getBlockAt(loc); 提供的坐标和世界来实现。最后,我们把方块放在了指定位置并改变了它的ID或方块数据——如果我们想要的话。方块数据大小为一比特,因此如果你想设置方块数据,你需要将变量类型转换为比特。例如,在这段代码之外加上 b.setData((byte)3); 。
通过使用数学公式,你可以程序化地创建建筑或者独立方块,比如一个立方体:

publicvoidgenerateCube(Locationloc,intlength){ //公共可见方法generateCube(),含有2个参数point和location
Worldworld = loc.getWorld();

intx_start = loc.getBlockX(); //将给定坐标赋值到起始点坐标
inty_start = loc.getBlockY();
intz_start = loc.getBlockZ();
/*注意:使用getBlockX()而不是getX()方法是因为前者返回int型变量,这就避免了类型转换(int)loc.getX() */

intx_length = x_start + length; //设置了每个维度单独的长度...应该会更清晰易懂
inty_length = y_start + length;


楼主 Jessefjxm_why  发布于 2012-11-21 23:25:00 +0800 CST  
intz_length = z_start + length;

for(intx_operate = x_start; x_operate <= x_length;x_operate++){
for(inty_operate = y_start; y_operate <= y_length; y_operate++){
for(intz_operate = z_start; z_operate <= z_length;z_operate++){//三重嵌套循环,想必大家都看得懂吧。原文太罗嗦,懒得翻译了~。~

//取得当前坐标上的方块
BlockblockToChange = world.getBlockAt(x_operate,y_operate,z_operate);
blockToChange.setTypeId(34); //将方块ID设置为34
}
}
}
}

以上方法会用提供的长度和起始点位置创建出一个3D立方体。如果想删除方块,只需把 ID 设置为 0 (空气)。

编辑 (玩家) 包裹

这部分内容主要讲述了对玩家包裹的编辑,不过其对箱子的编辑同样适用,只要你找到了取得箱子包裹的方法 :P 。下面是一个编辑包裹的范例:

publicvoidonPlayerJoin(PlayerJoinEvent evt) {
Playerplayer = evt.getPlayer(); //进入游戏的玩家
PlayerInventory inventory = player.getInventory(); //玩家的包裹
ItemStackitemstack =newItemStack(Material.DIAMOND, 64); //一组钻石

if(inventory.contains(itemstack)) {
inventory.addItem(itemstack); //向玩家包裹里添加一组钻石
player.sendMessage("欢迎!你看起来很很很很很很很很有钱的样子,所以我们决定再给你一组钻石!");
}
}

在onPlayerJoin 方法中,我们先创建了一些变量来减轻我们的工作:player, inventory 和 itemstack。 Inventory 就是玩家的包裹,而 itemstack 就是一整组的钻石。之后我们坚持了玩家的包裹看看是否已经有一组钻石,如果是,我们就通过inventory.addItem(itemstack) 方法再给他一组,并向他发送一条信息。编辑包裹并不困难,如果我们愿意也可以把 inventory.addItem(itemstack) 替换成inventory.remove(itemstack) 并稍微修改下信息来压榨玩家。希望对你有帮助!

编辑物品

在代码中处理物品时,你需要用ItemStack类来查找和设置物品的信息。


附魔


为了给物品附魔,你需要首先了解 物品代码 和 效果ID 。附魔类自身无法被实例化(newEnchantment() 不管用) ,因为他们是抽象的。因此你需要使用EnchantmentWrapper 类。一般情况下你只能给武器装备进行附魔,不给NBT标签的使用使得我们已经能做到给任意物品附魔——不过这超出我们的讨论范畴了。

intitemCode = 280; //你想要附魔的物品的ID
inteffectId = 20; //你想要附魔的属性的ID
intenchantmentLevel = 100;


ItemStack myItem =newItemStack(itemCode); //新的物品类
EnchantmentmyEnchantment =newEnchantmentWrapper(effectId); //新附魔类
myItem.addEnchantment(myEnchantment,enchantmentLevel); //进行附魔

楼主 Jessefjxm_why  发布于 2012-11-21 23:25:00 +0800 CST  

图,设置,列表,老天!


Besides theMap/HashMapclasses, Java offers many other data structures. They offer thesedifferentclasses because there are times when a Map is not the mostappropriate. Here'sa separate page for discussing Java data structureclasses in more detail.


哈希图和使用方法
When making a pluginyouwill get to a point where just using single variables to state an eventhashappened or a condition has been met will be insufficient, due to more thanoneplayer performing that action/event.
This was the problem Ihadwith one of my old plugins, Zones, now improved and re-named to Regions. Iwasgetting most of these errors because I didn't consider how the pluginwouldbehave on an actual server with more than one on at any given time. I wasusinga single boolean variable to check whether players were in the region ornotand obviously this wouldn't work as the values for each individual playerneedto be separate. So if one player was in a region and one was out thevariablewould constantly be changing which could/would/did cause numerouserrors.
A HashMap is anexcellentway of doing this. A HashMap is a way of mapping/assigning a value toa key.You could set up the HashMap so that the key is a player and the valuecould beanything you want, however the useful things with HashMaps is that onekey canonly contain one value and there can be no duplicate keys. So say forexample Iput "adam" as the key and assigned a value of "a"to it.That would work as intended, but then say afterwards I wanted to assignthevalue of "b" to key "adam" I would be able to and would getnoerrors but the value of "a" assigned to key "adam" intheHashMap would be overwritten because HashMaps cannot contain duplicatevalues.


定义哈希图
publicMap<Key, DataType> HashMapName =newHashMap<Key, Datatype>(); //Example syntax

//ExampleDeclaration

publicMap<Player, Boolean> pluginEnabled =newHashMap<Player, Boolean>();
publicMap<Player, Boolean> isGodMode =newHashMap<Player, Boolean>();
Keep that code inmindbecause we will be using it for the rest of the tutorial on HashMaps. So,forexample lets create a simple function which will toggle whether the pluginhasbeen enabled or not. Firstly, inside your on command function which Iexplainedearlier you will need to create a function to send the player name tothefunction and adjust the players state accordingly.
So inside on command you'llneedthis, the function name can be different but for the sake of simplicityit'sbest if you keep it the same.


楼主 Jessefjxm_why  发布于 2012-11-21 23:27:00 +0800 CST  
Player player = (Player)sender;
togglePluginState(player);
This code above willcast thevalue of sender to player and pass that argument to thefunctiontogglePluginState(). But now we need to create ourtogglePluginState()function.
publicvoidtogglePluginState(Player player){

if(pluginEnabled.containsKey(player)){
if(pluginEnabled.get(player)){
pluginEnabled.put(player,false);
player.sendMessage("Plugin disabled");
}else{
pluginEnabled.put(player,true);
player.sendMessage("Plugin enabled");
}
}else{
pluginEnabled.put(player,true); //If you want plugin enabled by defaultchange this valueto false.
player.sendMessage("Plugin enabled");
}

}
Now, what this codeisdoing is checking if the HashMap first contains the key player, so if ithasbeen put into the HashMap, if it is then we check the value of the HashMapkeyby get(player); if this is true then set value to false and send the playeramessage, else if the value is false then do the opposite, set the value totrueand send a message again. But if the HashMap does not contain the keyplayerthen we can assume that this is their first run/use so we change thedefaultvalue and add the player to the HashMap.


哈希图的更多创意
A HashMap (or reallyanykind of Map in Java) is an association. It allows quick and efficient lookupofsome sort ofvalue, given a uniquekey. Anywhere this happensin your code, a Map may be your solution.
Here are a few otherideaswhich are ideally suited to using Maps. As you will see, it doesn't haveto bedata that you store per player, but can be any kind of data that needs tobe"translated" from one form to another.


查阅物品ID
publicMap<String, Integer> wool_colors =newHashMap<String, Integer>();

// Runthis onplugin startup (ideally reading from a file instead of copied out rowby row):
wool_colors.put("orange",1);
wool_colors.put("magenta",2);
wool_colors.put("lightblue", 3);
..
wool_colors.put("black",15);

// Runthis inresponse to user commands - turn "green" into 13
intdatavalue = 0;
if(wool_colors.containsKey(argument)) {
datavalue =wool_colors.get(argument);
}else{


楼主 Jessefjxm_why  发布于 2012-11-21 23:27:00 +0800 CST  
try{ datavalue = Integer.parseInt(argument); }
catch(Exception e) { ; }
}


存储/载入哈希图
Once you know how toworkwith HashMaps, you probably want to know how to save and load the HashMapdata.Saving and loading HashMap data is appropriate if
§ you don'twant anadministrator to edit the data manually
§ you needto save data inbinary format (too complex to organize for YAML)
§ you wantto avoid parsingblock names and/or other objects from freeform text
This is very simple wayhowto save any HashMap. You can replace HashMap<String, Integer> withanytype of HashMap you want. Let's continue using the"pluginEnabled"HashMap defined from the previous tutorial. This codesaves the given HashMapto the file with given path.

publicvoidsave(HashMap<String, Integer> map, String path)
{
try
{
ObjectOutputStream oos =newObjectOutputStream(newFileOutputStream(path));
oos.writeObject(map);
oos.flush();
oos.close();
//Handle I/O exceptions
}
catch(Exception e)
{
e.printStackTrace();
}
}

// ...

save(pluginEnabled,getDataFolder()+ File.separator + "example.bin");

You can see it'sreallyeasy. Loading works very very similar but we use ObjectInputStreaminstead ofObjectOutputStream ,FileInputStream instead ofFileOutputStream,readObject()instead of writeObject() and we return theHashMap.

publicHashMap<String, Integer> load(String path)
{
try
{
ObjectInputStream ois =newObjectInputStream(newFileInputStream(path));
Object result = ois.readObject();
//you can feel free to cast result toHashMap<String, Integer> if youknow there's that HashMap in the file
return(HashMap<String,Integer>)result;
}
catch(Exception e)
{
e.printStackTrace();
}
}

// ...

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


楼主 Jessefjxm_why  发布于 2012-11-21 23:27:00 +0800 CST  

if(file.exists()) // checkif file exists before loading to avoid errors!
pluginEnabled = load(path);

You can usethis"API" for saving/loading HashMaps, ArrayLists, Blocks, Players...andall Objects you know ;) . Please credit Tomsik68(the author of this) ifyouuse this in your plugin/other project.
/** SLAPI =Saving/LoadingAPI
* API for Savingand Loading Objects.
* You can usethis API in your projects, butplease credit the original author of it.
* @authorTomsik68<[email protected]>
*/
publicclassSLAPI
{
publicstatic<TextendsObject>voidsave(T obj,Stringpath)throwsException
{
ObjectOutputStream oos =newObjectOutputStream(newFileOutputStream(path));
oos.writeObject(obj);
oos.flush();
oos.close();
}
publicstatic<TextendsObject> T load(String path)throwsException
{
ObjectInputStream ois =newObjectInputStream(newFileInputStream(path));
Tresult = (T)ois.readObject();
ois.close();
returnresult;
}
}
Example implementationofthis API:I'm skipping somepart of code in this source

publicclassExampleextendsJavaPlugin {
privateArrayList<Object> list =newArrayList<Object>();
publicvoidonEnable()
{
try{
list= SLAPI.load("example.bin");
}catch(Exception e){
//handle the exception
e.printStackTrace();
}
}
publicvoidonDisable()
{
try{
SLAPI.save(list,"example.bin");
}catch(Exception e){
e.printStackTrace();
}
}
}


A minor note aboutthisSLAPI and Java's ObjectOutputStream class. This will work un-modified ifyouare saving almost all well-known Java types like Integer, String, HashMap.Thiswill work un-modified for some Bukkit types as well. If you're writing yourowndata object classes, and you may want to save their state using thistechnique,you should read up on Java's Serializable or Externalizableinterface. The onlydifference between Externalizable and Serializable is, thatSerializableautomatically takes all of class's fields and tries to serializethem, whileExternalizable allows you to define method for reading and writingthe Object.It's easy to add to your code, and it will make your data persistentwith verylittle work on your part. No more parsing!


楼主 Jessefjxm_why  发布于 2012-11-21 23:27:00 +0800 CST  
小技巧
点燃玩家
CraftBukkit API 能干很多有趣的事情。下面就是一些有趣的运用!
如何使用命令将玩家点燃:
public booleanonCommand(CommandSender sender, Command cmd, String label, String[] args){
if(cmd.getName().equalsIgnoreCase("ignite")){
Player s = (Player)sender;
Player target = s.getServer().getPlayer(args[0]); // 取得指令内提供的玩家名称
// 例如,如果输入为 "/ignite notch", 那玩家就是 "notch".
// 注意:参数数组的第一个元素编号为[0], 不是 [1]。因此 arg[0] 能取得玩家名称。
target.setFireTicks(10000);
return true;
}
return false;
}
这样如果某个玩家输入 /ignite Notch并且 "Notch" 刚好在线,Notch身上就会燃起熊熊大火!

楼主 Jessefjxm_why  发布于 2012-11-21 23:35:00 +0800 CST  

楼主:Jessefjxm_why

字数:24364

发表时间:2012-11-22 07:15:00 +0800 CST

更新时间:2016-03-15 11:23:39 +0800 CST

评论数:58条评论

帖子来源:百度贴吧  访问原帖

 

热门帖子

随机列表

大家在看