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