%36%30%43%48%49%50%31%6f%70%31%35%30%52%30%49%74%6a%34

[MyCraft·我的手艺]申请权限?作品展示?下载存档?赶快注册吧!
Join MyCraft

合作站点账号登陆

QQ登录

只需一步,快速开始

快捷导航
查看: 21442|回复: 13

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

[复制链接]
发表于 2012-11-21 20:05:01 | 显示全部楼层 |阅读模式
插件教程

**注意:虽然本教程内容不多,但是由于译者时间不多效率较低并且有其他教程与本文关联,因此看到仍是英文的篇章请不要奇怪~
目录
· 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 范例与模板
介绍
本教程旨在帮助你开始Bukkit插件开发之路。它没法揭示Bukkit所有的可能玩法,只能讲述基础用法的概要。教材从保证你掌握Java开始,到使用IDE创建工作空间,再到介绍大部分Bukkit插件的必备功能。

学习Java

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

开发环境

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

开始一个插件项目

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

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


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

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


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


点击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"。

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

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

import org.bukkit.plugin.java.JavaPlugin;

public final class {$PluginName} extends JavaPlugin {

}

创建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载入了,并且也会被记录下来。但它什么也干不了!

onEnable()和 onDisable() 方法

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

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

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

import org.bukkit.plugin.java.JavaPlugin;

public final class {$PluginName} extends JavaPlugin {

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

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

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

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

import java.util.logging.Logger;
import org.bukkit.plugin.java.JavaPlugin;

public final class {$PluginName} extends JavaPlugin {

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

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

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

指令

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

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

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

编辑 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的一个借口,它有两个有用的(对插件开发者而言)子类: PlayerConsoleCommandSender
当你在编写插件时,保证能从控制台运行的指令能同时在玩家身上切实工作,以及玩家专用的指令仅能被登陆后的玩家使用,十分重要。有些插件仅简单地判断发送者是不是玩家(用于有人想在控制台上运行指令的情况),即使那些指令在控制台上一样能正常运行(比如改变服务器里的天气)。
一种判断的方法是:

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.sendMessage("该指令仅能由玩家执行。");
           } else {
                  Playerplayer =(Player) sender;
                  //做某事
           }
           return true;
    }
    return false;
}

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

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

空口无凭:

MyPlugin.java (the main plugin class):
@Override
public void onEnable() {
    // ...

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

    // ...
}
MyPluginCommandExecutor.java:
public class MyPluginCommandExecutor implements CommandExecutor {

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

    public MyPluginCommandExecutor(MyPlugin plugin) {
           this.plugin = plugin;
    }

    @Override
    public boolean onCommand(CommandSendersender, Command cmd, String label, String[] args) {
           //和前面一样的内容...
    }
}

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

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

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

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

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

插件配置/设置

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

权限

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

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

你也可以用以下函数来判断某权限是否存在 (不存在等价于 Java 中的 null) :
boolean isPermissionSet(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就拥有权限

子权限
你以前可能仅能用 * 来获取所有的权限。不过在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中的异步任务。

编辑方块

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

public void onPlayerMove(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); 。
通过使用数学公式,你可以程序化地创建建筑或者独立方块,比如一个立方体:

public void generateCube(Locationloc, int length){  //公共可见方法generateCube(),含有2个参数pointlocation
    Worldworld = loc.getWorld();

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

    int x_length = x_start + length;    //设置了每个维度单独的长度...应该会更清晰易懂
    int y_length = y_start + length;
    int z_length = z_start + length;

    for(int x_operate = x_start; x_operate <= x_length;x_operate++){
            for(int y_operate = y_start; y_operate <= y_length; y_operate++){
                    for(int z_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 。下面是一个编辑包裹的范例:

public void onPlayerJoin(PlayerJoinEvent evt) {
   Playerplayer = evt.getPlayer(); //进入游戏的玩家
  PlayerInventory inventory = player.getInventory(); //玩家的包裹
   ItemStackitemstack = new ItemStack(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标签的使用使得我们已经能做到给任意物品附魔——不过这超出我们的讨论范畴了。

int itemCode = 280;  //你想要附魔的物品的ID
int effectId = 20;  //你想要附魔的属性的ID
int enchantmentLevel = 100;

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

图,设置,列表,老天!

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.

定义哈希图
public Map<Key, DataType> HashMapName = new HashMap<Key, Datatype>(); //Example syntax
//ExampleDeclaration
public Map<Player, Boolean> pluginEnabled = new HashMap<Player, Boolean>();
public Map<Player, Boolean> isGodMode = new HashMap<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.
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.
public void togglePluginState(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 of value, given a unique key. 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
public Map<String, Integer> wool_colors = new HashMap<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
int datavalue = 0;
if (wool_colors.containsKey(argument)) {
   datavalue =wool_colors.get(argument);
} else {
    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.
public void save(HashMap<String, Integer> map, String path)
{
    try
    {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(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.
public HashMap<String, Integer> load(String path)
{
    try
    {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(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 = new File(path);
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<tomsik68@gmail.com>
*/
public class SLAPI
{
     public static <T extends Object> void save(T obj,Stringpath) 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));
            Tresult = (T)ois.readObject();
            ois.close();
            return result;
    }
}
Example implementationofthis API: I'm skipping somepart of code in this source
public class Example extends JavaPlugin {
    private ArrayList<Object> list = new ArrayList<Object>();
    public void onEnable()
    {
          try{
            list= SLAPI.load("example.bin");
          }catch(Exception e){
               //handle the exception
               e.printStackTrace();
          }
    }
    public void onDisable()
    {
           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!

元数据

Bukkit is trying tomakeplugin development as easy as possible, so HashMaps with key of typePlayer, Entity,World or even a Block were replaced by Metadata. Metadata issome kind ofalternative to HashMap. It allows you to add custom"fields" toPlayers, Entities, Worlds and Blocks. These things are allmembers ofMetadatable class(check[1])Itworks very simply. Everything what is Metadatable holdsits own HashMap ofMetadata which you have access to. That means, if you're forexample creatingeconomy plugin, you need a HashMap of Player and Float orDouble. With Metadata,you don't have to! You just attach to player newmetadata value, and that's it!

为什么要用元数据
Metadata is all handledbyBukkit, what makes it very good alternative to HashMaps.
Metadata can be usedtoshare info between plugins.

为什么不用元数据
Slightly more difficulttoget the value.
It is not savedonshutdown.

使用与配置元数据
/* Ifyou'rehaving these methods in your plugin's main class (which extendsJavaPlugin), youcan remove parameters plugin from them,
* and in theFixedMetadataValue constructorand getMetadata method, use "this"instead*/
public void setMetadata(Playerplayer, String key, Object value, Plugin plugin){
player.setMetadata(key,new FixedMetadataValue(plugin,value));
}
public Object getMetadata(Playerplayer, String key, Pluginplugin){
  List<MetadataValue>values = player.getMetadata(key);  
  for(MetadataValuevalue: values){
    if(value.getOwningPlugin().getDescription().getName().equals(plugin.getDescription().getName())){
      return value.value();
    }
  }
}
Note: Ifyou'remanipulating with numbers, booleans or strings, use convenient method togetthe result. For example, you can use asInt, asString or asBoolean insteadofvalue to find out the value.

数据库
Sometimes flat filesaren'tenough for what your looking to do, this is where databases come in. Themostcommon database engines available on Linux/Mac/Windows machines typicallyrunon some flavor of SQL (Structured Query Language).
Software offering SQLallowyou to create databases with columns and header to identify to contents ofeachcell. Think of it as a spreadsheet on steroids, where every column you setupin your database can enforce rules to ensure integrity. Apart from beingmoreorganised than a simple custom data file, SQL provides faster access andbettersearching than flat files.
The SQL standardhelpsapplications like Bukkit implement database storage for their data inaconsistent way. Unfortunately, there's more than one SQL-ready database engine,andeach has minor differences in how to configure and use it. Which one youchoosemay depend on your particular needs. (Some plugins even offerconfigurableoptions to connect to multiple database engines!)

SQLite
Alta189 has written a fantasticSQLite tutorial which I suggest you watchif you're interested in using SQL in yourplugins, included with the tutorialsis a handy library you can download and importto make using SQL easier. Onceyou have watched these video tutorials I wouldsuggest you go and learn someSQL syntax, it's very straightforward andshouldn't take you long to pick up.SQL Tutorials @W3Schools and @1Keydata.
SQLite is great forverysimple databases, because there's no server concerns to set up. Just make afewcalls to create a new database and table. It's easy to back up: just copythewhole database file in one go. SQLite is a little bit weaker at dataintegrity,flexibility in data types, and it may not be something you would wantto trustfor huge databases of millions of rows. But for a new plugin in development,it'soften easiest and fastest to get the SQL basics squared away with SQLite,evenif you "graduate" to a more server-class database engine later.

MySQL
Another popular SQLdatabaseengine is called MySQL. It is closer to server-grade than SQLite, wheremanypopular companies or websites depend on it for millions of webpage hitseveryday. With that security comes a little bit steeper learning-curve,becauseMySQL has more tunable parameters and capabilities.
The coding forpluginsaccessing MySQL is mostly the same as tiny SQLite or mega-sized Oracle,withonly small differences in syntax here or there. But the administration hasroomto grow. You may want to set up accounts and privileges inside yourMySQLsetup. You may want to set up SQL scripts that organize your backupsandrollback to previous states.
Deploying yourPlugin
Once you have writtenyourplugin, how do you get it from a collection of source files into a workingjarfile that can be installed on a server? First, set up a CraftBukkit serveronyour local machine. To do this, visit the wiki page on Settingup aserver. Next you have to export your plugin to a .jar so thatyoucan run it on your new server. To do this in Eclipse, click File> Export.In the window that pops up, under "Java", select"JAR file",and click next. You will see a window that lookslike this:


小技巧

点燃玩家
CraftBukkit API 能干很多有趣的事情。下面就是一些有趣的运用!
如何使用命令将玩家点燃:
public boolean onCommand(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身上就会燃起熊熊大火!

杀死玩家
如题所示,在onCommand方法中加入以下内容:
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
    if(cmd.getName().equalsIgnoreCase("KillPlayer")){
      Player target = sender.getServer().getPlayer(args[0]);
       target.setHealth(0);
    }
    return false;
}

下面是对上面程序的一个拓展:将玩家炸死!
float explosionPower = 4F; //This is the explosion power - TNTexplosions are 4F bydefault
Player target =sender.getWorld().getPlayer(args[0]);
target.getWorld().createExplosion(target.getLocation(),explosionPower);
target.setHealth(0);

创造爆炸
这些代码将会产生TNT/爬行者那样的爆炸效果和音效。如果你取消了TNT爆炸事件但又想保留特效,这段小代码就很有用了。
    public void onExplosionPrime(ExplosionPrimeEventevent){
            Entity entity = event.getEntity();
            if (entity instanceof TNTPrimed){
                   TNTPrimedtnt = (TNTPrimed) entity;
                   event.getEntity().getWorld().createExplosion(tnt.getLocation(), 0);
            }
    }

让某人从其他人眼中消失
以下代码将会让命令发送者从指定玩家眼中消失。请注意该命令只能对一个人产生隐藏效果。
public boolean onCommand(CommandSendersender, Command cmd, String label, String[] args){
    if(cmd.getName().equalsIgnoreCase("HideMe")&&args.length == 1){
     Player s = (Player)sender; //取得命令发送玩家
     Player target = sender.getServer().getPlayer(args[0]);  //取得被蒙蔽的对象
     target.hidePlayer(s);
     return true;
    }
    return false;
}
玩家点击触发闪电
如果你拿着鱼竿点击一个方块,闪电就会集中你点击的地方。又简单又有趣:)
@EventHandler
    public void onPlayerInteractBlock(PlayerInteractEventevt){
     if(evt.getPlayer().getItemInHand().getTypeId() ==Material.FISHING_ROD.getId()){
         //maximaldistance between player and thunderis 200 blocks
       evt.getPlayer().getWorld().strikeLightning(evt.getPlayer().getTargetBlock(null,200).getLocation());
     }
    }

请求来源

范例与模板
§    Example.Java

评分

参与人数 1软妹币 +5 收起 理由
Kumonda + 5 太有用了

查看全部评分

发表于 2012-11-21 20:10:19 | 显示全部楼层
吓尿了{:4_118:}
发表于 2012-11-21 21:10:36 | 显示全部楼层
jess大哥谢谢啦,我正愁教材看不懂呢
发表于 2012-11-21 23:19:40 | 显示全部楼层

回帖奖励 +10 软妹币

{:4_129:}我看不懂- -
发表于 2012-11-22 15:31:49 | 显示全部楼层

回帖奖励 +10 软妹币

辛苦了……
发表于 2012-11-22 16:54:19 | 显示全部楼层

回帖奖励 +10 软妹币

Ister 发表于 2012-11-21 23:19
我看不懂- -

不会吧……好简单的说……
发表于 2012-11-22 20:15:38 | 显示全部楼层

回帖奖励 +10 软妹币

{:4_134:}原来鸡丝也是个技术宅,不错(虽然看不懂,但觉得好厉害)
发表于 2012-11-23 09:09:52 | 显示全部楼层

回帖奖励 +10 软妹币

写天书的出门左转
发表于 2013-1-14 10:02:18 | 显示全部楼层

回帖奖励 +10 软妹币

马克~~~~~~~
发表于 2013-1-18 20:07:40 | 显示全部楼层

回帖奖励 +10 软妹币

图片呢- -
您需要登录后才可以回帖 登录 | Join MyCraft

本版积分规则

%36%30%43%48%49%50%31%6f%70%31%35%30%52%30%49%74%6a%34

手机版|小黑屋|MyCraft·我的手艺 ( 苏ICP备14010725号-5 )

GMT+8, 2024-12-29 02:07 , Processed in 0.018746 second(s), 8 queries , Gzip On, MemCached On.

Designed by Paddymama and Operated by Lann, Powered by Discuz!

Since 2012

快速回复 返回顶部 返回列表