本帖最后由 754503921 于 2017-8-7 02:07 编辑


Spigot Wiki 翻译


顶部目录字体可点!!



大坑开始于 2017-7-14

目前字数统计
Total 23619 汉字
@754503921
19921 汉字
@Smokey_Days 983 汉字
@1345979462 2715 汉字
我决定提前发,要翻译什么可以回复,我会优先翻译

否则就是从上到下顺序翻译
注:代码由于论坛新特性,不是第一页的代码都很爆炸,不怪我
关于 Spigot


什么是 SpigotMC?

成立于 2012,SpigotMC.org 是 Minecraft 最大的服务器软件项目背后的社区,提供了给包括服务器主在内的所有人一个寻求帮助、互相支持、展示自己的平台。我们提供了网络论坛、聊天室和维基百科,提供了支持和项目构建的服务,并希望你也能加入这个巨大且不断扩张的、超过300000 个成员的大家庭。

我们提供什么软件?
SpigotMC 团队致力于维护和支持很多用于建设一个 Minecraft 服务器的重要软件。我们所有的软件都是完全免费并且开源的,在我们的 Stash 和 GitHub 都可以查看。我们受到了很多志愿的支持,并希望有一天你也能这么做。

Spigot 安装
就如名字一样,Spigot 是我们原始的、也是使用最广泛的软件。这是一个修改过的 Minecraft 服务器,基于 CraftBukkit,提供了更多的性能优化、配置选项和特性,这些都是和已有的插件兼容的,并且包括了原版 Minecraft 的所有游戏机制。
目前一共有 150 个 Spigot 仅有的提升,包括了 BungeeCord 支持、很多如作物生长速率、饥饿、实体跟踪、地图种子、增强的看门狗和性能检测用于捕捉插件问题、更多的重量元素如生物活动和漏斗、重写的区块加载、卸载和保存的服务器值,还包括了一些附加的有用的开发者API。
如果你非常有耐心,你也可以在这里查看完整的列表,但是找到 Spigot 提供了什么的最好方法还是亲自动手尝试!

BungeeCord 安装

BungeeCord 是 完完全全的原始的 SpigotMC 作品,以一个代理的作用允许你一起连接对个不同的 Minecraft 服务器,并使用任意方法在这些服务器之间传送,无论是命令、传送门、背包菜单或者是任何你自己独创的方式。使用 BungeeCord 你就不需要将自己限制在一个服务器,而是连接10个甚至是100个不同的模式、不同的游戏规则的服务器到一起。BungeeCord 给所有顶尖的 Minecraft 服务器注入了验证的拓展性,允许你制作最多数的服务器资源。它也拥有一个完整的插件API,开发者可以听他们来编写插件,如聊天和队伍插这类功能跨越您的整个网络的插件。

CraftBukkit
自从 2014 年 Q3 Bukkit.org 项目停止后,SpigotMC 接手了这项提供支持和更新、并保留兼容性的重要工作。CraftBukkit 是一个修改的 Minecraft 服务器,允许运行 Bukkit 插件。这个项目的主要目标是提供一个和原版尽量相似的服务器环境,保证插件支持。这也是为什么CraftBukkit 仍然留有一些有用的优化,如异步区块加载,也修复了一些严重的原版漏洞和BUG。所有通过 BuildTools 构建的 Spigot jar 也会同时生成一个 CraftBukkit jar 文件,这让你使用  CraftBukkit 而不是 Spigot (虽然我们并不想这样)。

Bukkit

作为 CraftBukkit 的附属,我们仍然维护 Bukkit,这也是插件开发者用于开发服务器插件的API。在过去的几年里,我们添加了许多新的特性和变更,以支持更新的 Minecraft 版本,但是很多老的 Bukkit 文档仍然适合作为开发者的入门教材。更多关于如何使用 SpigotAPI 的信息可以在之后的 Wiki 找到。

总结

现在你对 SpigoMC.org 了解更多了,我们也希望你使用我们的软件,并加入我们的论坛和聊天社区。如果你需要帮助,你甚至可以询问合适的板块,那里会有人乐意帮助。
BuildTools


这是什么?

BuildTools.jar 是我们构建 Bukkit、CraftBukkit、Spigot 和 SpigotAPI 的解决方案。所有的这一切都在您的电脑完成!一些工作是必要的,但是下方的构建向导将指引你所要做的一切。

必要的准备
你必须安装这两个应用程序:Git 和 Java

Windows
下方的是指引你在 Windows 上运行 BuildTools.jar。如果你需要一个一击即到的方式,请查看“Tools”栏
Git - 为了在 Wimdowsz 上运行 BuidTools,你需要安装 Git。在 Windows 这是用 git-scm 分发的,可以在这里下载。在任意你喜欢的地方安装后,这将提供给你 git bash 脚本,用于运行 BuildTools.jar。你只需要点击下一步就可以完成 Git 的安装。
Java - 下载 JavaRE 8 并安装。你只需要不断点击下一步即可完成安装。

Linux
Git 和 Java 都可以使用包管理器的单个命令完成安装。
Debian/Ubuntu
  1. sudo apt-get install git openjdk-7-jre-headless
复制代码
CentOS/RHEL
  1. sudo yum install git java-1.7.0-openjdk-devel
复制代码
Arch
  1. pacman -S jdk8-openjdk git
复制代码
Mac
Git 可以在这里下载: http://sourceforge.net/projects/git-osx-installer/files/
Java 可能需要从 Apple 分发的版本升级,即使升级过也需要连接作为脚本使用。
请按照这里的步骤执行: https://gist.github.com/johan/10590467

运行 BuildTools
下载 BuildTools.jar 于 https://hub.spigotmc.org/jenkins ... get/BuildTools.jar.
持续关注 https://hub.spigotmc.org/jenkins/job/BuildTools 获得最新的更新和BUG修复。
如果你想要使用命令下载,你可以使用 curl -o BuildTools.jar <链接> 或 wget -O BuildTools.jar <链接>,使用上面提供的网址。
BuildTools 目录地址中含有任何空格和特殊符号都可能破坏 BuildTools,推荐删除他们。
如果你使用 Linux,打开你的终端,或是在 Windows 上打开 git bash。
Git bash 可以右键文件夹或桌面点击 Git bash here 打开。
跳转至你的 BuildTools.jar 的目录,或是直接在 BuildTools.jar 文件夹中右键选择 git bash here,就会打开终端。
在终端中运行 BuildTools.jar,不要双击。
在 Linux 运行命令 git config --global --unset core.autocrlf, 接着运行 java -jar BuildTools.jar。
在 Windows 中,在打开的终端界面中运行:
  1. java -jar BuildTools.jar
复制代码
请注意你必须下载 BuildTools #35 以后的版本,老的版本不会生效。
在 Mac 上运行以下命令:
  1. export MAVEN_OPTS="-Xmx2G"
  2. java -Xmx2G -jar BuildTools.jar
复制代码
设置 (所有系统)
--rev 设置可以获得确切的 CraftBukkit / Spigot 版本。
查看下方所有可用的版本
(可选) 在 Windows 上创建一个 .bat 文件并写入以下信息:
  1. @echo off
  2. set startdir=%~dp0
  3. set bashdir="C:\Program Files (x86)\Git\bin\bash.exe"
  4. %bashdir% --login -i -c "java -jar ""%startdir%\BuildTools.jar"""
  5. pause
复制代码
这个脚本可以让你将文件拷贝到任何你想构建新版本的目录下。你只需要将脚本复制到 BuildTools 同一目录下,并确保 basedir 变量正确,这决定于你的操作系统,或是你的 Git 的安装位置。
改变 BuildTools 使用的版本,你需要:

  1. %bashdir% --login -i -c "java -jar ""%startdir%\BuildTools.jar"""
复制代码
改为
  1. %bashdir% --login -i -c "java -jar ""%startdir%\BuildTools.jar"" --rev <版本>
复制代码
这将告诉 BuildTools 你使用的版本,使用 --rev latest 获得最新的版本或是查看版本列表。
等到构建你的 jar 文件,大约几分钟后,你就获得你的自己的新鲜的编译过的 jar 了。
你可以在 BuildTools 文件夹内找到 CraftBukkit 和 Spigot,也可以在各自的文件夹内的 target 文件夹内找到 Spigot-API 和 CraftBukkit-API。
享受你的新服务器吧!
需要开启服务器的帮助?看看
这里(目录:安装)

版本
Spigot 的版本可以在 BuildTools 里指定你想要获得的版本,使用 --rev latest 设置。下面是支持的版本。
最新
  1. java -jar BuildTools.jar --rev latest
复制代码
将会构建最新版本, CraftBukkit 和 Spigot jar 可以不用指定 --rev latest 而自动获得最后的版本。
使用
  1. java -jar BuildTools.jar --rev 版本
复制代码
获得某版本的 Spigot

常见问题

  1. There's an error regarding jacobe.exe or jacobe being missing from BuildData/bin
复制代码
升级 BuildTools.jar

Buildtools 出现错误
  1. java.io.FileNotFoundException: BuildData/mappings/bukkit-1.8-cl.csrg
复制代码
升级至最新的 BuildTools 避免这个问题

  1. Exception in thread "main" org.eclipse.jgit.api.errors.TransportException
复制代码
BuildTools 建立 Git 的安全连接时出现错误,这可能是你的反病毒软件拦截了连接。请将 https://hub.spigotmc.org 添加至你的反病毒软件白名单。

  1. Spigot's applyPatches.sh 出现 "/bin/bash^M: bad interpreter"
  2. Spigot's applyPatches.sh 出现 "line 2: command not found"
复制代码
在 Linux(偶尔在 Windows)上,当你开启了 Git 的 autoclf,将会出现此错误。运行
  1. git config --global --unset core.autocrlf
复制代码
并重新运行 BuildTools。

  1. Spigot's applyPatches.sh 出现 "fatal: sha1 information is lacking or useless"
  2. Spigot's applyPatches.sh 出现 "Patch failed at ..."
复制代码
在 Windows 上如果将 autoclf 设置为停用则会出现此问题,运行
  1. git config --global --replace-all core.autocrlf true
复制代码
并重新运行 BuildTools。
如果是 Linux,运行
  1. git config --global --unset core.autocrlf
复制代码
并重新运行 BuildTools

  1. Failed to create log file: BuildTools.log.txt
  2. Exception in thread "main" org.eclipse.jgit.api.errors.JGitInternalException: Creating directories...
复制代码
保证你的文件夹内有写入权限

  1. [ERROR] ... The import gnu.trove.... cannot be resolved
复制代码
VIPRE 反病毒软件已知拥有此问题,其他的杀毒软件也可能拥有此问题或相似问题。关闭杀毒软件并重新运行 BuildTools。
如果关闭杀毒软件后此问题继续,你可能需要通过删除 .m2 文件夹清空你的本地 Maven 仓库,打开你的用户文件夹(Win + R,打开 %userprofile%)并删除,然后尝试重新运行 BuildTools。
  1. (Windows 10 用户) fatal error in forked process - fork: can't reserve memory for parent stack
复制代码
这是一个已知的问题,当在 Win 10 运行 Git 会出现此问题。卸载 64 位的 Git,重新安装 32 位的Git。
  1. [ERROR] Exception in thread "main" org.eclipse.jgit.api.errors.JGitInternalException: Invalid ref origin/master specified
复制代码
这可能是个随机出现的问题。删除 BuildTools 创建的所有文件夹,重新运行。
  1. (Mac OS X 10.11 El Capitan users) xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun
复制代码
如果你从以前的 OS X 升级而来,那么El Capitan 损坏了 Xcode 的安装。运行 xcode-select --install 然后重新运行 BuildTools。

Git bash Windows 不能选择文字
点击 git bash 左上角 > properties > enable QuickEdit mode > 可以了
左键拖动选择,右键一次复制
单击右键也可以粘贴
我可以在 CI 服务器构建它们吗?
是的,CI 服务器可以用来运行简单的 bash 命令,你可以通过运行以下命令进行构建:
  1. wget https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar -O BuildTools.jar && java -jar BuildTools.jar
复制代码
请记住这些构建只能作为私人使用,不可以公开扩散传播。
  1. java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
复制代码
你的服务器缺少了 Java 证书,或者你的防火墙 / 杀毒软件冻结了网络连接。
安装 ca-certificates-java 或者通过添加 --disable-certificate-check 参数运行 BuildTools。

工具
这些工具都是社区成员制作的,它们不受到 SpigotMC 的任何形式的支持,如果你有疑问,请按照下方的指示操作。
DemonWav 的 BuildToolsGUI
BuildToolsGUI 将 Windows 上的 BuildTools 包装了用户界面,它兼容 Windows 7,8,8.1,10。它将自动处理 BuildTools 的所有依赖项目。你必须安装 .NET 4.5 才能使用,你可以在这里找到它们。
BuildToolsGUI 将会在每次运行时自动检测更新,你也可以在这里下载它们,或是在这里查看源码。

故障排除和支持
如果你仍然有疑问,请在 IRC 上询问或是在这里看看你的问题是否已经出现过。
请注意我们推荐你将编译的 jar 文件移出 BuildTools 文件夹来运行服务器,不这样做可能会将 BuildTools 文件夹搞混。
Mac OS 上的 Spigot 更新器

自动在 Mac OS 创建/更新 Spigot 服务器

Spigot 更新器 (macOS) 可以轻松安装和更新你的 Spigot 服务器。
一旦设置好,你就只需要打开并使用 update.command 就可以自动更新你的 Spigot Minecraft 服务器,使用最新的 BuildTools。使用 start.command 来运行服务器。
查看 GitHub 的 polarstoat/spigot-updater 获得下载和使用说明。

需求
你必须安装 Java(推荐 Java 8)

安装
将这个 repo 复制到新的 /Minecraft Server 文件夹下:
  1. git clone https://github.com/polarstoat/spigot-updater.git 'Minecraft server'
复制代码
运行 update.command。这将花费一些时间,然后将会出现 spigot-X.X.X.jar 文件。
运行 start.command。这将开启服务器。

使用
update.command: 下载最新的 BuildTools.jar,接着构建,并将 spigot-X.X.X.jar 复制到你的服务器文件夹。
start.command: 启动服务器

下载
GitHub repository: polarstoat/spigot-updater
简介文档页面


为 Spigot 添砖加瓦

第一步:IDE
开发任何软件的第一步就是一个你想要使用的开发环境。这是一份使用 IntelliJ IDEA 的指导,我们会使用这个 IDE,你可以在
这里下载。
你可以使用默认的设置,也可以尽情改变外观、安装插件。

Stash 权限 + 贡献
第一步是阅读
给项目添砖加瓦 。确保你阅读了 CLA 部分,这是非常重要的。接下来获得 Stash 权限。

提交 CLA?
请耐心等待,但是如果超过了 24 小时,你可以前往 IRC 告诉一个工作人员。


Fork 一个需要的仓库?
一旦你拥有了 Stash 的权限,你就可以 Fork 一个仓库而不是 clone。他将在你的 Stash 界面创建一个仓库。

前往主仓库,选择想要 Fork 的仓库:


比如我选择了 Bukkit 仓库,Fork 设置可以在左手边的菜单栏的 [...] 里找到。
点击 Fork Repository

接着你就可以在你的页面查看你的新的仓库了!

你现在可以复制你的新的仓库了。


使用 Maven pom.xml 导入一个项目?
Spigot 使用一个叫 Maven 的依赖管理软件,这相当节省时间。你可以不用手动下载所有的依赖库,手动编译打包 Spigot Bukkit CraftBukkit,Maven 可以自动完成这一切。
当你打开 IntelliJ,你可以选择新建项目或是导入项目,作为 Maven 安装,选择导入(Import Project)。

现在你可以选择下方的 pom.xml 了,这将下载所有需要的依赖,并且允许你开发自己的 Spigot.jar,CraftBukkit.jar,Bukkit.jar 文件。

构建 JAR 更加简单了,你不需要添加一个 Artifact,而是:
  • 前往 IntelliJ 的右手边的窗口,选择 Maven Project
  • 当你打开 Spigot 文件夹,你会看到 LifeCycle, Plugins 和 Dependencies.
  • 打开 LifeCycle 并找到 'Package'. 双击将会运行封包工具,并分离出一个 JAR 文件于 /Spigot/Spigot-Server/target/spigot-1.9-R0.1-SNAPSHOT.jar
你可以使用这个 JAR 来测试变更。
注意: 你也可以使用其他的包来运行测试,编译。

测试你的新的 Spigot.jar
选择 Run->Edit Configurations... 运行本地 Minecraft 服务器

选择绿色的+, 输入 JAR, 选择 JAR Application

JAR 的路径是新建的 Spigot.jar 的路径,看起来像:Spigot-Server\out\artifacts\spigot_jar
创建新的文件夹,这将是你的 Spigot 服务器文件夹,会有很多东西。
可选:你可以添加 Build Artifacts 于 Before Launch,点击 Build "spigot.jar" artifacts 的 Activate tool window。

如果你需要 Debug,你可以添加 Remote。
注意:
如果你按下 Run -> Spigot,然后你得到了
  1. no manifest attribute
复制代码
你需要移动 src/main/java/META-INF 至 src/main/resources 。


Bukkit
不像 CraftBukkit,Bukkit 只有版本追踪,并且没有补丁系统。这让我们的工作更加轻松,因为不需要担心没有的文件。

为 Bukkit 做贡献:
像上方的教程,将 Bukkit Fork 到你的 Stash 界面。
复制 Bukkit 到你的本地仓库
  1. git clone https://Kato@hub.spigotmc.org/stash/scm/~kato/bukkit.git
复制代码
打开新复制的项目的 pom.xml
查看一个新创建的分支,命名
  1. git checkout -b tm_world_sleep_event
复制代码
应用你的更改到代码
  1. git add src/
  2. git commit -m "Adds WorldSleepEvent"
  3. git push origin tm_world_sleep_event
复制代码
回到 Spigot 的 Stash 界面
前往 Bukkit Stash
点击 Pull Request
选择你的 Fork 的仓库
在右边选择你的分支(注意不是 master 分支)
在左边选择 spigot repo
右边一样
添加介绍,JIRA 的说明,还有其他需要的东西
将 reviewers 留空
提交你的 Pull Request!


CraftBukkit
CraftBukkit 有补丁系统,这是我们管理版本的方法。
按照上方步骤 Fork CraftBukkit 项目,编辑 [om.xml,以下是生成一个包和 Pull Request 的步骤:
Fork Craft Bukkit
复制新的仓库
下载 BuildTools.jar 并放在另一个文件夹,运行
现在我们需要用一些命令获得全部的代码来获得用于编译构建的源码
确保你在 CraftBukkit 的仓库文件夹中
运行
  1. ./applyPatches /path/to/buildtools/work/decompile-xxxx
复制代码
这将创建合适的 nms 文件和类。确保没有将其添加至版本控制,版本控制的代码应在 nms-patches/ 目录。
你可以编辑 CraftBukkit 代码了。
当你完成后,你需要运行以下命令创建新的 patch
确保你在 CraftBukkit 的仓库文件夹中
运行
  1. ./makePatches /path/to/buildtools/work/decompile-xxxx
  2. git add nms-patches
  3. git commit -m "Adds implementation of x"
  4. git push origin your_branch_name
复制代码
当你的更新全部上传后,你就可以创建新的 Pull Request 了

使用 BuildTools!
你可以在
这里下载 BuildTools
将文件移动到自己的文件夹,并使用命令。你必须安装了 Java。


为项目添砖加瓦

Developers
Whilst the scripts are nice and easy to use, as a developer you may want a better insight into how they operate so that you can make your own changes, or do it by hand in case they don’t work as intended. Roughly put, the following steps are performed by the install scripts:
Clone the Bukkit, CraftBukkit and Spigot repositories from Atlassian Stash.
Download the required build depends: Maven, SpecialSource, Fernflower, Jacobe, Minecraft Server Jar, etc.
Apply the included deobfuscation mappings and access transformations to the Minecraft Server Jar.
Decompile and format the decompiled code using fernflower and Jacobe.
Apply the included patches to the CraftBukkit source.
Compile the whole thing using Maven!
If installing Spigot, run ./applyPatches and Maven as per usual.
If you require help making changes to Spigot or CraftBukkit, please stop by our development IRC channel on irc.spi.gt, #spigot-dev. In particular to regenerate the NMS patches, you need to use the ./makePatches.sh script in the CraftBukkit source. The argument to this script should be the clean decompiled directory which BuildTools generated, ie: ../work/decompile-bb26c12b

We are also very eager for enhancements and improvements to the build scripts, in particular there is not much support for custom forks, although once the deobfuscated jar has been installed the server can be compiled with Maven as per usual.

Stash Access
As a developer you are probably also interested in getting access to the Stash server so that you can make pull requests with all your awesome new features and enhancements.

In order to get this access you will first need to create an account on JIRA, and then submit our CLA.
Why a big scary CLA you might ask? Well first of all its not scary at all, it is substantially shorter and easier to read than many of the agreements you submit on a daily basis (think Apple, iTunes, and all those other terms and conditions). The reason for this document is to protect you, and to protect us. What the CLA does, is instead of licensing your code under [L]GPL, BSD, or whatever other licenses our projects may use, it licenses your code to us instead. This is a good thing, and in implementing it, we hope you will see the benefits behind our key reasons:
It is easy to understand: Previously when contributing to a LGPL license project you had to agree to both the GPL and LGPL licenses, which when combined represent nearly 6500 words of legal speak so complex that it requires in depth study by even experienced legal professionals. Our CLA on the other hand clocks in at around 1800 words, or four times less, and is written in language which we hope is easy to understand even for those whom English is not their first language.
It protects you as a developer: The CLA makes it completely clear, in writing, what rights you give us, and what rights you don’t. In particular, the right we are most concerned about is the right to use, and relicense your code. It also makes it abundantly clear that you give irrevocable consent to your contributions being used in Spigot / Bukkit.
It protects us as a project: By having a signed document stating that you give us the ability to license and use your code, it 1) prevents the idea that past contributions can be rescinded in any way, shape or form, and 2) enables us to relicense your code to better serve and preserve the long term goals of the project should that need arise in the future.
It eliminates ambiguities and hopefully encourages contributions from those who would not normally contribute. One of the biggest reasons (excuses) I have heard from developers not contributing to Spigot is “I don’t think I’m allowed to because of my NDA”. This document is hopefully the solution to all of those troubles, and by getting it cosigned by your employer there should be no issues and you can contribute to your heart's content! We can’t expect to advance further if the best developers don’t contribute as they are tied up elsewhere.
We provide Spigot, and now CraftBukkit as free and open source software which is free of charge. Almost all of you make a profit off your servers, and some of you make hundreds of thousands a year; we don’t. That’s ok, because we’re not focused on that. What we feel isn’t ok however is not contributing changes you make to our software back to us, especially changes which fix exploits and other critical bugs. It may seem far fetched, but this is the exact situation our team has had to deal with more than once in the last year.

So this is our plea to you larger server owners, if you have a developer, or a team of developers working on modified versions of our software, please do so in a public manner which we can work with. Doing so will only lead to a better software, with more API features and less bugs. We are able to provide any assistance you require in doing this, including providing dedicated organisations on our Stash instance. In particular I would like to draw upon the example of SportBukkit, which has been maintained as an open source project by the @@MonsieurApple and oc.tc servers for many year(s) now - in fact several Spigot API features are based on their work, kudos to them!

Finally with regards to the CLA, if you are interested in any of the included content you should note that it has largely been based on the well known Harmony CLA, with a few minor alterations made by our lawyers. If you have any specific concerns about any of the wording within, please don’t hesitate to contact us and we will do our best to help.

Please note that the "real name" field on JIRA is public to all users. If you are not comfortable with this please set it to your username. All details in the CLA however must be correct.
Submit the CLA here: https://www.spigotmc.org/go/cla

创建你的开发工作空间

新的系统是怎么工作的
由于法律原因,SpigotMC 不能直接提供可下载的 jar 文件,我们必须使用一个叫做 BuildTools 的程序来生成 JAR。
BuildTools 下载所有相关的工具如 Maven(项目管理和构建),FermFlower(反编译)


BuildTools 的工作原理
BuildTools 首先检查是否是在 Mac 上运行,是否是在 bash 中运行,接着检查是否设置了 Git 用户名和密码。
现在真正的工作开始了:
  • 保证“work”文件夹出现,如果没有就创建一个。
  • 检查 Bukkit 文件夹是否出现,如果没有就将 Bukkit 仓库复制进去。
  • 检查 CraftBukkit 文件夹是否出现,如果没有就将 CraftBukkit 仓库复制进去。
  • 检查 Spigot 件夹是否出现,如果没有就将 Spigot 仓库复制进去。
  • 检查 BuildData 件夹是否出现,如果没有就将 BuildData 仓库复制进去。这个文件夹包含了重要的信息包括原版 jar 的映射。
记住这只是个空文件夹的初始化工作,我们将在下一分钟更新它们
  1. git clone https://hub.spigotmc.org/stash/scm/spigot/bukkit.git Bukkit
  2. git clone https://hub.spigotmc.org/stash/scm/spigot/craftbukkit.git CraftBukkit
  3. git clone https://hub.spigotmc.org/stash/scm/spigot/spigot.git Spigot
  4. git clone https://hub.spigotmc.org/stash/scm/spigot/builddata.git BuildData
复制代码
现在将会以系统为 Windows 或是 Linux 下载 Jacobe。
接着将会解压 jacobe 到主文件夹。

在 Linux 上你可以这样下载 Jacobe(在 Windows 你可以使用图形界面)
  1. wget -O jacobe.linux.tar.gz http://www.tiobe.com/content/products/jacobe/jacobe.linux.tar.gz
  2. tar xzvf jacobe.linux.tar.gz -C jacobe
复制代码
接下来将会确认 Maven 是否安装完成。

常见问题

Spigot/Bukkit

什么是 Spigot?
Spigot 既是一个修改版 Minecraft 服务器也是一个 API。Spigot 基于 Bukkit,添加了很多的改良的设置,都可以在 spigot.yml 进行设置。这个 API 允许开发者给原版服务器添加新的特性。

Spigot 需要花费什么?
Spigot 是完全免费并且开源的软件,但是请考虑给 Spigot 捐赠,这将帮助维持网站部署和软件开发,就在这里:ttps://www.spigotmc.org/#donate 非常感谢!
Bukkit 怎么了?
之前有一场 DMCA 官司把 Bukkit 打垮了,现在 Spigot 正在维护 Bukkit。

我从哪里得到 Spigot 或者 Bukkit 的 jar 文件?

由于 DMCA,你必须自己编译服务器。幸运的是这很简单,查看 BuildTools 章节获得详细信息。

我需要服务器的帮助!
如果发生了错误,并且你又不知道怎样做,请前往论坛询问,那里有上千的成员帮助你。

BungeeCord

什么是 BungeeCord?
BungeeCord 允许一个服务器连接多个子服务器,这意味着玩家不需要通过退出登录重新进入新的服务器。
这就像一个代理服务器,一些著名的服务器就使用 BungeeCord,如 MinePlex,Hypixel 和其他的一些多服务器群组。

我可以在哪里下载到 BungeeCord?
BungeeCord 可以在公共构建服务器下载,这里是链接:
http://ci.md-5.net/job/BungeeCord/

我应该怎样使用 BungeeCord?
查看 BungeeCord Wiki,那里可以帮助你!
https://www.spigotmc.org/wiki/bungeecord/

插件

我可以在哪里下载插件?
大多数 Bukkit 插件都在 http://dev.bukkit.org/bukkit-plugins/, Spigot 有一个插件资源栏 ttps://www.spigotmc.org/resources/ 里面有几千个插件!有的是免费的,有的是付费的。

我自己制作了插件,如何上传?
你可以在这里上传你的资源: https://www.spigotmc.org/resources/add

为什么我不能让我的插件付费使用?
请详细阅读这篇文章: https://www.spigotmc.org/threads ... e-guidelines.31667/

如果我找到了一个恶意插件,我应该怎么办?
很不幸人们有时会上传恶意插件,如果你确实找到了一个,或者怀有疑虑,请给 resources@spigotmc.org 或是使用插件页面的 report 按钮。

如果有人出售给我一份坏插件,我应该怎么办?
如果插件没有按照预期工作,请联系作者寻求帮助。
请给作者一些时间修复,如果作者过了一周后仍然没有修复,请联系 esources@spigotmc.org.

有人正在扩散我的插件,我应该怎么办?
有时,插件的协议不阻止分发插件,这是合法的。其他的情况则不然:
  • 插件的协议不允许用户这样做。
  • 这是一份付费插件。
如果插件没有在 SpigotMC 分发,我们无能为力,你只能联系网站的管理员。
发送你的疑虑和证据到 resources@spigotmc.org,如果你能得到插件的一份拷贝,请一起发过来。

服务与招聘

我可以在哪里招募/出售开发、系统管理、图形制作或是建筑的技术?
有一整个论坛版块都是干这个的,叫做 Services & Recruitment (S&R).
https://www.spigotmc.org/forums/services-recruitment-v2.54/

为什么我不能在 Services and Recruitment 论坛发帖?
请注意,如果你要在这个版块发布消息,你必须:
至少有 20 个回帖
账户使用期超过一周
这适用于招募或者应聘。

如何学习 Spigot API ?


安装 Spigot
Spigot 是 CraftBukkit 的一个分支,拥有更多的优化和更多的功能。安装是很简单的,你只需要将新的 Spigot jar 替换原有的 CraftBukkit jar 即可。
如果你从原来的版本升级到 CraftBukkit 1.7.9+,请阅读 UUID 转换指南
请注意 Java 8 是任何 Minecraft 程序的推荐 Java 版本。

安装

Windows
将下面的文字复制到一个文本文档里,保存为 start.bat
  1. @echo off
  2. java -Xms512M -Xmx1G -XX:+UseConcMarkSweepGC -jar spigot.jar
  3. pause
复制代码
双击启动
Windows (可选)
这下面的代码也可以
  1. @echo off
  2. :restart
  3. java -Xms512M -Xmx1G -XX:+UseConcMarkSweepGC -jar spigot.jar
  4. goto restart
复制代码

Linux
作为先决条件,请先安装 Java
使用 BuildTools 页面的指示编译 spigot jar
将 jar 文件放入新的文件夹
创建新的脚本文件(start.sh)来启动 spigot
  1. #!/bin/sh
  2. java -Xms512M -Xmx1G -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -jar spigot.jar
复制代码
请注意 MaxPermSize 在 Java 8 已经不受支持,另外这段代码对 Debian 无效
打开你的终端,使用:
  1. chmod +x start.sh
复制代码
运行脚本:
  1. ./start.sh
复制代码

Mac OS X
作为先决条件,请先安装 Java
使用 BuildTools 页面的指示编译 spigot jar
a. 创建一个 start.command
  1. #!/bin/sh
  2. cd "$( dirname "$0" )"
  3. java -Xms512M -Xmx1G -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -jar spigot.jar
复制代码
b. 放入 BuildTools.jar
c. 将脚本保存在 BuildTools 文件夹
d. 使用命令 chmod a+x 来给自己权限,将命令脚本拖入终端窗口
e. 双击脚本,等待 5 - 10 分钟的文件下载
双击你的脚本

Multicraft
决定于 Minecraft 服务器提供商的设置,我们有两种方法启用 Spigot。
如果已经存在 Spigot,选中它,重启即可。但是如果提供商没有及时更新最新的 Spigot,这样很不好。
如果你可以使用 FTP 自定义上传 JAR,那么下载 Spigot JAR,进入文件夹,复制进去,在菜单中选中。
如果你有你的个人服务器的权限,那么将 spigot.jar.conf 放入你的 daemon jar 文件夹,接着使用管理面板更新,现在这个 jar 应该是可选的了。

安装后
当 Spigot.jar 已经运行了一遍后,文件夹和配置将会创建,你需要编辑这些配置文件,让你的服务器正确工作。
server.properties
bukkit.yml
spigot.yml
服务器图标
如果服务器不正常工作,请确保你开放了端口,并且你按照上面的步骤做了、如果你有疑惑或是问题,请在 Spigot 论坛发布求助帖或是在 IRC 上联系我们。
由于 Windows 和 Mac OS X 的 kernel 的低效率,我们不推荐在这些平台上部署严肃的/商业化的服务器。


插件
在几乎所有情况下,Bukkit 插件完全兼容 Spigot,除非开发者使用了 CraftBukkit 和 Minecraft 的内部代码。
查看我们的
资源栏或是查看 BukkitDev,来寻找各种各样的插件,如添加完全新的游戏模式的管理插件。如果你没有找到合适的,你可以在 Spigot 的 Services & Recruitment 论坛或是 Bukkit 的 Plugin Requests 论坛请求制作。请按照指示发布请求。
你可以将下载的 jar 放入插件文件夹,然后重启服务器。如果这不管用或是出现了错误,请在
Spigot 论坛寻求帮助。
Linux 上的 Spigot

作为先决条件,请先安装 Java
使用 BuildTools 页面的指示编译 spigot jar
将 jar 文件放入新的文件夹
创建新的脚本文件(start.sh)来启动 spigot
  1. #!/bin/sh
  2. java -Xms512M -Xmx1G -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -jar spigot.jar
复制代码
请注意 MaxPermSize 在 Java 8 已经不受支持,另外这段代码对 Debian 无效
打开你的终端,使用:
  1. chmod +x start.sh
复制代码
运行脚本:
  1. ./start.sh
复制代码

Mac OS X 上的 Spigot
Mac OS X
作为先决条件,请先安装 Java
使用 BuildTools 页面的指示编译 spigot jar
a. 创建一个 start.command
  1. #!/bin/sh
  2. cd "$( dirname "$0" )"
  3. java -Xms512M -Xmx1G -XX:MaxPermSize=128M -XX:+UseConcMarkSweepGC -jar spigot.jar
复制代码
b. 放入 BuildTools.jar
c. 将脚本保存在 BuildTools 文件夹
d. 使用命令 chmod a+x 来给自己权限,将命令脚本拖入终端窗口
e. 双击脚本,等待 5 - 10 分钟的文件下载

需要注意的事情


当你正在构建时,千万不要关闭!你可能会因此必须重新进行所有操作。
Maven 是一个软件 API,用于有关 Java 的任何事情,包括 Bukkit 和 Minecraft 也在使用。

Windows 上的 Spigot

将下面的文字复制到一个文本文档里,保存为 start.bat
  1. @echo off
  2. java -Xms512M -Xmx1G -XX:+UseConcMarkSweepGC -jar spigot.jar
  3. pause
复制代码
双击启动
Windows (可选)
这下面的代码也可以
  1. @echo off
  2. :restart
  3. java -Xms512M -Xmx1G -XX:+UseConcMarkSweepGC -jar spigot.jar
  4. goto restart
复制代码
如果你在使用 Java 7,使用这个:
  1. @echo off
  2. java -Xms512M -Xmx1536M -XX:MaxPermSize=128M -jar spigot.jar
  3. pause
复制代码
使用 Morphia 连接到 MongoDB

简介
在继续向这个 Wiki 挖掘之前,我非常建议你先看看 MongoDB Wiki 的最开头的一段,那里讲述了 NoSQL 的细节以及如何使用的方法。阅读到它讲到如何连接 MongoDB 之前,因为剩下的就是这里的 Wiki 的意义。

什么是 Morphia?
Morphia 是一个 MongoDB 开发的 API,用于让开发者轻松映射他们的对象至 MongoDB。你可以创建类,创建字段,并且直接存储你的对象到这个数据库。这让 MongoDB 成为了一个非常精简的处理过程,这也意味着你不需要头疼数据库的问题。

将 Morphia 添加到项目
为了在项目中使用 Morphia,我使用 Maven,这既可以将资源构建整合到最后的输出,也可以管理我的依赖项目。可能后面我会添加一种不是 Maven 的管理方式,但是现在就是 Maven。

你要做的第一件事就是添加 dependencies 到你的项目。你需要添加 Maven 和 Morphia,所以将以下两段放入你的 pom.xml。
  1. <dependency>
  2.     <groupId>org.mongodb.morphia</groupId>
  3.     <artifactId>morphia</artifactId>
  4.     <version>1.3.2</version>
  5. </dependency>
  6. <dependency>
  7.     <groupId>org.mongodb</groupId>
  8.     <artifactId>mongo-java-driver</artifactId>
  9.     <version>3.4.2</version>
  10. </dependency>
复制代码
加入之后,你必须保证当你的插件编译后,Bukkit 知道如何找到你所需的库文件。有很多种方法做到这点,但是我选择了直接添加进我的插件。你只需要把这一段放入 pom.xml。
  1. <build>
  2.     <plugins>
  3.         <plugin>
  4.             <groupId>org.apache.maven.plugins</groupId>
  5.             <artifactId>maven-shade-plugin</artifactId>
  6.             <version>2.3</version>
  7.             <executions>
  8.                 <execution>
  9.                     <phase>package</phase>
  10.                     <goals>
  11.                         <goal>shade</goal>
  12.                     </goals>
  13.                     <configuration>
  14.                         <artifactSet>
  15.                             <includes>
  16.                                 <include>org.mongodb</include>
  17.                                 <include>org.mongodb.morphia</include>
  18.                             </includes>
  19.                         </artifactSet>
  20.                         <createDependencyReducedPom>false</createDependencyReducedPom>
  21.                     </configuration>
  22.                 </execution>
  23.             </executions>
  24.         </plugin>
  25.     </plugins>
  26. </build>
复制代码
现在已经完成了!你只需要连接了!

连接到 Mongo 和 Morphia
有两种不同的方法连接到 MongoClient 和 Morphia,一种需要凭据,一种不需要。如果是本地主机,那么你可以直接关闭凭据,然后直接禁止远程连接。我将会演示这两种方法。
使用凭据:
  1.     private MongoClient mc;
  2.     private Morphia morphia;

  3.     public DatabaseHandler(int i)
  4.     {
  5.         ServerAddress addr = new ServerAddress("hostname", port);
  6.         List<MongoCredential> credentials = new ArrayList<>();
  7.         credentials.add(MongoCredential.createCredential("username", "database", "password".toCharArray()));
  8.         mc = new MongoClient(addr, credentials);

  9.         morphia = new Morphia();
  10.     }
复制代码
不使用凭据:
  1.     private MongoClient mc;
  2.     private Morphia morphia;

  3.     public DatabaseHandler(int i)
  4.     {
  5.         mc = new MongoClient();

  6.         morphia = new Morphia();
  7.     }
复制代码
剩下的代码片段都是建立在你的 Mongo 客户端已经初始化完毕的前提下。

创建你的对象
我们一会儿还会回到你的 DatabaseHandler,但是现在,我们需要创建映射的对象。在这个例子里,我将会创建一个 User 类用于映射和保存。代码就在下方,我会解释每个注释的意义。
  1. import java.util.ArrayList;
  2. import java.util.List;

  3. import org.mongodb.morphia.annotations.Entity;
  4. import org.mongodb.morphia.annotations.Id;
  5. import org.mongodb.morphia.annotations.IndexOptions;
  6. import org.mongodb.morphia.annotations.Indexed;
  7. import org.mongodb.morphia.annotations.Property;

  8. @Entity(value = "Users", noClassnameStored = true)
  9. public class User
  10. {

  11.     @Id
  12.     public int id;

  13.     @Indexed(options = @IndexOptions(unique = true))
  14.     public String uuid;

  15.     @Indexed
  16.     public String username;

  17.     public int ip;

  18.     public long connectionTime;

  19.     @Property("ip_history")
  20.     public List<Integer> ipHistory = new ArrayList<>();

  21.     @Property("name_history")
  22.     public List<String> nameHistory = new ArrayList<>();

  23. }
复制代码

注释
下面的注释应该在合适的地方使用。
@Entity  这个注释告诉 Morphia 这个对象将会存储在一个集合里。“value”参数声明了集合的名称,“noClassnameStored”参数告诉 Morphia,如果接下来你改变了类的名称,MongoDB 仍然允许你存储在同一个集合。这并不是必须的,但是非常推荐使用。
@Id  这是标注每个不同的对象的唯一的 ID。你不需要自行声明,Morphia 将会在你没有声明的情况下自行创建,但是我喜欢用一个数字代表我的对象。
@Indexed  这告诉 Morphia 你会搜索整个集合,并且希望快速搜索。尽管这不是必须的,它显著的提升了搜索的时间。我的使用
@IndexOptions(unique = true) 的参数告诉 Morphia 每次只有一个有该值的对象在数据库中。
@Property  默认情况下,Morphia 将字段命名为你类中一样的名字,如果你想更改,那么使用 @Property。
@Transient  这个注释告诉 Morphia 你不想将这个字段存入数据库。用更加技术性的话来说,transient 意味着这个字段不会被序列化。
@Embedded  如果你想存贮的类不是下一节里支持的数据类型,你可以创建一个 Embedded 对象。你只需要将字段的类使用你想要的类型,并用 @Embedded 注释标记。接着,前往这个类,用注释将其中的字段全部标注。
@Reference:  对于那些从 SQL 慕名而来的用户,你可能更加熟悉外键和连接。也就是这个类存储在一个不同的集合。就比如说你已经有一个存储的类了,你现在需要直接引用,而不用重新存储他们的 ID。
想要阅读更加详细的注释资料,请自行百度。

数据类型
Morphia 不允许存储所有种类的数据,但是可以支持大多数,下面的都支持:
  • 所有的原始数据类型
  • 枚举(以 String 存储)
  • java.util.Date
  • java.util.Locale
更多的数据类型的说明都在
这里

创建你的 DAO
(注:DAO Data Access Object)
这一步不是强制性的,但是会让处理工作变得简单,接下来的教程也将以拥有 DAO 作为前提。
你将会创建每个对象的 DAO。作为示例,我将继续使用 User 类。
  1. import org.mongodb.morphia.Datastore;
  2. import org.mongodb.morphia.dao.BasicDAO;

  3. public class UserDAO extends BasicDAO<User, String>
  4. {

  5.     public UserDAO(Class<User> entityClass, Datastore ds)
  6.     {
  7.         super(entityClass, ds);
  8.     }

  9. }
复制代码
我将在下一节详细介绍如何构造你的 DAO。另外请保证通用类型参数为 <MyClass, String>, DAO 参数为 (Class<MyClass>, Datastore).

数据存储与映射
现在你有了你的类、DAO,还有你的 MongoClient 和 Morphia,现在是时候创建你的数据存储了。数据存储就是你的实际的数据库的连接。
  1.     private MongoClient mc;
  2.     private Morphia morphia;
  3.     private Datastore datastore;
  4.     private UserDAO userDAO;

  5.     public DatabaseHandler(int i)
  6.     {
  7.         mc = ...;
  8.         morphia = new Morphia();

  9.         morphia.map(User.class);

  10.         datastore = morphia.createDatastore(mc, "dbName");
  11.         datastore.ensureIndexes();

  12.         userDAO = new UserDAO(User.class, datastore);
  13.     }
复制代码
就是这样了!你可以加入一些代码用于存储数据,我在下面放了一些代码片段基于我的示例,希望能让你更好的入门。

代码片段
获得一个 User:
  1.     public DUser getUserByPlayer(Player player)
  2.     {
  3.         DUser du = userDAO.findOne("uuid", player.getUniqueId().toString());
  4.         if (du == null)
  5.         {
  6.             du = new DUser();
  7.             du.setUUID(player.getUniqueId().toString());
  8.             du.setIp(PlayerUtils.inetAddressAsInteger(player.getAddress().getAddress()));
  9.             du.setUsername(player.getName());
  10.             userDAO.save(du);
  11.         }
  12.         return du;
  13.     }
复制代码
保存一个 User:
  1.     public void saveUser(DUser user)
  2.     {
  3.         userDAO.save(user);
  4.     }
复制代码
获得全部 User:
  1.     public List<User> getAllUsers()
  2.     {
  3.         return userDAO.find().asList();
  4.     }
复制代码

插件代码片段

这是什么?
这个 Wiki 目录下的文章包含了用户贡献的片段,方便一些开发者希望共享一些片段来帮助你开发你的自己的插件。尽管他们可能很简单,但是他们旨在帮助你入门使用 Spigot API 编程,是制造更大更好的插件的垫脚石!通过参考代码片段或者这个目录下的指南,入门用户可以阅读所有的教程和指南,它们会教你好的编程习惯,提供一个有用的参考,让你浅尝 SpigotAPI 之美。

我怎样可以做贡献?
也想贡献你自己的代码?第一步,创建一个 Wiki 文章,包含你的示例代码,在 Spigot Plugin Development 论坛经过同行们的点评。我们只希望公开那些能演示好的习惯和方法、帮助开发者走上正确道路的代码。
一旦你的代码经过了用户的点评后,请创建一个新的 Wiki 文章,将父节点设置为“Plugin Snippets”。如果你有任何问题,请在讨论版块发布帖子。

异步连接数据库
开始之前
这个例子我们会使用 MongoDB,但是并不需要你有很多的 MongoDB 的知识。如果你确实希望了解 MongoDB,你可以看看前面的“使用 MongoDB”Wiki。我们也会创建一个命令,你可以在
这里查看详情。

什么是异步?
所有的程序都有一个入口,程序就一行一行的执行,这也意味着每个任务必须在前一个任务完成后才能开始。但是你也可以开始一个新的任务,这样主程序运行时也可以同时进行这个任务。
这些任务就叫做线程,系统和大多数的编程语言允许你使用它们。在有些情况下,比如你需要玩家输入聊天信息,但是你不想让整个程序在等待输入的时候完全停止,你就可以使用多个线程。线程也会被系统分配到每个 CPU 核心,这也意味着如果你有多核心的处理器,你甚至能够得到性能的提升,虽然 Minecraft 主循环使用一个 CPU 核心。
异步是同步的反义词,并且当你在另一个线程完全独立的计算某个任务,并不和主任务互相干扰,你就可以叫其异步了。当你开始了一个任务,但是你必须等待启用这个任务的线程结束后才能开始,这就是同步。每种情况都有好有坏,使用要按照具体情况而定。

创建一个 /playerinfo 命令
这个教程假设你已经有了一个连接的数据库,并且已经存在一个储存玩家数据的集合。命令会提取玩家的名字或是 UUID。数据库内的玩家的文档示例:
  1. {
  2.     _id: ObjectId("564bff868ab04da7798b4569"),
  3.     username: "Wouto1997",
  4.     lookupUsername: "wouto1997",
  5.     uuid: "a44c33ce480e486f9f782d1f52db037b",
  6.     money: 17500,
  7.     flying: true,
  8.     friends: [],
  9.     rank: "DEVELOPER",
  10.     lastSeen: ISODate("2015-11-18T11:33:10.852Z"),
  11.     registered: ISODate("2014-08-14T21:15:10.152Z")
  12. }
复制代码
所以我们立刻创建一个同步的 /playerinfo 命令:
  1. public class CommandPlayerInfo implements CommandExecutor {

  2.     @Override
  3.     public boolean onCommand(CommandSender cs, Command cmnd, String label, String[] args) {
  4.         if (args.length < 1) {
  5.             cs.sendMessage("Usage:");
  6.             cs.sendMessage("  /playerinfo <name>");
  7.             cs.sendMessage("  /playerinfo <uuid>");
  8.             return true;
  9.         }
  10.         String param = args[0];
  11.         String key = null;
  12.         if (param.length() <= 16 && param.length() >= 3) {
  13.             key = "lookupUsername";
  14.         } else {
  15.             param = param.replaceAll("-", "");
  16.             if (param.length() != 32) {
  17.                 cs.sendMessage("Invalid username or uuid");
  18.                 return true;
  19.             }
  20.             key = "uuid";
  21.         }
  22.         param = param.toLowerCase();
  23.         DBCollection playerCollection = DatabaseHelper.getPlayerDatabase();
  24.         DBObject result = playerCollection.findOne(new BasicDBObject(key, param));
  25.         if (result == null) {
  26.             cs.sendMessage("The specified player could not be found");
  27.             return true;
  28.         }
  29.         cs.sendMessage( "Information about " + ((String) result.get("username")) );
  30.         cs.sendMessage( "UUID: " + ((String) result.get("uuid")) );
  31.         cs.sendMessage( "money: " + ((Integer) result.get("money")) );
  32.         cs.sendMessage( "Fly: " + ((Boolean) result.get("fly")).toString() );
  33.         cs.sendMessage( "Friends: " + ((BasicDBList) result.get("friends")).size() );
  34.         cs.sendMessage( "rank: " + ((String) result.get("rank")) );
  35.         cs.sendMessage( "Last online: " + ((Date) result.get("lastSeen")).toLocaleString() );
  36.         cs.sendMessage( "Registered: " + ((Date) result.get("registered")).toLocaleString() );
  37.         return true;
  38.     }

  39. }
复制代码

使用 Bukkit 的异步方法
你要学习的第一个就是 Bukkit Scheduler,可以在任何地方使用 Bukkit.getScheduler() 获得一个实例。有三种不同的任何可以添加到 Scheduler,每种都有同步和异步的种类。
需要特别注意的是,同步的任何是运行于每个 Minecraft tick 的,也不是通常叫的“线程”,并且推荐不要在异步线程内使用 BukkitAPI,但是你也可以在主线程外运行复杂的任务和连接数据库。
我们将要使用 runTask 和 runTaskAsynchronously ,因为我们不需要延迟执行命令或是重复执行命令。当转换完所有的参数后,我们要做的就是“逃出”主线程并开启一个新的线程。
  1.         Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
  2.             @Override
  3.             public void run() {

  4.             }
  5.         });
复制代码
现在我们有一个异步的线程,我们可以用其存储读取数据。因为这个方法在命令之外执行,我们必须让每个变量 final,这样这个变量就不能够重新赋值了,但是变量的所有方法都可以执行。现在的代码应该像是这样:
  1.     @Override
  2.     public boolean onCommand(final CommandSender cs, Command cmnd, String label, String[] args) {
  3.         if (args.length < 1) {
  4.             cs.sendMessage("Usage:");
  5.             cs.sendMessage("  /playerinfo <name>");
  6.             cs.sendMessage("  /playerinfo <uuid>");
  7.             return true;
  8.         }
  9.         String param = args[0];
  10.         String key = null;
  11.         if (param.length() <= 16 && param.length() >= 3) {
  12.             key = "lookupUsername";
  13.         } else {
  14.             param = param.replaceAll("-", "");
  15.             if (param.length() != 32) {
  16.                 cs.sendMessage("Invalid username or uuid");
  17.                 return true;
  18.             }
  19.             key = "uuid";
  20.         }
  21.         final String fparam = param.toLowerCase();
  22.         final String fkey = key;

  23.         Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
  24.             @Override
  25.             public void run() {
  26.                 DBCollection playerCollection = DatabaseHelper.getPlayerDatabase();
  27.                 DBObject result = playerCollection.findOne(new BasicDBObject(fkey, fparam));
  28.                 if (result == null) {
  29.                     fcs.sendMessage("The specified player could not be found");
  30.                     return;
  31.                 }
  32.                 fcs.sendMessage("Information about " + ((String) result.get("username")));
  33.                 fcs.sendMessage("UUID: " + ((String) result.get("uuid")));
  34.                 fcs.sendMessage("money: " + ((Integer) result.get("money")));
  35.                 fcs.sendMessage("Fly: " + ((Boolean) result.get("fly")).toString());
  36.                 fcs.sendMessage("Friends: " + ((BasicDBList) result.get("friends")).size());
  37.                 fcs.sendMessage("rank: " + ((String) result.get("rank")));
  38.                 fcs.sendMessage("Last online: " + ((Date) result.get("lastSeen")).toLocaleString());
  39.                 fcs.sendMessage("Registered: " + ((Date) result.get("registered")).toLocaleString());
  40.             }
  41.         });

  42.         return true;
  43.     }
复制代码
注意我添加了 final 键值。现在唯一的错误就是我们在异步线程内使用了 bukkit 方法 sendMessage,尽管我可以保证这没有任何问题,但是我仍然建议你回到主线程,尤其是你要做的不只是向玩家发送信息。
尽管现在我们已经在命令中创建了一个任务,但是再创建一个就会显得很麻烦,所以我们将在下一节讲述一个更加优雅的方法。

回调
创建回调是一个是完成一个或者多个任务后的返回的很优雅的方法。之前调度器内的 Runnable 类其实就是回调,每当 Minecraft tick 过去它们就被执行一次。
创建自己的回调需要创建一个接口,我将其命名为 FindOneCallback,并且添加了查询完成后执行的方法,看起来像这样:
  1. public interface FindOneCallback {

  2.     public void onQueryDone(DBObject result);

  3. }
复制代码
现在我们想要创建一个方法,包含所有的数据库操作,这看起来可能很混乱,但是这是为了离开主线程,第二次运行则是回到主线程。
  1.     public static void findPlayerAsync(final DBObject query, final FindOneCallback callback) {
  2.         // Run outside of the tick loop
  3.         Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
  4.             @Override
  5.             public void run() {
  6.                 DBCollection playerCollection = DatabaseHelper.getPlayerDatabase();
  7.                 final DBObject result = playerCollection.findOne(query);
  8.                 // go back to the tick loop
  9.                 Bukkit.getScheduler().runTask(plugin, new Runnable() {
  10.                     @Override
  11.                     public void run() {
  12.                         // call the callback with the result
  13.                         callback.onQueryDone(result);
  14.                     }
  15.                 });
  16.             }
  17.         });
  18.     }
复制代码

重新规划命令
现在我们要做的是打包我们的命令,让其看起来更加美观实用高效:
  1.     @Override
  2.     public boolean onCommand(final CommandSender cs, Command cmnd, String label, String[] args) {
  3.         if (args.length < 1) {
  4.             cs.sendMessage("Usage:");
  5.             cs.sendMessage("  /playerinfo <name>");
  6.             cs.sendMessage("  /playerinfo <uuid>");
  7.             return true;
  8.         }
  9.         String param = args[0];
  10.         String key = null;
  11.         if (param.length() <= 16 && param.length() >= 3) {
  12.             key = "lookupUsername";
  13.         } else {
  14.             param = param.replaceAll("-", "");
  15.             if (param.length() != 32) {
  16.                 cs.sendMessage("Invalid username or uuid");
  17.                 return true;
  18.             }
  19.             key = "uuid";
  20.         }

  21.         BasicDBObject query = new BasicDBObject(key, param);

  22.         DatabaseHelper.findPlayerAsync(query, new FindOneCallback() {
  23.             @Override
  24.             public void onQueryDone(DBObject result) {
  25.                 if (result == null) {
  26.                     cs.sendMessage("The specified player could not be found");
  27.                     return;
  28.                 }
  29.                 cs.sendMessage("Information about " + ((String) result.get("username")));
  30.                 cs.sendMessage("UUID: " + ((String) result.get("uuid")));
  31.                 cs.sendMessage("money: " + ((Integer) result.get("money")));
  32.                 cs.sendMessage("Fly: " + ((Boolean) result.get("fly")).toString());
  33.                 cs.sendMessage("Friends: " + ((BasicDBList) result.get("friends")).size());
  34.                 cs.sendMessage("rank: " + ((String) result.get("rank")));
  35.                 cs.sendMessage("Last online: " + ((Date) result.get("lastSeen")).toLocaleString());
  36.                 cs.sendMessage("Registered: " + ((Date) result.get("registered")).toLocaleString());
  37.             }
  38.         });

  39.         return true;
  40.     }
复制代码

总结
使用异步编程是一种熟练地技巧,比如玩家数据加载或者是一个事件发生时,存储数据,都是推荐使用异步的。异步处理不仅仅只用于 MongoDB,它还适用于 SQL,文件读写、从网站传输、接受数据,以及其他的方面。

基础聊天禁言

基础
最基础的入门方法是学习如何取消一个事件。这在一会儿会很有用,所以让我们先看看如何取消聊天事件。
在这里,我们使用 AsyncPlayerChatEvent 事件,我们可以取消所有事件。这很有效率,会直接移除这条聊天。让我们看一看:
  1. @EventHandler
  2. public void onAsyncPlayerChat(AsyncPlayerChatEvent event) {
  3.     event.setCancelled(true);
  4. }
复制代码
我们不会讨论如何注册这个监听器,但是如果你安装了这个插件,那么聊天将会停用。

添加一个开关
看起来每次你想要聊天的时候你都会觉得受到限制是吗?让我们添加一个命令来开关它!重新看看我们的监听器:
  1. private volatile boolean chatEnabled = true;
  2. @EventHandler
  3. public void onAsyncPlayerChat(AsyncPlayerChatEvent event) {
  4.     if (!chatEnabled) {
  5.         event.setCancelled(false);
  6.     }
  7. }
复制代码
我们现在改变了检查的方式,并且添加了一个新的字段。注意 volatile 关键字是必要的,因为这个聊天事件是异步的(不在服务器线程运行)。
现在我们可以添加命令处理器(添加命令到你的 plugin.yml 不在这个片段的讲述范围内)。假设你的监听器和插件是同一个类。
  1.     @Override
  2.     public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
  3.         // 检查输入的命令
  4.         if (cmd.getName().equalsIgnoreCase("mutechat")) {
  5.             // 转换 chatEnabled 到相反的值
  6.             chatEnabled = !chatEnabled;
  7.             // See if the chatEnabled boolean is true if it is print the string 'Unmuted the chat' if not then 'Muted the chat'
  8.             sender.sendMessage(ChatColor.GREEN + (chatEnabled ? "Unmuted the chat" : "Muted the chat"));
  9.         }
  10.         // 返回值
  11.         return true;
  12.     }
复制代码
一旦这个插件开始运行,你就可以使用 /mutechat 来禁言聊天,重新使用解禁!就是这么简单!

练习
你可以怎样规划这个插件的结构呢?
怎样可以让所有的消息都被处理,但是只有发送者能看见呢?
注意我们没有包含任何权限检查。你可以怎样添加一个检查 /mutechat 命令的权限呢?
如果你使用了 /mutechat,就算你是管理员也会被禁言。怎样才能添加管理员的覆盖呢?

绕开玩家数量上限

大多数小服务器全部都被玩家人数限制所限制,下面的例子将会展示跳过人数限制的方法。
第一步,让我们创建一个主类。
  1. package me.web.playerlimitbypass;
  2. import org.bukkit.event.Listener;
  3. import org.bukkit.plugin.java.JavaPlugin;
  4. public class PlayerBypass extends JavaPlugin implements Listener {
  5.     @Override
  6.     public void onEnable() {
  7.     }
  8. }
复制代码
非常简单。
接下来,创建我们的 EventHandler 并注册。
  1.     @EventHandler
  2.     public void onPlayerLoginEvent(PlayerLoginEvent event){
  3.     }
  4.     @Override
  5.     public void onEnable() {
  6.         // 注册事件
  7.         getServer().getPluginManager().registerEvents(this, this);
  8.     }
复制代码
下面的例子是最基础的。
  1.     @EventHandler
  2.     public void onPlayerLoginEvent(PlayerLoginEvent event){
  3.         // Checking if the reason we are being kicked is a full server
  4.         if (event.getResult() == PlayerLoginEvent.Result.KICK_FULL) {
  5.             // If the condition above is true, we execute the following code, that is allow the player on the server
  6.             event.allow();
  7.         }
  8.     }
复制代码
你可以优化这个基础,随便怎样都可以。比如检查玩家是否有权限。
  1. @EventHandler
  2.     public void onPlayerLoginEvent(PlayerLoginEvent event) {
  3.         // Checking if the reason we are being kicked is a full server
  4.         if (event.getResult() == PlayerLoginEvent.Result.KICK_FULL) {
  5.             // Checking if the player has the specified permission node
  6.             if (event.getPlayer().hasPermission("playerlimit.bypass"))
  7.                 // If the condition above is true, we execute the following code, that is allow the player on the server
  8.                 event.allow();
  9.         }
  10.     }
复制代码

彩色粒子特效

有 3 种粒子可以上色,但是看起来不是很明显。
REDSTONE, SPELL_MOB 和 SPELL_MOB_AMBIENT 粒子可以使用自定义颜色,通过在生成时提供一些特殊的数据。
你需要使用 Player.spawnParticle 方法,这含有粒子所需的全部参数。特别的,你需要判断 XYZ 的偏移量,"extra" 控制亮度,"count" 控制上色开关。
这些参数的作用如下:
count: 必须设置为 0,这启用了粒子上色。注意这意味着你不能一次性生成多个彩色粒子(在一次命令/发包完成)。
extra: 这控制了粒子颜色的亮度,一般情况下你设置为 1。
offsetX: 在此输入 0 到 1 的值用于控制颜色的红色系数。
offsetY: 在此输入 0 到 1 的值用于控制颜色的绿色系数。
offsetZ: 在此输入 0 到 1 的值用于控制颜色的蓝色系数。
所以生成绿色的红石粒子,你需要使用以下代码:
  1. player.spawnParticle(Particle.REDSTONE, x, y, z, 0, 0.001, 1, 0, 1);
复制代码
你可能注意到了红色系数没有设置为 0,这是因为一些特殊的客户端的原因,将其设置为 0 会一直渲染为红色 ....
这只是红石粒子的特效,如果你想要完全的非红色,你可以使用 Float.MIN_VALUE 显示几乎没有红色。

连接到 MySQL 数据库

什么是数据库?
当你进入了程序设计的深水区后,你会希望节省时间,将数据存储在表格里,方便开发者和用户,而不是创建各种包装类,hashmap,hashmap,无尽的 hashmap,等等。这就是数据库的由来。我可以保证我们中的大多数都听说过 "数据库",而这也如其名一般:一个充满数据的库。但是它们是怎么存储的呢?他们必须使用一些高效的文件里而不是普通的文本。MySQL 服务器里的数据看起来就像这样:

看着这张图,我想你大概对这有了一些理解。数据被整洁的保存为一行一行的,每种数据都被分类为每一列。在这个教程里,我将展示如何连接你的插件至数据库。但是请注意,你必须要有一些使用 MySQL 的经验。这里是一个很好的教程,我就是在这里学习这些基础的。
现在回到正题,到底什么是数据库?在 MySQL 里的数据库是类似上面的表格,含有行和列。每一列都是一个数据类型,比如 Date,Int 等。基本的 SQL 数据类型都在这里

设置一个连接(Connection)
首先,你必须准备这些东西:
Hostname - 域名。数据库的IP地址
Port - 端口。数据库地址的端口
Database - 数据库。使用的数据库名称,因为一个服务器可以有多个数据库
Username - 用户名。用于连接数据库
Password - 密码。与用户名相似,验证登录
保证你有这五个东西,接着我们开始建立一个连接
  1. public class Test extends JavaPlugin {
  2.     private Connection connection;
  3.     private String host, database, username, password;
  4.     private int port;
  5.     @Override
  6.     public void onEnable() {  
  7.         host = "localhost";
  8.         port = 3306;
  9.         database = "BukkitCoding";
  10.         username = "root";
  11.         password = "123";     
  12.     }
  13.     @Override
  14.     public void onDisable() {
  15.     }
  16. }
复制代码
这是你的插件的类里现在应该有的东西。注意域名,端口,数据库,用户名,密码只是示例,你需要将其改为你需要的信息。让这些数据可以配置是个不错的点子,这样你不用在数据库信息改变时都需要重新编译插件。如果你打算发布这个插件,那么你必须这样做。
你可能注意到我声明的一个变量:
  1. private Connection connection;
复制代码
这是 'java.sql.Connection' 类,这也是我们用于连接的实例。在我们这样做之前,我们需要保证我们满足了所有连接 MySQL 服务器的要求。这里是一个可以返回 "安全" 的连接的方法:
  1. public void openConnection() throws SQLException, ClassNotFoundException {
  2.     if (connection != null && !connection.isClosed()) {
  3.         return;
  4.     }
  5.     synchronized (this) {
  6.         if (connection != null && !connection.isClosed()) {
  7.             return;
  8.         }
  9.         Class.forName("com.mysql.jdbc.Driver");
  10.         connection = DriverManager.getConnection("jdbc:mysql://" + this.host+ ":" + this.port + "/" + this.database, this.username, this.password);
  11.     }
  12. }
复制代码
这样做会检查系统是否安装了必须的 MySQL 的 jdbc 驱动。完成这个检查后,它将尝试使用 DriverManager 获得 Connection 实例,通过使用 'java.sql.DriverManager' 类的方法 getConnection 方法获得使用提供信息的连接。
这个方法可以通过创建一个单独的类来控制所有的 MySQL 方法来让连接更加简单。
现在你的主类应该是这样子的:
  1. public class Test extends JavaPlugin {
  2.     private Connection connection;
  3.     private String host, database, username, password;
  4.     private int port;
  5.     @Override
  6.     public void onEnable() {
  7.         host = "localhost";
  8.         port = 3306;
  9.         database = "TestDatabase";
  10.         username = "user";
  11.         password = "pass";   
  12.         try {     
  13.             openConnection();
  14.             Statement statement = connection.createStatement();         
  15.         } catch (ClassNotFoundException e) {
  16.             e.printStackTrace();
  17.         } catch (SQLException e) {
  18.             e.printStackTrace();
  19.         }
  20.     }
  21.     @Override
  22.     public void onDisable() {
  23.     }
  24.     public void openConnection() throws SQLException, ClassNotFoundException {
  25.     if (connection != null && !connection.isClosed()) {
  26.         return;
  27.     }
  28.     synchronized (this) {
  29.         if (connection != null && !connection.isClosed()) {
  30.             return;
  31.         }
  32.         Class.forName("com.mysql.jdbc.Driver");
  33.         connection = DriverManager.getConnection("jdbc:mysql://" + this.host + ":" + this.port + "/" + this.database, this.username, this.password);
  34.     }
  35. }
复制代码
现在我们需要做的是连接到 MySQL 服务器。我们怎样可以发送命令来设置或是读取数据呢?现在就是 'java.sql.Statement' 实例的用处了。由你所见,我通过使用 connection.createStatement() 来获得了一个 Statement。这个方法会返回一个 statement,用于发送命令。注意到了现在你应该需要有如何使用 SQL 语言的经验。

Statements - 读取/存储数据
你可以使用 statements 来执行命令,执行查询来接受存储在数据库里的数据,或是添加新的列,创建新的条目,或是编辑已有数据。

获得数据
假设我们有一个有两列的数据表, 'PLAYERNAME' 和 'BALANCE'。我们将要检索所有金钱为空的玩家。
  1. ResultSet result = statement.executeQuery("SELECT * FROM PlayerData WHERE BALANCE = 0;");
  2. List<String> bankruptPlayers = new ArrayList<String>();
  3. while (result.next()) {
  4.     String name = result.getString("PLAYERNAME");
  5.     bankruptPlayers.add(name);
  6. }
复制代码
现在你会看到我通过执行 statement 对象的查询来创建了一个 ResultSet 对象。当处理查询时,这需要接收一个单独的  String 作为一个你必须包含在所有的查询中的参数。这将返回一个结果集。一个 ResultSet 是一个特殊的,包含所有查询返回的值的对象。我创建了一个 while 循环,并使用了 result.next() 方法作为条件。这个方法的作用是将你得到的结果集的迭代器向前加一。如果还有可用的条目则返回 true。注意当你创建 ResultSet 时,你需要执行 result.next() 方法来移动第一个条目。在循环中,我调用了  resultset.getString("PLAYERNAME")。这将获得在 'PLAYERNAME' 下的一个玩家名。注意只有几个 get 方法才可以使用,用于接受可用的列的值。这是你从 MySQL 获得数据所需做的唯一一件事。

设置数据
使用这个教程相同的表,我们会设置一个玩家条目。不像是 getter,这更像是简单的一行代码。我们需要玩家的 String 名称,和 int 金钱。
  1. statement.executeUpdate("INSERT INTO PlayerData (PLAYERNAME, BALANCE) VALUES ('Playername', 100);");
复制代码
我们使用这个例子设置了 'Playername' 的玩家的金钱为 100。这就是向 MySQL 服务器存储数据的基本方法了。

异步数据库交互
异步的数据库交互对一个高效的插件是非常必要的。从数据库接受/存储信息会消耗时间,这是因为必须通过网络连接访问数据库,并且在主线程这样做会造成很大的延迟。
所以,"异步" 究竟是什么呢?
异步意味着不在主线程。主线程是游戏运行的循环,更新生物,破坏和放置方块。在主线程进行 IO(输入输出) 时,线程将会暂停直到输入输出完成。这就意味着当你等待 MySQL 发送数据,或者接受数据,服务器将不能进行任何事。这是造成卡顿的常见原因!
异步执行就是不在主线程执行。这意味着当你等待数据发送接受时,所有的东西都会继续运行,不会停止。
所以我要怎样做?
异步执行是很简单的,你只需要创建一个新的 Runnable:
  1. BukkitRunnable r = new BukkitRunnable() {
  2.     @Override
  3.     public void run() {
  4.         //This is where you should do your database interaction
  5.     }
  6. }
复制代码
我们创建了一个新的 BukkitRunnable 实例。这是一个效用类,通过 Bukkit 的调度器让规划的任务更加简单执行。你只需要执行 BukkitRunnable#runTaskAsynchronously(Plugin) 方法就可以将其轻松的异步执行。
  1. r.runTaskAsynchronously(<Instance of your plugin>);
复制代码
你应该将 <Instance of your plugin> 替换为你的插件主类的实例,这可以通过一个变量储存或者如果你在主类里完成数据库交互,你只需要使用关键字 this。
所以现在你需要做的就是把数据库交互的代码放进 run() 方法里。你的代码应该看起来像这样:
  1. BukkitRunnable r = new BukkitRunnable() {
  2.    @Override
  3.    public void run() {
  4.       try {
  5.          openConnection();
  6.          Statement statement = connection.createStatement();
  7.       } catch(ClassNotFoundException e) {
  8.          e.printStackTrace();
  9.       } catch(SQLException e) {
  10.          e.printStackTrace();
  11.       }
  12.    }
  13. };
  14. r.runTaskAsynchronously(this);
复制代码
一点也不难,但是你一定记住任何时候将数据库交互异步化都是极为重要的。
当你知道如何使用它们的时候,MySQL 服务器是非常实用的工具。希望这篇文章可以带领你走向使用数据库的道路。

提示
如果你正在保存很多小的变化如经济更新,请在服务器使用缓存。避免发送过多的更新到 MySQL 服务器,因为这可能造成卡顿,尤其是你在主线程这样做的时候。

创建一个简单的命令

设计命令
首先,你需要有一个已经想好的命令名,在这个片段,我们使用 "/kit",然而你应该按照自己的情况使用合适的命令。
通常情况下,推荐为每个新的命令创建一个新的类,这个类必须实现 CommandExecutor 接口。类文件看起来应该是这样的:
  1. public class CommandKit implements CommandExecutor {
  2.     // This method is called, when somebody uses our command
  3.     @Override
  4.     public boolean onCommand(CommandSender arg0, Command arg1, String arg2, String[] arg3) {
  5.             return false;
  6.     }
  7. }
复制代码
重命名参数并填充方法,这是每个参数的意义:
CommandSender 可以是是玩家, ConsoleCommandSender, 或 BlockCommandSender (一个命令方块)
Command 代表调用的命令是什么
Label 代表发送者输入的命令的第一个单词是什么 (不包括参数)
Args 是命令的剩余部分(不包括 label),以空格分开并放入数组内
在使用命令后给玩家物品之前,我们需要一个玩家对象。如果命令发送者为玩家,我们就可以执行,在这种情况,执行会给玩家物品。
注意: CommandSender 对象有时不是玩家,有时需要检查是不是玩家执行的命令。如果你要做的只是发送消息,那么类型检查会让你的代码显得杂乱,并且降低实用性。
  1.     @Override
  2.     public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
  3.         if (sender instanceof Player) {
  4.             Player player = (Player) sender;
  5.             // Here we need to give items to our player
  6.         }
  7.         // If the player (or console) uses our command correct, we can return true
  8.         return true;
  9.     }
复制代码
接着我们给玩家物品,在这个例子中,我们给玩家 1 钻石和 20 砖。ItemStack 类代表一个物品,所以让我们创建一个新的 ItemStack,设置数量,最后,我们会将其给予玩家。
我们的实例命令的代码看起来是这样的:
  1.     @Override
  2.     public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
  3.         if (sender instanceof Player) {
  4.             Player player = (Player) sender;
  5.             // Create a new ItemStack (type: diamond)
  6.             ItemStack diamond = new ItemStack(Material.DIAMOND);
  7.             // Create a new ItemStack (type: brick)
  8.             ItemStack bricks = new ItemStack(Material.BRICK);
  9.             // Set the amount of the ItemStack
  10.             bricks.setAmount(20);
  11.             // Give the player our items (comma-seperated list of all ItemStack)
  12.              player.getInventory().addItem(bricks, diamond);
  13.         }
  14.         // If the player (or console) uses our command correct, we can return true
  15.         return true;
  16.     }
复制代码
注意: 你也可以在创建物品的时候就设置数量,这样子会显得更简单,使用起来也更短。
  1. ItemStack bricks = new ItemStack(Material.BRICK, 20);
复制代码

注册命令
下一步,我们会注册我们的命令。通过在 onEnable() 方法中添加这一段代码就可以轻松完成:
  1.     @Override
  2.     public void onEnable() {
  3.         // Register our command "kit" (set an instance of your command class as executor)
  4.         this.getCommand("kit").setExecutor(new CommandKit());
  5.     }
复制代码
添加至 plugin.yml
最后一步是将我们的命令添加到 plugin.yml。打开它并加入这样一段,并测试你的插件。
  1. # Replace "kit" with the name of your command.
  2. commands:
  3.   kit:
  4.     description: Your description
  5.     usage: /kit
复制代码

有一些需要注意的事:
用于注册命令的字符串必须是你在 plugin.yml 使用的。
你可以使用你的主类实例来初始化你的 CommamdExecutor 实例,如果有必要的话。
如果你想收到你的 plugin.yml 设置的 usage 信息,将 onCommand() 方法返回 false。返回 true 代表执行成功。
当一个无效的 CommandSender 试图执行命令,你可以抛出一个异常(比如控制台使用 /kit)。
现在你已经完成了!我们希望你学会了创建命令的基础。

1.9 以上的自定义物品模型

目标
在 1.9 更新中最令人激动的一件事就是物品现在可以有不同的,基于损坏值的模型了!这样我们就可以利用这个来给 Minecraft 添加 1000 种新的物品了。
举个例子,下面的所有东西都是木锄头,使用不同的耐久值。


代码
这一部分相对来说简单一些。选择一个有耐久的物品(比如工具),接着创建一个 ItemStack,设置耐久,在这个例子中我们设置为 1。
  1. ItemStack customItem = new ItemStack(Material.WOOD_HOE, 1, (short)1);
复制代码
这会给你一个轻微损坏的木锄,像这样:

但是我们显然不需要显示耐久条,并且也不希望这个物品使用后耐久变化。幸运的是,有一个标签就是做这个用的!
https://hub.spigotmc.org/javadocs/s...ry/meta/ItemMeta.html#setUnbreakable(boolean)
  1. ItemStack customItem = new ItemStack(Material.WOOD_HOE, 1, (short)1);
  2. ItemMeta meta = customItem.getItemMeta();
  3. meta.setUnbreakable(true); // On versions 1.11 and above
  4. customItem.setItemMeta(meta);
复制代码
这会给你这个:

好,不再有损坏计了!但是如果我们想要创建自定义物品,我们可能不想要所有的原版的 lore。幸运的是,还有更多的 API!你可以添加隐藏 lore 的标签,像这样:
  1.         ItemStack customItem = new ItemStack(Material.WOOD_HOE, 1, (short)1);
  2.         ItemMeta meta = customItem.getItemMeta();
  3.         meta.spigot().setUnbreakable(true);
  4.         meta.addItemFlags(ItemFlag.HIDE_ATTRIBUTES, ItemFlag.HIDE_UNBREAKABLE);
  5.         customItem.setItemMeta(meta);
复制代码
太棒了,我们现在有了有耐久无损坏无介绍的物品了。
这就是有关代码方面的东西了。你可能想要设置自定义名称和 lore,请自行决定。
这个物品马上就要变为你梦想的自定义物品了,你只需要新的皮肤材质。

资源包
这里的东西变得有一些难懂了,如果你没有任何创建材质的经验,那么这就很简单。创建一个新的文件夹,放入看起来像这样的 pack.mcmeta 文件:
  1. {
  2.   "pack": {
  3.     "pack_format": 3,
  4.     "description": "Official elMakers Magic Resource Pack"
  5.   }
  6. }
复制代码
注意: Minecraft 1.10 或更低版本需要使用 pack_format: 2。
像 "assets/minecraft/models/item" 一样添加子文件夹。
在物品文件夹内,创建一个新的文件叫做 wooden_hoe.json。这将是你所有的自定义物品的 "容器"。这告诉了客户端应该在什么地方使用什么模型。
这个文件看起来应该是这样的:
  1. {
  2.   "parent": "item/handheld",
  3.   "textures": {
  4.     "layer0": "items/wood_hoe"
  5.   },
  6.   "overrides": [
  7.     {"predicate": {"damaged": 0, "damage": 0.01666666666667}, "model": "item/my_cool_custom_item"}
  8.   ]
  9. }
复制代码
好吧,让我们拆开来讲。
我们覆盖了木锄,但是拓展了新的版本。 "parent" 和 "textures" 告诉客户端使用原版模型。这意味着没有损坏的木锄看起来是原样。
现在是新的物品了,"overrides" 让你给不同的货品使用依赖的物品模型。这些设置被称作 "predicates",我们的目的是关注 "damage"  关键字。
这将会将基于伤害值的模型多样化,如 0 - 1 的百分比。所以 "0.016666" 的值是来自于 "1/60" 的比率,因为这个物品最大的耐久为 60。
查看 mc Wiki 中有关耐久的部分: http://minecraft.gamepedia.com/Hoe
你也可以添加很多种 predicate,只要每次计算 "x/60" 的公式即可。
你也可以使用这个工具来给物品一个特定的模板而不需要计算: http://accidentalgames.co"/media/durabilityModels.php

保存原始物品
如果你仍然想用原版物品,那么这是可行的!
添加另一行新的条目到你的模型中,看起来是这样的:
  1. {
  2.   "parent": "item/handheld",
  3.   "textures": {
  4.     "layer0": "items/wood_hoe"
  5.   },
  6.   "overrides": [
  7.     {"predicate": {"damaged": 0, "damage": 0.01666666666667}, "model": "item/my_cool_custom_item"},
  8.     {"predicate": {"damaged": 1, "damage": 0}, "model": "item/wooden_hoe"}
  9.   ]
  10. }
复制代码
最后,将你的自定义物品模型放入材质包中的同一位置,这里是 "my_cool_custom_item.json"。 (创建材质不是这个教程的目的,我认为已经有很好的教程和类似 Cubik 这样的工具了)
将你的材质包压缩,放入 <minecraft>/resourcepacks 文件夹,你就可以加载并使用你的自定义材质了!
如果还有疑惑,你可以看看我的GitHub上的材质包结构:
https://github.com/elBukkit/MagicPlugin/tree/master/Magic/src/resource-pack/default

最终总结
一旦这些都可以正常使用后,你就应该考虑这些问题。
更高等级的工具可以提供更多物品,比如钻石锄可以提供最高 1,562 的耐久度 (!)
如果你想在服务器使用,你必须有个地方用于下载你的材质包!我认为 Dropbox 可以一试,只需要获得一个直链,然后添加到你的 server.properties 文件。
如果你想要更新你的材质包,记得添加 SHA1 码到你的 server.properties 文件,或者每次都重命名你的材质包,否则客户端不会重新下载。
另一个 1.9 的酷炫特性就是物品可以穿戴在头上并且会在游戏中显示!你需要添加一个 "head" 显示块到你的模型文件中,你可以在这里看看:

https://github.com/elBukkit/MagicPl...ecraft/models/item/custom/magic_hat.json#L587
我希望这可以帮到你,如果你在教程中找到了任何的问题,请务必让我知道!

使用 Vault

我们将会创建 3 个主命令:
  • resetbalance - 设置玩家账户为 0
  • givemoney - 将 100 放入玩家账户
  • money - 查看玩家账户

我们需要做的第一件事是下载 Vault,你可以在这里下载。
接着,你需要将 "Vault" 添加到你的构建路径,不懂百度,或者看这篇Wiki。
接着我们可以开始写代码了,你需要一个新的类,并且在 onEnable() 中连接到 Vault。
  1. package me.yourpackage.vault;
  2. import net.milkbowl.vault.economy.Economy;
  3. import org.bukkit.java.JavaPlugin;
  4. import org.bukkit.plugin.java.JavaPlugin;
  5. public class Main extends JavaPlugin{
  6.     private static final Logger log = Logger.getLogger("Minecraft");
  7.     public static Economy econ = null;
  8.     private static Permission perms = null;
  9.     private static Chat chat = null;
  10.     @Override
  11.     public void onEnable(){
  12. if (!setupEconomy()) {
  13.             log.severe(String.format("[%s] - Disabled due to no Vault dependency found!", getDescription().getName()));
  14.             getServer().getPluginManager().disablePlugin(this);
  15.             return;
  16.         }
  17.         setupPermissions();
  18.         setupChat();
  19.     }
  20. public void onDisable(){
  21. log.info(String.format("[%s] Disabled Version %s", getDescription().getName(), getDescription().getVersion()));
  22. }
  23.     private boolean setupEconomy() {
  24.         if (getServer().getPluginManager().getPlugin("Vault") == null) {
  25.             return false;
  26.         }
  27.         RegisteredServiceProvider<Economy> rsp = getServer().getServicesManager().getRegistration(Economy.class);
  28.         if (rsp == null) {
  29.             return false;
  30.         }
  31.         econ = rsp.getProvider();
  32.         return econ != null;
  33.     }
  34.     private boolean setupChat() {
  35.         RegisteredServiceProvider<Chat> rsp = getServer().getServicesManager().getRegistration(Chat.class);
  36.         chat = rsp.getProvider();
  37.         return chat != null;
  38.     }
  39.     private boolean setupPermissions() {
  40.         RegisteredServiceProvider<Permission> rsp = getServer().getServicesManager().getRegistration(Permission.class);
  41.         perms = rsp.getProvider();
  42.         return perms != null;
  43.     }
  44.     public static Economy getEcononomy() {
  45.         return econ;
  46.     }
  47.     public static Permission getPermissions() {
  48.         return perms;
  49.     }
  50.     public static Chat getChat() {
  51.         return chat;
  52.     }
  53. }
复制代码
现在我们可以开始我们的第一个命令了。
所以,我们在一个新的类做这件事,但是你必须先学会使用命令系统。
  1. public boolean onCommand(CommandSender player, Command cmd, String label, String[] args) {
  2. }
复制代码
现在开始写第一个命令,resetbalance。
  1. Main.econ.withdrawPlayer(args[0], Main.econ.getBalance(args[0]));
  2. if (!(Bukkit.getPlayer(args[0]) == null)){
  3.            Main.econ.withdrawPlayer(args[0], Main.econ.getBalance(args[0]));
  4.            return true;
  5. } else {
  6. //Player Isnt Online
  7. return true;
  8. }}
复制代码

可交互的书

创建一个书
我们需要 ItemStack 的 BookMeta 来开始我们的第一步。只有一个为 Material.BOOK_AND_QUILL 或 Material.WRITTEN_BOOK 的物品才有 BookMeta。
  1. ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
  2. BookMeta bookMeta = (BookMeta) book.getItemMeta();
复制代码

获取每一页
BookMeta 是一个 CraftBukkit 中的 CraftMetaBook 的 Bukkit 接口。这个教程写的时候,BookMeta 还没有一个方法获得一个 IChatBaseComponent 列表的方法,所以我们必须使用反射来获得。这是一个示例:
  1. List<IChatBaseComponent> pages = (List<IChatBaseComponent>) CraftMetaBook.class.getDeclaredField("pages").get(bookMeta);
复制代码
这个字段的任何变化都会让书更新,所以我们编辑完成后必读赋值回去。

创建一个可以点击和悬浮查看的页
使用 BungeeCord 的 ChatComponentAPI(包括在 Spigot 服务器 jar 中),你可以制造一个 IChatComponent 使用 BungeeCordAPI,就可以创建一个 IChatBaseComponent 来制作悬浮和点击事件了。
当然你也可以创建自己的 jsonString,这可以让你使用书本特定的事件。
  1. BaseComponent text; //this is the base component we will be turning into a page
  2. //convert the base component into a json string
  3. String pageJson = ComponentSerializer.toString(text);
  4. //get an IChatBaseComponent object which represents this json string
  5. IChatBaseComponent page = IChatBaseComponent.ChatSerializer.a(pageJson);
  6. //add this page to the pages list
  7. pages.add(page);
复制代码

完成创建 ItemStack
当你添加了这个页之后,不要忘记将新的 BookMeta 添加回原来的 ItemStack! 你也可以设置书本的标题和作者。
  1. bookMeta.setTitle("Interactive Book");
  2. bookMeta.setAuthor("gigosaurus");
  3. book.setItemMeta(bookMeta);
复制代码

一个样书
  1. //create the book
  2. ItemStack book = new ItemStack(Material.WRITTEN_BOOK);
  3. BookMeta bookMeta = (BookMeta) book.getItemMeta();
  4. List<IChatBaseComponent> pages;
  5. //get the pages
  6. try {
  7.     pages = (List<IChatBaseComponent>) CraftMetaBook.class.getDeclaredField("pages").get(bookMeta);
  8. } catch (ReflectiveOperationException ex) {
  9.     ex.printStackTrace();
  10.     return;
  11. }
  12. //create a page
  13. TextComponent text = new TextComponent("Click me");
  14. text.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "http://spigotmc.org"));
  15. text.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("Goto the spigot website!").create()));
  16. //add the page to the list of pages
  17. IChatBaseComponent page = ChatSerializer.a(ComponentSerializer.toString(text));
  18. pages.add(page);
  19. //set the title and author of this book
  20. bookMeta.setTitle("Interactive Book");
  21. bookMeta.setAuthor("gigosaurus");
  22. //update the ItemStack with this new meta
  23. book.setItemMeta(bookMeta);
复制代码
不同版本的 NMS (无反射)


我看见过很多人制作了一些固定版本的插件,用来发送 title,或者 actionbar 信息。很多人通过使用 NMS(net.minecraft.server) 实现这些功能,并且常常只有这样做才能实现这样的功能。因为 NMS 是版本特定的,所以我见过很多限定版本的插件,当新版本发行后,他们就不再支持原来的版本了。
之前我在寻找让我的插件支持多版本的最好方法就是使用反射。
当我在想 这是一个好事的时候(因为我的插件不支持老版本),@DarkSeraphim 在我发布的一篇关于如何使用反射的帖子里告诉我说,服务器使用反射的情况更少,我们应该使用一个接口,用来检查版本合适时才加载我的插件。
使用接口可以让我的 NMS 代码放在不同的类中,并且根据版本来使用特定的类,这完全避免了使用反射!
尽管这个指南没有消除使用反射的需要,因为你可能遇到各种情况(有时你有必要使用它们),比如发送一个 title 或者一条 actionbar 信息,这不是很棒吗?
在这个示例插件里我们会发送一条 actionbar 公告到每个登录的玩家,这个插件将会在任何 1.8 的服务器工作。
所以让我们开始:
在你的插件里,你应该新建一个包用来放接口和 NMS 类:
在这个包里,我们会创建一个叫 Actionbar 的接口,这个接口会含有 NMS 类用于发送实际的 actionbar 的虚方法,任何实现这个接口的类必须包含这个方法。
所以让我们开始创建接口吧:
  1. package me.clip.actionbarplugin.actionbar;
  2. import org.bukkit.entity.Player;
  3. public interface Actionbar {
  4.     public void sendActionbar(Player p, String message);
  5. }
复制代码
很简单是吧,只有一行代码,这是我们实现这个接口的类需要调用 NMS 的方法。
如果你想给你的类添加更多功能,你可以任意添加新的方法,但是每个实现它的类必须包含列出的所有方法。
现在我们有了自己的接口,让我们创建实际的用于发送 actionbar,使用 NMS 的类吧!
1.8.1 的实现了 Actionbar 接口的类:
  1. package me.clip.actionbarplugin.actionbar;
  2. import net.minecraft.server.v1_8_R1.ChatSerializer;
  3. import net.minecraft.server.v1_8_R1.IChatBaseComponent;
  4. import net.minecraft.server.v1_8_R1.PacketPlayOutChat;
  5. import org.bukkit.craftbukkit.v1_8_R1.entity.CraftPlayer;
  6. import org.bukkit.entity.Player;
  7. public class Actionbar_1_8_R1 implements Actionbar {
  8.     @Override
  9.     public void sendActionbar(Player p, String message) {
  10.         IChatBaseComponent icbc = ChatSerializer.a("{"text": "" + message + ""}");
  11.         PacketPlayOutChat bar = new PacketPlayOutChat(icbc, (byte) 2);
  12.         ((CraftPlayer) p).getHandle().playerConnection.sendPacket(bar);
  13.     }
  14. }
复制代码
1.8.3 的实现了 Actionbar 接口的类:
  1. package me.clip.actionbarplugin.actionbar;
  2. import net.minecraft.server.v1_8_R2.IChatBaseComponent;
  3. import net.minecraft.server.v1_8_R2.PacketPlayOutChat;
  4. import net.minecraft.server.v1_8_R2.IChatBaseComponent.ChatSerializer;
  5. import org.bukkit.craftbukkit.v1_8_R2.entity.CraftPlayer;
  6. import org.bukkit.entity.Player;
  7. public class Actionbar_1_8_R2 implements Actionbar {
  8.     @Override
  9.     public void sendActionbar(Player p, String message) {
  10.         IChatBaseComponent icbc = ChatSerializer.a("{"text": "" + message + ""}");
  11.         PacketPlayOutChat bar = new PacketPlayOutChat(icbc, (byte) 2);
  12.         ((CraftPlayer) p).getHandle().playerConnection.sendPacket(bar);
  13.     }
  14. }
复制代码
现在我们有了所有的版本特定的类了,现在需要做的就是在主类里加载正确的类,获得一个 Actionbar 实例。
Actionbar 插件:
  1. package me.clip.actionbarplugin;
  2. import me.clip.actionbarplugin.actionbar.Actionbar;
  3. import me.clip.actionbarplugin.actionbar.Actionbar_1_8_R1;
  4. import me.clip.actionbarplugin.actionbar.Actionbar_1_8_R2;
  5. import org.bukkit.Bukkit;
  6. import org.bukkit.event.EventHandler;
  7. import org.bukkit.event.Listener;
  8. import org.bukkit.event.player.PlayerJoinEvent;
  9. import org.bukkit.plugin.java.JavaPlugin;
  10. public class ActionbarPlugin extends JavaPlugin implements Listener {
  11.     // our interface reference! Any class that implements Actionbar can be assigned to this reference!
  12.     // when we need to send an actionbar, all we need to do is call actionbar.sendActionbar(player, message);
  13.     // since the proper NMS class was assigned onEnable, we are now backwards compatible!
  14.     private Actionbar actionbar;
  15.     @Override
  16.     public void onEnable() {
  17.         if (setupActionbar()) {
  18.             Bukkit.getPluginManager().registerEvents(this, this);
  19.             getLogger().info("Actionbar setup was successful!");
  20.             getLogger().info("The plugin setup process is complete!");
  21.         } else {
  22.             getLogger().severe("Failed to setup Actionbar!");
  23.             getLogger().severe("Your server version is not compatible with this plugin!");
  24.             Bukkit.getPluginManager().disablePlugin(this);
  25.         }
  26.     }
  27.     // this method will setup our actionbar class and return true if the server is running a
  28.     // version compatible with our NMS classes.
  29.     // If the server is not compatible, it will return false!
  30.     private boolean setupActionbar() {
  31.         String version;
  32.         try {
  33.             version = Bukkit.getServer().getClass().getPackage().getName().replace(".",  ",").split(",")[3];
  34.         } catch (ArrayIndexOutOfBoundsException whatVersionAreYouUsingException) {
  35.             return false;
  36.         }
  37.         getLogger().info("Your server is running version " + version);
  38.         if (version.equals("v1_8_R1")) {
  39.             //server is running 1.8-1.8.1 so we need to use the 1.8 R1 NMS class
  40.             actionbar = new Actionbar_1_8_R1();
  41.         } else if (version.equals("v1_8_R2")) {
  42.             //server is running 1.8.3 so we need to use the 1.8 R2 NMS class
  43.             actionbar = new Actionbar_1_8_R2();
  44.         }
  45.         // This will return true if the server version was compatible with one of our NMS classes
  46.         // because if it is, our actionbar would not be null
  47.         return actionbar != null;
  48.     }
  49.     @EventHandler
  50.     public void onJoin(PlayerJoinEvent event) {
  51.         actionbar.sendActionbar(event.getPlayer(), "Welcome to the server!");
  52.     }
  53. }
复制代码
在我的教程插件里,我将所有的东西放在了主类。
,如果你有一个更大的插件,并且使用了多个类(如你的监听器类),你应该创建 Actionbar 的 getter,这样你在其他的类里也可以访问这个字段。
创建一个 getter 是非常简单的,你看了这个就懂了:
  1. public Actionbar getActionbar() {
  2.     return actionbar;
  3. }
复制代码
现在你的类就可以发送 actionbar 信息了!
有很多种方式检查服务器版本,这完全由你决定,我使用了 Bukkit 包名,但是你也可以使用 Bukkit.getBukkitVersion() 然后决定使用哪一个 NMS 类。
这样处理 NMS 代码是向后兼容的,这是个很蠢的证明。
如果你的插件加载了与版本不符合的类,服务器就会抛出一个 ClassNotFoundException 异常。
所以请保证只加载正确版本的类。
就是这样了,我们现在有了一个向后兼容的 actionbar 插件,使用 NMS 而不是反射!当 Spigot 的新版本发行后,有了新的包名,我们要做的只是添加一个新的 NMS 类,然后更新,检查新版本的加载设定。

感谢阅读。我不是最好的开发者,我也像你一样每天学习新的知识。
我希望这个教程可以让一些人学会如何使用接口来用 NMS 在不同版本上做事而不用反射。

创建自定义的合成

在这个代码片段中,我们会向你展示如何创造使用自定义物品的自定义合成。我们将会制作一个以钻石剑为基础的绿宝石剑。

你需要什么
现在我们需要一个继承了 JavaPlugin 的类,含有 onEnable() 方法。

变量
现在我们开始,我们需要 3 个 onEnable() 内的变量。
  1. // Our custom variable which we will be changing around.
  2. ItemStack item = new ItemStack(Material.DIAMOND_SWORD);
  3. // The meta of the diamond sword where we can change the name, and properties of the item.
  4. ItemMeta meta = item.getItemMeta();
  5. // We will initialise the next variable after changing the properties of the sword
复制代码
这里我们更改了属性,这样这就不是一个普通的钻石剑了。
  1. // This sets the name of the item.
  2. // Instead of the § symbol, you can use ChatColor.<color>
  3. meta.setDisplayName("§aEmerald Sword");
  4. // Set the meta of the sword to the edited meta.
  5. item.setItemMeta(meta);
  6. // Add the custom enchantment to make the emerald sword special
  7. // In this case, we're adding the permission that modifies the damage value on level 5
  8. // Level 5 is represented by the second parameter. You can change this to anything compatible with a sword
  9. item.addEnchantment(Enchantment.DAMAGE_ALL, 5);
复制代码

创建新的合成
这里就是最有趣的地方了,我们的变量在这里将会被使用。
  1. // Create our custom recipe variable
  2. ShapedRecipe recipe = new ShapedRecipe(item);
  3. // Here we will set the places. E and S can represent anything, and the letters can be anything. Beware; this is case sensitive.
  4. recipe.shape(" E ", " E ", " S ");
  5. // Set what the letters represent.
  6. // E = Emerald, S = Stick
  7. recipe.setIngredient('E', Material.EMERALD);
  8. recipe.setIngredient('S', Material.STICK);
  9. // Finally, add the recipe to the bukkit recipes
  10. Bukkit.addRecipe(recipe);
复制代码

完成
你已经完成了创建新的合成,现在你应该看看合在一起的代码:
  1.    public void onEnable() {
  2.         // Our custom variable which we will be changing around.
  3.         ItemStack item = new ItemStack(Material.DIAMOND_SWORD);
  4.         // The meta of the diamond sword where we can change the name, and properties of the item.
  5.         ItemMeta meta = item.getItemMeta();
  6.         // We will initialise the next variable after changing the properties of the sword
  7.         // This sets the name of the item.
  8.         // Instead of the § symbol, you can use ChatColor.<color>
  9.         meta.setDisplayName("§aEmerald Sword");
  10.         // Set the meta of the sword to the edited meta.
  11.         item.setItemMeta(meta);
  12.         // Add the custom enchantment to make the emerald sword special
  13.         // In this case, we're adding the permission that modifies the damage value on level 5
  14.         // Level 5 is represented by the second parameter. You can change this to anything compatible with a sword
  15.           item.addEnchantment(Enchantment.DAMAGE_ALL, 5);
  16.         // Create our custom recipe variable
  17.         ShapedRecipe recipe = new ShapedRecipe(item);
  18.         // Here we will set the places. E and S can represent anything, and the letters can be anything. Beware; this is case sensitive.
  19.         recipe.shape(" E ", " E ", " S ");
  20.         // Set what the letters represent.
  21.         // E = Emerald, S = Stick
  22.         recipe.setIngredient('E', Material.EMERALD);
  23.         recipe.setIngredient('S', Material.STICK);
  24.         // Finally, add the recipe to the bukkit recipes
  25.         Bukkit.addRecipe(recipe);
  26.     }
复制代码
确保你的所有的导入都来自 org.bukkit.
这里是正确的导入列表:
  1. import org.bukkit.Material;
  2. import org.bukkit.enchantments.Enchantment;
  3. import org.bukkit.inventory.ItemStack;
  4. import org.bukkit.inventory.ShapedRecipe;
  5. import org.bukkit.inventory.meta.ItemMeta;
复制代码
来自 md-5 的源码就在 GitHub
https://github.com/md-5/SmallPlugins/blob/master/EmeraldSword/src/main/java/net/md_5/EmeraldSword.java

编辑、使用牌子

这是一个用来教你如何编辑牌子,交互牌子的教程。我可不是最好的插件编写者,所以如果有什么缺漏或者错误请务必告诉我,然后我会添上。

SignChangeEvent
这个事件显然是当牌子更改时被触发,并且可以取消。关于事件的其他说明和 API 可以前往 Spigot Developer Hub 查看。现在,让我们先开始写一些代码,我会先创建一个简单的 EventHandler:
  1. @EventHandler
  2. public void onSignChange(SignChangeEvent e) {
  3.     if (e.getPlayer().hasPermission("sign.color")) {
  4.     }
  5. }
复制代码
上面的代码也包含了一个简单的权限检查,方便管理服务器。下一步,我们显然知道牌子有 4 行,所以我们可以这样更新我们的 handler:
  1. @EventHandler
  2. public void onSignChange(SignChangeEvent e) {
  3.     if (e.getPlayer().hasPermission("sign.color")) {
  4.         for (int i = 0; i < 4; i++) {
  5.             String line = e.getLine(i);
  6.             if (line != null && !line.equals("")) {
  7.                 e.setLine(i, ChatColor.translateAlternateColorCodes('&', line));
  8.             }
  9.         }
  10.     }
  11. }
复制代码
现在我们拆开来讲,我们有一个重复 4 遍的循环:
  1. for (int i = 0; i < 4; i++) {
复制代码
接着,用代码获取每一行,要保证其不是 null 或者是一个空的字符串,因为玩家可能什么也没有写。
  1. String line = e.getLine(i);
  2. if (line != null && !line.equals("")) {
复制代码
这个检查完成后,我们现在可以操作这个事件了,在这个例子里,我们将会吧 '&' 替换为真正的颜色符。
  1. e.setLine(i, ChatColor.translateAlternateColorCodes('&', line));
复制代码
这就是了,这是我们的 SignChangeEvent ,我们可以用它设置获取行数内容,但是不要忘了有个方法可以返回所有的行。
  1. getLines()
复制代码

如何从一个方块得到牌子
这就是代码,你可以运行:
  1. Block b = p.getLocation().getBlock();
复制代码
你是知道这是一个牌子的,你只是想编辑它,但是你不能,因为电脑不知道这是一个牌子,所以我们需要告诉他怎么做,通过使用:
  1. getType()
复制代码
返回一个 Material 枚举告诉你这是一个牌子。
  1. if (b.getType() == Material.WALL_SIGN || b.getType() == Material.SIGN_POST)
复制代码
当这是一个牌子,你就可以将 Block State 转换为牌子:
  1. Sign sign = (Sign)b.getState();
复制代码
保证你导入了 Block 对象。
  1. import org.bukkit.block.Sign;
复制代码
而不是
  1. import org.bukkit.material.Sign;
复制代码
这是两个完全不同的东西,当你编辑某个方块你需要导入它,现在我们有了方块,我们可以做很多事了,比如:
  1. sign.getLine(int line); // Get any line (line ranges from 0 to 3); returns String
  2. sign.setLine(int line, String text); // Set any line (Same range for line); returns void
  3. sign.getLines(); // Get all the lines; returns String[]
复制代码
现在继续,我们可以使用 PlayerInteractEvent 来让点击木牌的玩家交互,只需要继续按照上面的代码来:
  1. @EventHandler
  2. public void onSignClick(PlayerInteractEvent e) {
  3.     if (e.getAction() != Action.RIGHT_CLICK_BLOCK) {
  4.         return;
  5.     }
  6.     Player p = e.getPlayer();
  7.     if (p.hasPermission("sign.use")) {
  8.         Block b = e.getClickedBlock();
  9.         if (b.getType() == Material.SIGN_POST || b.getType() == Material.WALL_SIGN) {
  10.             Sign sign = (Sign) b.getState();
  11.             if (ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("[WARP]")) {
  12.                 String warp = sign.getLine(2);
  13.                 Bukkit.dispatchCommand(p, "warp " + warp);
  14.             }
  15.         }
  16.     }
  17. }
复制代码
这个部分你唯一不需要详细了解的就是
  1. if (ChatColor.stripColor(sign.getLine(0)).equalsIgnoreCase("[WARP]")) {
  2.     String warp = sign.getLine(2);
  3.     Bukkit.dispatchCommand(p, "warp " + warp);
  4. }
复制代码
现在,我们应该检查第一行是不是 [WARP],如果是,我们就要将玩家传送到第三行的坐标去。(我们使用了 dispatchCommand,这会提醒玩家是否有权限传送)
上面的这些包含了一些轻量的木牌传送,区域跳跃 甚至是有趣的小游戏。我希望这个教程可以教会不知道木牌的开发者们。请自由留下评论 我只是希望教会人们怎么在 Bukkit/Spigot 使用木牌。
如果你觉得你能添加更多,或者修复我的一些小错误,请随意编辑!我会很高兴有这么多人帮助我完善这篇教程。

不同版本的 Sound 枚举

如果你想要在插件里给玩家播放声音,这是很简单的,你只需要
  1. player.playSound(player.getEyeLocation(), Sound.BLOCK_NOTE_PLING, 1, 1);
复制代码
所以这有什么问题吗?其实是因为在 1.9,Sound 枚举里的声音名称已经改变,变得更加像你的 /playesound 命令一样。你可以在这里找到
1.9
1.9 之前
现在,所有为 1.7/1.8 制造的插件不会继续在 1.9 上运行了!如何让 1.9 的插件仍然有向后的兼容呢?好吧,你可以使用 if 来判断版本号。我们快来试试!
  1. if (Bukkit.getVersion().contains("1.9")) {
  2.      player.playSound(player.getEyeLocation(), Sound.BLOCK_NOTE_PLING, 1, 1);
  3. } else {
  4.      player.playSound(player.getEyeLocation(), Sound.NOTE_PLING, 1, 1);
  5. }
复制代码
好吧,很令人悲伤,这也不会运行,为什么呢?因为 Sound.NOTE_PLING 没有在 1.9 出现,但你使用了 1.9 作为你的库!而且不要试图切换库的版本,要不然第一个就不会正常工作了!所以要怎样同时使用两个库呢?其实这很简单:
  1. if (Bukkit.getVersion().contains("1.9")) {
  2.      player.playSound(player.getEyeLocation(), Sound.valueOf("BLOCK_NOTE_PLING"), 1, 1);
  3. } else {
  4.      player.playSound(player.getEyeLocation(), Sound.valueOf("NOTE_PLING"), 1, 1);
  5. }
复制代码
现在对了!你可以一排解决,牺牲一些可读性,像这样:
  1. player.playSound(player.getEyeLocation(), Sound.valueOf(Bukkit.getVersion().contains("1.9") ? "BLOCK_NOTE_PLING" : "NOTE_PLING"), 1, 1);
复制代码
如果你正在制造一些像是 Util 的东西,你可以这样:
  1. public static String getPre1_9Name(Sound sound) {
  2.      switch (sound) {
  3.           case BLOCK_NOTE_PLING: return "NOTE_PLING";
  4.           case ENTITY_ITEM_BREAK: return "ITEM_BREAK";
  5.      }
  6. }
复制代码
等等。
这是一个简单的、能让你的插件兼容多个版本、能让玩家享受完美的音效的小技巧。


禁止小僵尸

这是一个简单的示例,展示了如何在一个生物即将出生时编辑它。
在这个例子里,如果一个僵尸出生并且是一个小僵尸,你可以阻止生成或者将其更改为普通僵尸。
在这个例子里,我们阻止生成。下面的例子我们将其更改为大僵尸,你可以学习在自己的实际情况下使用。
  1. //called when a creature spawns.
  2. @EventHandler
  3. public void onCreatureSpawn(CreatureSpawnEvent event) {
  4.     //check if the creature spawned is a zombie.
  5.     if (event.getEntity().getType() == EntityType.ZOMBIE) {
  6.         //create a variable to store the zombie.
  7.         Zombie zombie = (Zombie) event.getEntity();
  8.         //check if the zombie is a baby.
  9.         if(zombie.isBaby()) {
  10.             // don't spawn it.
  11.             event.setCancelled(true);
  12.              // Or you could instead just restore the zombie's maturity with this line.
  13.             zombie.setBaby(false);
  14.         }
  15.     }
  16. }
复制代码



防止 Tab 重置你的配置文件

目标
作为一个插件开发者,如果你有一个 config.yml 在你的插件里,你会知道当用户告诉你他们的整个 config.yml 被重置了的痛苦、随着脸色一阵苍白,你知道这是因为用户输入了 Tab 而不是空格。
所以我们应该怎么样呢?有了这个教程,你可以监测 yml 文件中的 TAB 防止文件重置。不再需要在线的 YAML 转换器了!你的插件将会很有效率的处理。

开始之前
非常建议你先对以下进行了解后再开始。
  • 对 java.util.Scanner 的基础了解
  • 对 Bukkit Configuration API 的基础了解

建立你的类
我们要用主类来进行 TAB 检测。
  1. /**
  2. * Our class name is going to be YamlTabParser. Replace with your main class
  3. * name!
  4. *
  5. */
  6. public class YamlTabParser extends JavaPlugin {}
复制代码

基础操作
接下来我们定义两个变量。
  1. public class YamlTabParser extends JavaPlugin {
  2.     /*
  3.      * These two member variables are pointers to the config.yml file we will be
  4.      * creating. They are fields because they have a very broad scope and must
  5.      * be handled appropriately during save and load operations
  6.      */
  7.     private File file;
  8.     private FileConfiguration config;
  9. }
复制代码
接着我们覆写 onEnable().
  1.     @Override
  2.     public void onEnable() {
  3.         // define member variables
  4.         // NOTE: You cannot use a constructor in the main class! This would
  5.         // crash your plugin!
  6.         file = new File(getDataFolder(), "config.yml");
  7.         config = new YamlConfiguration();
  8.         // reload and save files, we will be overriding the JavaPlugin
  9.         // implementations in a later step.
  10.         reloadConfig();
  11.         saveConfig();
  12.     }
复制代码

覆写 saveConfig() 和 getConfig()
我们在 onEnable() 完成初始化后,我们必须覆写 saveConfig() 方法和 getConfig() 方法来匹配我们之前定义的成员变量。
  1.     /**
  2.      * Overrides by returning our defined FileConfiguration variable
  3.      */
  4.     @Override
  5.     public FileConfiguration getConfig() {
  6.         return config;
  7.     }
  8.     /**
  9.      * Overrides the default save operation by saving using our predefined File
  10.      * and FileConfiguration variables.
  11.      */
  12.     @Override
  13.     public void saveConfig() {
  14.         try {
  15.             config.save(file);
  16.         }
  17.         catch (IOException e) {
  18.             // print stacktrace if you prefer
  19.             getLogger().severe(
  20.                     "Could not save config.yml due to: " + e.getMessage());
  21.         }
  22.     }
复制代码
注意这里没什么多说的,我们只是在制作一个配置存储的方法。
技巧: 使用 @Override 注释,这样如果你有方法拼写错误,编译器将会提示你。

创建你的 Scanner
现在如果你按照之前的步骤来的话,我们就要开始真正的重点:扫描文件中的 TAB。
注意: 如果你之前没有使用过 Scanners,我建议你花些时间来阅读一下。这会让你理解下面几步。
我们的方法返回 void,并且没有参数。因为我们的 FileConfiguration 和 File 变量是全局的,这是最方便的。
  1.     /**
  2.      * Opens a java.util.Scanner to scan the config file for any tabs.
  3.      */
  4.     private void scanConfig() {}
复制代码
声明 Scanner
  1.     private void scanConfig() {
  2.         // declare our scanner variable
  3.         Scanner scan = null;
  4.     }
复制代码
定义 Scanner
  1.     private void scanConfig() {
  2.         Scanner scan = null;
  3.         try {
  4.             scan = new Scanner(file);
  5.         }
  6.         catch (FileNotFoundException e) {
  7.             // this error should never happen if the file exists
  8.             e.printStackTrace();
  9.         }
  10.     }
复制代码
现在我们完成了一些基础工作,scanner 读取文件或是一个一个读字节都是很有效率的。当我们读取文件时,我们希望注意两个东西,文件的行数和该行的内容。
注意我们实际上不需要行数,这取决于用户,如果我们一直跟踪行数,然后我们找到了一个 TAB,我们可以告诉用户哪一行是找到的 TAB!
  1.     private void scanConfig() {
  2.         Scanner scan = null;
  3.         try {
  4.             scan = new Scanner(file);
  5.             int row = 0;
  6.             String line = "";
  7.         }
  8.         catch (FileNotFoundException e) {
  9.             // this error should never happen if the file exists
  10.             e.printStackTrace();
  11.         }
  12.     }
复制代码
我们必须读取文件,一行一行的来,一个 while 循环是最方便的方法。当文件有下一行,我们会重新定义一遍临时变量。
  1.     private void scanConfig() {
  2.         Scanner scan = null;
  3.         try {
  4.             scan = new Scanner(file);
  5.             int row = 0;
  6.             String line = "";
  7.             // iterate through the file line by line
  8.             while (scan.hasNextLine()) {
  9.                 line = scan.nextLine();
  10.                 // add to the row
  11.                 row++;
  12.             }
  13.         }
  14.         catch (FileNotFoundException e) {
  15.             // this error should never happen if the file exists
  16.             e.printStackTrace();
  17.         }
  18.     }
复制代码
现在我们在一行一行间迭代,我们检查是否包含 TAB,在 Java 里,TAB 将会转变为正则表达式。一些对正则表达式的基础了解是很有用的,但并不是必须的。TAB 的正则表达式是 '\t'。每次在你的文件中有 TAB,他都将转换为 '\t'。我们不在意 TAB 在哪里,但是确实出现了。我们可以用 String#indexOf() 来检查是否含有 TAB。
  1.     private void scanConfig() {
  2.         Scanner scan = null;
  3.         try {
  4.             scan = new Scanner(file);
  5.             int row = 0;
  6.             String line = "";
  7.             // iterate through the file line by line
  8.             while (scan.hasNextLine()) {
  9.                 line = scan.nextLine();
  10.                 // add to the row
  11.                 row++;
  12.                 // If a tab is found ... \t = tab in regex
  13.                 if (line.indexOf("\t") != -1) {
  14.                     /*
  15.                      * Tell the user where the tab is! We throw an
  16.                      * IllegalArgumentException here. The reason for this will
  17.                      * be explained further down the article.
  18.                      */
  19.                     String error = ("Tab found in config-file on line # " + row + "!");
  20.                     throw new IllegalArgumentException(error);
  21.                 }
  22.             }
  23.         }
  24.         catch (FileNotFoundException e) {
  25.             // this error should never happen if the file exists
  26.             e.printStackTrace();
  27.         }
  28.     }
  29. }
复制代码
因为这个方法处理文件读取,所以我们必须在没有 TAB 后加载文件。我们也需要关闭 Scanner 来防止内存泄露。
注意: 加载操作会抛出 InvalidConfigurationException 和 IOException,你必须正确处理。
  1.     /**
  2.      * Opens a java.util.Scanner to scan the config file for any tabs.
  3.      */
  4.     private void scanConfig() {
  5.         // declare our scanner variable
  6.         Scanner scan = null;
  7.         try {
  8.             scan = new Scanner(file);
  9.             int row = 0;
  10.             String line = "";
  11.             // iterate through the file line by line
  12.             while (scan.hasNextLine()) {
  13.                 line = scan.nextLine();
  14.                 // add to the row
  15.                 row++;
  16.                 // If a tab is found ... \t = tab in regex
  17.                 if (line.indexOf("\t") != -1) {
  18.                     /*
  19.                      * Tell the user where the tab is! We throw an
  20.                      * IllegalArgumentException here.
  21.                      */
  22.                     String error = ("Tab found in config-file on line # " + row + "!");
  23.                     throw new IllegalArgumentException(error);
  24.                 }
  25.             }
  26.             /*
  27.              * load the file, if tabs were found then this will never execute
  28.              * because of IllegalArgumentException
  29.              */
  30.             config.load(file);
  31.         }
  32.         catch (FileNotFoundException e) {
  33.             // this error should never happen if the file exists
  34.             e.printStackTrace();
  35.         }
  36.         catch (IOException e) {
  37.             // failed loading error
  38.             e.printStackTrace();
  39.         }
  40.         catch (InvalidConfigurationException e) {
  41.             // snakeyaml error if the config setup is incorrect.
  42.             e.printStackTrace();
  43.         }
  44.         finally {
  45.             // Close the scanner to avoid memory leaks.
  46.             if (scan != null) {
  47.                 scan.close();
  48.             }
  49.         }
  50.     }
复制代码

覆写重载方法
我们还没完呢!我们需要在插件加载时和插件重载时都要读取配置。我们在插件加载时调用 scanConfig() 方法,在插件重载时调用 reloadConfig() 方法。
  1.     /**
  2.      * Override default implementation for reloading
  3.      */
  4.     @Override
  5.     public void reloadConfig() {
  6.         if (!file.exists()) {
  7.             // Create file if it doesn't exist. Change appropriately based on
  8.             // your setup
  9.             saveDefaultConfig();
  10.         }
  11.         scanConfig();
  12.     }
复制代码

总结
如果你按照这些正确执行后,你现在有了一个 YAML TAB 检测器了!整个的类可以在 GitHub Gist 找到。
提示: 如果你想要一个线程安全的操作,不要使用 Scanner。一个 BufferedReader 将会同步完成任务,但我不会在这里讲。Bukkit-API 是非常不线程安全的,所以没有必要将这个操作异步化。

建立 MongoDB 数据库

查看 https://www.runoob.com/mongodb/mongodb-linux-install.html
其他关于如何在 Java 连接到 MongoDB 的方法请在目录寻找,前面有两篇

Spigot 配置 (server.properties)

generator-settings
默认值: ""
数据类型: String
描述: 让主世界进一步定制生成引擎,例如: SuperFlat

op-permission-level
默认值: 4
数据类型: Integer
描述:设置你的管理员的默认权限的一种方式. 权限大小从低到高为1~4,1级的管理员能够无视主城保护,2级的管理员能够管理普通玩家并维护服务器秩序,他们能够执行诸如/clear, /difficulty, /effect, /gamemode, /gamerule, /give, 和 /tp 这样的命令. 请注意,他们还能够编辑命令方块. 3级的管理员能够管理普通的管理员——他们能够使用/ban, /deop, /kick, 和 /op 这样的命令. 4级的管理员相比于3级更多的权限是能够直接控制服务器——是的,他们能够使用/stop 和 /save-*.

allow-nether
默认值: true
数据类型: Boolean
描述: 是否生成地狱. false意味着地狱将不会生成

level-name
默认值: "world"
数据类型: String
描述: 选择主世界的名称以及其主目录. 对应的地狱或者末地会生成在该子目录下.

enable-query
默认值: false
数据类型: Boolean
描述: 选择是否能够远程查询你的服务器.

allow-flight
默认值: false
数据类型: Boolean
描述: 是否检查飞行. 这里指的不仅仅是非法飞行——所有非OP成员的飞行都会被拦截,无论是通过作弊端亦或是/fly

announce-player-achievements
默认值: true
数据类型: Boolean
描述: 当玩家获得一个新成就时,是否在全屏发送通告.

server-port
默认值: 25565
数据类型: Integer
描述: 选择启动的端口. 玩家输入 IP:端口 以连入服务器.

level-type
默认值: "DEFAULT"
数据类型: String
描述: 选择世界生成器,例如 AMPLIFIED, FLAT, LARGEBIOMES, CUSTOMIZED.

enable-rcon
默认值: false
数据类型: Boolean
描述: 控制服务器是否接受rcon (远程控制台)协议.

force-gamemode
默认值: false
数据类型: Boolean
描述: 玩家在加入游戏时是否重置为默认模式.

level-seed
默认值:
数据类型: String
描述: 选择服务器的种子.

server-ip
默认值:
数据类型: IPv4 Address
描述: 如果服务器同时有多个IP,控制服务器从哪个IP接收玩家. 通常留空.

max-build-height
默认值: 256
数据类型: Integer
描述: 世界之顶,玩家能够建筑的最高高度,填写1~256的整数.

spawn-npcs
默认值: true
数据类型: Boolean
描述: 在村庄里是否生成村民.

hardcore
默认值: false
数据类型: Boolean
描述: 是否开启极限模式(死亡的玩家将被从服务器中禁止).

snooper-enabled
默认值: true
数据类型: Boolean
描述: 选择是否自动将服务器信息发送给Mojang.

online-mode
默认值: true
数据类型: Boolean
描述: 是否开启正版验证.

resource-pack
默认值:
数据类型: URL
描述: 服务端是否使用其他资源包. 如果不使用其他资源包,请留空;否则,请填写资源包下载链接.

pvp
默认值: true
数据类型: Boolean
描述: 控制玩家是否能给他人造成伤害.

difficulty
默认值: 1
数据类型: Integer
描述: 控制游戏难度,从0~3依次是:和平,简单,普通,困难.

enable-command-block
默认值: false
数据类型: Boolean
描述: 服务器中能否籍借命令方块来执行命令.

gamemode
默认值: 0
数据类型: Integer
描述: 服务器的默认模式,从0~2,依次是生存,创造,冒险.

player-idle-timeout
默认值: 0
数据类型: Integer
描述: 当此项为0,则不对挂机玩家做出任何处理;当此项非0,则会踢出该秒数未活动的玩家.(注意,以秒为单位)

max-players
默认值: 20
数据类型: Integer
描述: 控制同一时刻最多能有多少玩家在线.

spawn-monsters
默认值: true
数据类型: Boolean
描述: 是否生成怪物.

generate-structures

默认值: true
数据类型: Boolean
描述: 在世界中是否生成各种结构体,例如沙漠神庙、村庄和地牢.

view-distance (被Spigot覆盖)
默认值: 10
数据类型: Integer
描述: 玩家能够看到多大范围内的生物,取值为(1-15).
#与Spigot.yml比例关系为4:1,这儿4=那儿1

spawn-protection
默认值: 16
数据类型: Integer
描述: 以主城为中心,半径多少的方格将不会被非OP破坏.

motd
默认值: A minecraft server
数据类型: String
描述: 控制显示在服务器列表中的MOTD信息.

by @Smokey_Days

使用 Redis (Jedis)

什么是 Redis?
Redis 是一个键值数据库,基于 HashMap(但也接受其他的数据类型),非常适用于计分板,统计,用户账户等。BuildAPrefix 是我使用 Redis 的一个产物。

我怎样使用 Redis?
很高兴你问了!我会使用 Apache Maven 来管理依赖,但你可以直接将 Jedis 添加到构建路径。GitHub 页面。请注意有些代码是来自我的项目。
添加 Jedis 为一个依赖,很简单,不需要 repo。你还需要添加 Apache Commons Pools 来保证 Jedis 正常运行。
  1. <dependency>
  2.     <groupId>redis.clients</groupId>
  3.     <artifactId>jedis</artifactId>
  4.     <version>2.8.1</version>
  5.     <type>jar</type>
  6.     <scope>compile</scope>
  7. </dependency>
  8. <dependency>
  9.     <groupId>org.apache.commons</groupId>
  10.     <artifactId>commons-pool2</artifactId>
  11.     <version>2.4.2</version>
  12. </dependency>
复制代码
接着把 Jedis 打包到你的 jar 里,这很重要,否则运行时会报错。
  1. <build>
  2.         <plugins>
  3.             <plugin>
  4.                 <groupId>org.apache.maven.plugins</groupId>
  5.                 <artifactId>maven-shade-plugin</artifactId>
  6.                 <version>2.1</version>
  7.                 <executions>
  8.                     <execution>
  9.                         <phase>package</phase>
  10.                         <goals>
  11.                             <goal>shade</goal>
  12.                         </goals>
  13.                         <configuration>
  14.                             <relocations>
  15.                                 <relocation>
  16.                                     <pattern>redis.clients.jedis</pattern>
  17.                                     <shadedPattern>your.package.here.shaded.redis.clients.jedis</shadedPattern>
  18.                                     <pattern>org.apache.commons</pattern>
  19.                                     <shadedPattern>your.package.here.shaded.org.apache.commons</shadedPattern>
  20.                                 </relocation>
  21.                             </relocations>
  22.                         </configuration>
  23.                     </execution>
  24.                 </executions>
  25.             </plugin>
  26.         </plugins>
  27.     </build>
复制代码
现在在 onEnable() 方法初始化 JedisPool,这是一个线程安全的访问 Jedis 的方法。
  1. private JedisPool pool;
  2. @Override
  3. public void onEnable() {
  4.     /*
  5.      * THANKS TO @Tux for the ClassLoader wizardry.
  6.      */
  7.     ClassLoader previous = Thread.currentThread().getContextClassLoader();
  8.     Thread.currentThread().setContextClassLoader(RedisDatabase.class.getClassLoader());
  9.     pool = new JedisPool("ip", 123 /* Port */);
  10.     Thread.currentThread().setContextClassLoader(previous);
  11. }
  12. @Override
  13. public void onDisable() {
  14.     pool.close();
  15. }
复制代码
最后,要用 Jedis 只需要(try-with-resource)
  1. // Try-with-resources will handle calling #close() for us
  2. try (Jedis jedis = pool.getResource()) {
  3.    // If you want to use a password, use
  4.    jedis.auth("some-secure-password");
  5.    jedis.set("key", "value");
  6.    getLogger().info(jedis.get("key"));
  7. }
复制代码

入门向量编程

什么是向量?
向量由长度和方向组成,总是用来描述从一个点到另一个的移动。很多人包括你可能已经听说过向量,或是见到一些用来表示向量的奇怪的箭头。现在让我们看看 Spigot 里的向量。

Spigot 里的向量
Spigot 里的向量可以在 org.bukkit.util 包里找到,可以用以下方法创建:
  1. Vector v = new Vector();  //Creates a vector with length 0 and NO direction
  2. Vector v = new Vector(x, y, z);  //Creates a vector with defined direction and length
复制代码
每个向量都用 3 个值来表示他的方向(X, Y, Z)。有一点你需要知道,你可以使用 length() 方法获得这个向量的长度(速度的大小),使用这些方法来获得/设置每个轴的值 .getX(), .getY(), .getZ(), and .setX(x), .setY(y), .setZ(z)。

Spigot 的向量有什么用?
无论是一个实体的移动,还是你正在看着的目标,计算的时候都需要向量。在这篇教程的结尾,你也可以这样做。
让我们解释一下可能的计算:
* 蓝色的箭头总是计算的结果

向量加法

这个计算很简单,你有两个向量相加,让我们编程实现:
  1. Vector first = new Vector(1, 3, 2);
  2. Vector second = new Vector(3, -1, 4);
  3. // Let's add them together!
  4. Vector result = first.add(second); // Result is now a Vector of 4, 2, 6
复制代码

向量数乘

第二个简单而必要的计算。只需要将向量长度相乘,当乘数是个负数,向量的方向与原来相反。
  1. Vector v = new Vector(3, 4, 2);
  2. Vector result1 = v.multiply(2);  // Vector of 6, 8, 4
  3. Vector result2 = v.multiply(-1); // Vector of -3, -4, -2
复制代码
就像你看到的一样,向量的每一个值都被乘以乘数。

普通化
将向量的长度设置为 1。比如你有一个向量 (3, 3, 3),接着它的长度是 √( (3^2)+(3^2)+(3^2) ) = √(27) = 5.19。
普通化一个向量就是将其长度缩短至 1。所以普通化也就等于使用这个方法 .multiply(1/5.19) ,得到这个向量 (0.57, 0.57, 0.57)。(1/5.19=0.57)
  1. Vector v = new Vector(3, 3, 3);
  2. Vector result = v.normalize();  // Returns vector of length 1 and movement of 0.57, 0.57, 0.57
复制代码

向量积(叉乘)

这个计算返回两个向量正交的向量。长度是浅蓝色的四边形。这个可以用来比较两个向量是否几乎相同(比如当指向玩家时)。
  1. Vector first = new Vector(1, 2, 3);
  2. Vector second = new Vector(-7, 8, 9);
  3. Vector result = first.crossProduct(second); // Will return vector of -6, -30, 22
复制代码


中点
这很简单!两个向量相加,除以二,就有了中点。

你有一个黑色的和橙色的向量,将其相加得到了绿色的,除 2 或者乘 0.5 得到了中点向量。
  1. Vector first = new Vector(1, 3, 4);
  2. Vector second = new Vector(4, 3, 1);
  3. Vector midpoint1 = first.midpoint(second); // Vector of 2.5, 3, 2.5
  4. Vector midpoint2 = first.add(second).multiply(0.5); // Vector of 2.5, 3, 2.5
复制代码
现在你知道怎么使用向量了,是时候来点练习了!

示例
我们来检测玩家是否指向了一个坐标点。
  1. public boolean doesPlayerTarget(Player p, Location target){
  2.         //p is your player
  3.         //target is the location the player might target
  4.         //Check if they are in the same world
  5.         if(!target.getWorld().equals(p.getWorld()))return false;
  6.         //Let's begin!
  7.         //Get the players head location
  8.         Location head = p.getLocation().add(0, p.getEyeHeight(), 0);
  9.         //Get the players looking direction as vector and
  10.         // shorten it to a length of 1 by using normalize()
  11.         Vector look = p.getLocation().getDirection().normalize();
  12.         //Get the direction of the target from the player by substracting his location with the target
  13.         //Again use normalize() of course, to shorten the value
  14.         Vector direction = head.subtract(target).toVector().normalize();
  15.         //Lets build our crossProduct. When the crossProduct's length is 0, the player
  16.         //is exactly targeting our target location
  17.         //why? because then the parallelogram shown above has an area of 0 :)
  18.         Vector cp = direction.crossProduct(look);
  19.         //Lets get the length from the vector
  20.         double length = cp.length();
  21.         //If the length is bigger than 0.1 the player is probably
  22.         //Not targeting our location. Choose this value appropriate
  23.         return (length < 0.1);
  24.     }
复制代码

服务器图标

什么是服务器图标
服务器图标是在用户将服务器添加到服务器列表后,显示在服务器列表中服务器旁边的一张小图片。
服务器图标可能够被通过“Minecraft 服务器列表”网页来作为服务器的标志而下载。
服务器图标能够帮助用户在服务器列表界面滚动时轻松地辨认出服务器。

安装
打开你选择好的图片制作器/编辑器,然后制作一张64x64像素大小的图片,你需要用PNG格式来储存。
文件名需要是 server-icon.png ,目的是文件能够被服务器辨别。
将 server-icon.png 放到与spigot或bungeecord核心.jar文件的同目录下;
在你在服务器根目录放好文件之后你必须重启服务器才能让服务器图标被服务器所识别。

by @1345979462

使用配置文件

配置文件有什么用
在拟开发的时候,你总会用很多的实例来存储数据,比如一个 HashMap 或者 ArrayList,但是当你的服务器重启或者关闭的时候,这些数据会全部丢失,因为这些对象全部存储在虚拟内存里,换句话说,它们消失就像创建它们一样容易,这就是我们使用配置文件的原因。
文件是另一种存储数据的方法,但是他们不会在程序结束后消失,这是因为文件不是存储在虚拟内存的而是实际的存储盘。你可以用此存储玩家的经济、昵称和其他的你想在服务器重启后仍然存在的数据。
让我们开始吧!

使用单个配置文件
创建配置文件:
  1. private void createConfig() {
  2.     try {
  3.         if (!getDataFolder().exists()) {
  4.             getDataFolder().mkdirs();
  5.         }
  6.         File file = new File(getDataFolder(), "config.yml");
  7.         if (!file.exists()) {
  8.             getLogger().info("Config.yml not found, creating!");
  9.             saveDefaultConfig();
  10.         } else {
  11.             getLogger().info("Config.yml found, loading!");
  12.         }
  13.     } catch (Exception e) {
  14.         e.printStackTrace();
  15.     }
  16. }
复制代码

检查是否存在
  1. if (!getDataFolder().exists()) {
  2.     getDataFolder().mkdirs();
  3. }
复制代码
这段代码用来检查你的插件的文件夹是否存在,这是 Bukkit / Spigot 管理插件的方法,给每个插件分配单独的“数据文件夹”,这些文件夹用于存放每个插件单独的配置文件,存储重要的信息。使用 getDataFolder() 方法获得路径后,使用 if 检查这个文件夹是否存在,如果不存在就使用 mkdiirs() 方法(创建这个文件夹)。

声明配置文件
  1. File file = new File(getDataFolder(), "config.yml");
  2. if (!file.exists()) {
  3.     getLogger().info("config.yml not found, creating!");
  4.     saveDefaultConfig();
  5. } else {
  6.     getLogger().info("config.yml found, loading!");
  7. }
复制代码
这是你用来声明配置文件的代码。你需要先创建一个 File 对象,使用参数 'getDataFolder' 和 'config.yml',就像之前提到的,getDamaFolder() 方法返回插件的数据文件夹,将其作为第一个参数,第二个参数告诉构造方法要在这个文件夹中创建一个叫 'config.yml' 的文件,但是这时候这个文件还没有被存储进入物理盘,下一个 if 检查这个配置文件是否存在,如果不存在就调用 saveDefaultConfig() 方法将插件 JAR 中的 'config.yml' 保存在插件文件夹里。
这就是使用配置文件之前必要的所有工作了,但是请记住在 onEnable() 里执行这些代码,保证在其他发生之前加载完成配置文件。

使用配置文件
按照上面的代码之后,我们就可以使用 getConfig() 了。但是如果你要在主类外使用这个方法,你需要使用主类的实例。
现在我们有了配置,就可以从中读写,一个简单的例子:
  1. // Reading from the config
  2. String name = plugin.getConfig().getString("player-name");
  3. // Writing to the config
  4. plugin.getConfig().set("player-name", name);
复制代码
"player-name" 是配置文件中的路径,如果你见过其他的 YAML 配置文件,你就会见到这种格式 "路径: 值"。访问一个路径就需要这个名字,但是有时有 "子路径",标签在其他的标签之下就是这样的情况。
  1. player-name: Steve
  2. player:
  3.   time:
  4.     join: 6:00pm
复制代码
访问子路径你可以使用 '.' 来表示更低的路径级别,比如:
  1. // Reading from the config
  2. String time = plugin.getConfig().getString("player.time.join");
  3. // Writing to the config
  4. plugin.getConfig().set("player.time.join", time);
复制代码
插件会将 '.' 作为 "啊!我应该检查子路径" 的标记。
注意我尽管使用了 String,但是你可以使用其他的类型。Getter 有这些: getInt(), getBoolean(), getList(), 而且还有更多,而 Setter,你只需要填一个 String 作为路径名,第二个 Object 参数为你想要存储的数据。
很重要的一点,当你读写配置文件后,一定要保存。你可以:
  1. plugin.saveConfig();
复制代码
配置文件有各种技巧和工具,正确使用,你就可以将其作为你最有力的财富。

使用多个配置文件
创建文件
第一步,你需要保证你的 File 和 FileConfiguration 对象对其他类可用,这样你才可以读写配置文件,怎样做呢?
在你的主类,创建字段变量,这些变量没有包含在方法里,这样它们可以被外部访问。
  1. public class Main extends JavaPlugin {
  2.     private File configf, specialf;
  3.     private FileConfiguration config, special;
  4.     @Override
  5.     public void onEnable(){
  6.         createFiles();
  7.     }
  8.     public FileConfiguration getSpecialConfig() {
  9.         return this.special;
  10.     }
  11.     private void createFiles() {
  12.         configf = new File(getDataFolder(), "config.yml");
  13.         specialf = new File(getDataFolder(), "special.yml");
  14.         if (!configf.exists()) {
  15.             configf.getParentFile().mkdirs();
  16.             saveResource("config.yml", false);
  17.         }
  18.         if (!specialf.exists()) {
  19.             specialf.getParentFile().mkdirs();
  20.             saveResource("special.yml", false);
  21.          }
  22.         config = new YamlConfiguration();
  23.         special = new YamlConfiguration();
  24.         try {
  25.             config.load(configf);
  26.             special.load(specialf);
  27.         } catch (IOException e) {
  28.             e.printStackTrace();
  29.         }
  30.     }
  31. }
复制代码
这干了什么呢?这是一个基本的主类的示例,还有就是它创建了两个配置文件,"config.yml" 和 "special.yml"。当插件启用时,调用 createFiles() 方法,这个方法检查每个文件是否存在,如果不存在就创建,这一部分在上面的使用单个配置文件部分有更详细的解释。
那么 saveResource(String, boolean) 部分呢?你可以在你的 JAR 文件里储存其他的东西,比如 YML 文件,而不只是你的类。决定与你的 IDE,你可以在 src 文件夹创建一个新的文件夹(名字取决于编辑器),然后你可以在主类调用 saveResource("JAR 里文件的名字", 是否替换已经出现的文件)。这会在插件的数据文件夹里存储这个文件。现在你已经创建了多个配置文件了!

读写多个文件
我们可以这样:
  1. plugin.getSpecialConfig().getString("路径");
复制代码
基本上你可以使用 getConfig(),像使用一个配置一样,如果你想查看更多关于操作和访问配置的说明,阅读这篇文章。至于保存,你需要使用 FileConfiguration#save(File) (就像 saveConfig() 对 config.yml 的效果) 来将数据写入硬盘。

分析崩溃

世界相关问题
栈溢出
发生这个问题的错误报告通常以 “java.lang.StackOverflowError” 开头,在Minecraft默认的崩溃日志文件中看的很清楚。
这样的字符会重复出现多次。

区块损坏
错误报告信息以
  1. “java.io.UTFDataFormatException: malformed input around byte 111”
  2. ”java.io.EOFException: Unexpected end of ZLIB input stream” 开头。
复制代码
在Minecraft崩溃日志中你很容易就能找到这样的字符,在这开头的报错信息之后请注意后面的服务器信息,以
Section, BlockLight 或是 Blocks 这种标志表示。

常见问题
在Minecraft 1.7中当前有两个较为常见的漏洞。
它们是
  1. " java.lang.IllegalArgumentException: n must be positive "
复制代码
  1. “java.lang.RuntimeException: Already decorating!!”
复制代码
现在这两个已知Bug目前还没有修复方法。


会话锁损坏
在服务器运行时 session.lock 文件或世界相关文件被损坏,删除或是强制更改后出现的报错。
  1. “java.lang.RuntimeException: Failed to check session lock, aborting”,
  2. “java.io.FileNotFoundException: .\world\session.lock (The requested operation cannot be performed on a file with a user-mapped section open)”
复制代码
这些都能够在server.log和Minecraft客户端的崩溃日志中找到。
修复方法是关闭服务器后,删除你的世界目录下的 session.lock 文件,然后再开启服务器。


读取插件出现错误
一般崩溃原因
每次你服务器崩溃时(不是JVM出现问题),Spigot都会储存追踪记录。
90%的时间它都不会生成默认的Minecraft崩溃日志。
大多数时间你都可以在 “Server thread” 这里找到使服务器崩溃的插件。
大多数时间,这个问题的解决方法是更新插件,或是移除等待作者更新修复问题。
在你找到了服务器主线程之后,仔细阅读这里的代码,这些是与错误无关的内容:
  • Java
  • Apache
  • Sun
  • Netty
  • Spigot
  • Net.minecraft.server
  • Bukkit
  • Google
  • Yaml/SnakeYaml
如果你找到一行字没有上面的内容的话,你就可能找到了制造问题的插件,最简单的解释方式就是用例子——
这是服务器崩溃的主线程记录,尝试找出是什么让服务器崩溃:

如果你看到了像这样的错误信息的话:
  1. [WARNING] [Server] The main thread failed to respond after 10 seconds
  2. [WARNING] [Server] Probable Plugin causes: 'iConomy, Vault and ChestShop'
复制代码
这是明确的告诉你 iConomy, Vault 或是 ChestShop 是报错的原因。
当然目前不是所有的崩溃都是插件引起,有些时候是因为你或玩家的不当操作。
例如,这个崩溃日志的原因就是大型WorldEdit操作所造成的(使用//set命令,详见第十行),它让你的服务器达到了连接超时上限。

如果你看到了类似这样的报错信息的话
  1. [SEVERE] Could not pass event X to PluginY
  2. org.bukkit.event.EventException
复制代码
PluginY 就是报错的原因,如果插件目前还没有修复这个问题的版本的话,请联系插件的开发者。
Metrics和版本检测也可能会让你的服务器崩溃。
就像在这个崩溃日志中所显示的—— 一些插件会尝试连接外服,在18和19行我们可以看到崩溃是 ModReq 造成的,最有可能的原因就是连接超时。
要想修复这个的话,请检查插件的配置文件,关闭自动更新系统。
有关Metrics崩溃的话,前往spigot安装目录/Plugins/PluginMetrics/ 然后将统计在配置中关闭。
下面就是metrics崩溃的例子。


Spigot/Bukkit 相关问题
文件权限错误
因为文件权限问题,Spigot 可能不能生成一些核心的文件,例如日志文件,配置文件和 metrics 文件夹。
在linux上这个问题的解决方法很简单,前往你的jar所在路径然后把jar的文件权限设置为700,其他的文件设置为500就好了。
请确保文件的拥有者权限正确。
这个问题的典型报错:


内存空间相关
在1.7的更新之后,Mojang为Minecraft增加了更多的资源文件,这导致了jar文件中类的数量显著增加。
结果就是,运行服务器所需要的PermGen空间就变多了。
你可以在下面的 设置PermGen大小 小节查看怎么修复这个问题。

其他问题
作弊修改的实体或物品
因为创造模式拥有无限的权利,使用作弊端的玩家能生成使你的服务器崩溃的物品。
最容易看到的就是 “java.lang.IllegalArgumentException: n must be positive”,这是因为实体或玩家的物品或装备的附魔等级是个负数。
每次玩家加入游戏或是进入这片区域的时候,服务器就会崩溃。
尝试将玩家或实体的物品栏清空是解决这个崩溃的方法。

玩家也能够生成无限长名字的生物,这会让生物附近玩家的客户端崩溃,原因是包过长。
解决方法只有移除这个生物。

MultiCraft 会缩减日志文件
Multicraft 其中一个正在开发的特性就是它会在崩溃时缩短你的日志文件。
如果你看到
  1. [Multicraft] Skipped x lines due to rate limit (y/s)
复制代码
这说明你可能无法成功地分析错误报告了。将限制的值设置为一个尤其大的数能够阻止这个特性缩减你的日志文件——
如果你没有权限的话尝试联系你的服务器提供商。

by @1345979462

更改PermGen大小

重要:Java 8对 PermGen 的支持被移除了。
背景
Mojang在1.7的更新中为Minecraft服务器增加了一些资源文件,这导致了核心文件中类的增加,以及需要使用更多的PermGen空间。
为了让你的服务器运行状态达到最佳并预防崩溃,我们推荐你在JVM中增加PermGen的配置来预防这些问题。
因为动态配置 metaspace 这一新特性的发明,在Java 8中对PermGen的支持被移除了。
结果是你就不需要再注意这一项了,也不要陷入任何相关问题之中,除非你耗尽了堆内存。

方法
首先,用你喜欢的文本编辑器打开你的运行命令行或运行文件。
运行命令看起来应该像这样:
  1. <path to java executable // java> -Xms1G -Xmx2G -<extra flags> -jar spigot.jar nogui
复制代码
要想改变你的PermGen设置,你需要在这个命令中加入 -XX:MaxPermSize=128M 参数。
你需要把它放在java执行命令后,-jar参数之前。举个例子:
  1. <path to java executable // java> -Xms1G -Xmx2G -XX:MaxPermSize=128M -<extra flags> -jar spigot.jar nogui
复制代码
在你关闭服务器并使用这个新的命令重启服务器之后,你就成功配置了更多的PermGen空间,最终你就能运行更多插件了。

其它配置设置
  • 如果你使用 McMyAdmin 的话,将 -XX:MaxPermSize=128M 放在 McMyAdmin.conf 文件里的 Java.CustomOpts 设置项中
  • 如果你使用 mark2 的话,将 java.cli.XX.MaxPermSize=128M 放在 mark2.properties 中
  • 如果你使用 Multicraft 的话,请去阅读适合Spigot的配置文档
  • 如果你使用 Rtoolkit 的话,打开工具包文件夹下的 "wrapper.properties" 文件。然后在 "extra-runtime-arguments" 设置项后加入 ",-XX:MaxPermSize=128M",重启服务器来应用设置。

by @1345979462

减少服务器卡顿

大家好 !我要制作本页面的原因是我找不到其它减少延迟相关的Wiki页面。
现在,这将会是最棒的基础教程。但我会将它做的最好的。欢迎大家来改善它!
这个教程需要参考你服务器根目录下的 spigot.yml 文件。
如果你使用Windows系统的话推荐你使用 Notepad++ 应用打开文件。
  1.     anti-xray:
  2.       enabled: true
复制代码
现在,根据你服务器的模式你可以将这一样启用或关闭。
事实是,玩家很喜欢作弊。如果玩家发现这个服务器无法作弊的话,他们可能会去下个服务器的。
这个选项的启用与否可以完全取决于你,但我推荐你设置为:
  1.     anti-xray:
  2.       enabled: false
复制代码
  1.     merge-radius:
  2.       item: 2.5
  3.       exp: 3.0
复制代码
这些选项指定物品合并所需要的距离,你可以将其设置为:
  1.     merge-radius:
  2.       item: 4
  3.       exp: 6
复制代码
更多内容很快就到!请关注本页面!
by @1345979462

分离崩溃

如果你在服务器启动时出现问题的话,这是能够帮助你排查问题并找到原因的步骤。

1. 获取最新的Spigot构建
通常这都能解决问题。使用不同版本的构建,试试早些或晚些的构建能不能解决问题。没用?请看第二步。

2. 移除世界运行
在读取插件之前,一些错误的世界也可能会让你的服务器崩溃。尝试不装载世界开启服务器来查看问题能否解决。
你可以将你的世界移出你的服务器目录。
如果这有用的话,说明你服务器的世界之一出错了。
将世界一个一个放回服务器来查找出现问题的世界。
找到出现问题的世界以后,如果你可以删除的话就请删除,如果不能请运行区块修复工具。
没有解决?看第三步。

3. 移除插件运行
关闭服务器,尝试删除或移走你的插件目录。如果问题解决的话说明是崩溃是插件引起,然后请去3a。

3a. 更新所有插件
看起来这样做工作量会很大,但是让你所有的插件都保持最新确实是个很好的习惯。
问题仍存在?看3b。

3b. 排查错误插件
现在你已经知道是插件造成的问题,所以就是你梳理插件目录找到问题插件的时候了。请仅仅安装你一半的插件(把另一半放到别的地方),然后开启服务器。
如果问题已经解决,那么说明问题插件在你拿出来的那一半中。
如果问题仍然存在,那么说明问题插件还在你留下来的那一半中。
请重复这种操作直到你找到单个问题插件。
请确定就是它造成的问题。

如果这些都没有用的话,请将你的崩溃日志发送到Spigot论坛的Bug板块中。
请确保使用 pastebin.com 将你所有的控制台错误发送,并且请给出足够多的信息。
by @1345979462

使用 WorldEdit API

如何使用 WorldEdit API
将以下代码放入 pom.xml 的 repositories里
  1. <repository>
  2. <id>sk89q-snapshots</id>
  3. <url>http://maven.sk89q.com/artifactory/repo</url>
  4. <releases>
  5. <enabled>true</enabled>
  6. </releases>
  7. <snapshots>
  8. <enabled>true</enabled>
  9. </snapshots>
  10. </repository>
复制代码
接着把这部分代码放入 dependencies 里
  1. <dependency>
  2. <groupId>com.sk89q.worldedit</groupId>
  3. <artifactId>worldedit-bukkit</artifactId>
  4. <version>6.1.1-SNAPSHOT</version>
  5. </dependency>
复制代码

等一小会儿,然后你的 Maven Dependencies 里多了一大堆东西,点击 worldedit-bukkit-6.1.1-SNAPSHOT.jar 按下 Command-I,前往 Javadoc Location 部分,粘贴 http://docs.sk89q.com/worldedit/apidocs/,点击 Validate,然后点击 OK (或者按下 return) 然后 WorldEdit 应该设置完成了。

WorldEdit 的包名应该是 com.sk89q.worldedit

配置管理器

大家好,很多人都还在创建默认的 config.yml 文件,但是我个人不是很喜欢。。。只有一个文件,所以这就是我制作这篇 SettingsManager 教程的原因:

什么是 SettingsManager:
SettingsManager 允许你创建自己的文件,自己的文件夹,比如 "/plugins/PluginName/CustomFolder/CustomFile.yml"。

如何制作 SettingsManager:
我将展示我使用的 SettingManager:
  1. package YourPackage;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import org.bukkit.Bukkit;
  5. import org.bukkit.ChatColor;
  6. import org.bukkit.configuration.file.FileConfiguration;
  7. import org.bukkit.configuration.file.YamlConfiguration;
  8. public class YourClass {
  9.     static FileConfiguration YourFile;
  10.     static File pdfile;
  11.     public static void setup() {
  12.         pdfile = new File("plugins/YourPlugin/", "YourFile.yml");
  13.         if (!pdfile.exists()) {
  14.             try {
  15.                 pdfile.createNewFile();
  16.             } catch (IOException e) {
  17.             }
  18.         }
  19.         YourFile = YamlConfiguration.loadConfiguration(pdfile);
  20.     }
  21.     public static FileConfiguration get() {
  22.         return YourFile;
  23.     }
  24.     public static void save() {
  25.         try {
  26.             YourFile.save(pdfile);
  27.         } catch (IOException e) {
  28.             Bukkit.getServer().getLogger().severe(ChatColor.RED + "Could not save yourfile.yml!");
  29.         }
  30.     }
  31.     public static void reload() {
  32.         YourFile = YamlConfiguration.loadConfiguration(pdfile);
  33.     }
  34. }
复制代码
如果你想创建/重载/保存/获取一个文件,你需要在 onEnable() 里这样做:
  1. YourClass.setup();
  2. YourClass.get().addDefault("Default", "Default");
  3. YourClass.get().options().copyDefaults(true);
  4. YourClass.save();
复制代码
这就是创建你自己的设置管理器的方法,不难吧?
感谢阅读,祝你和你的自定义配置好运!

Spigot 1.11 插件列表


原文

Spigot 1.12 插件列表


原文

Spigot 1.12 插件列表


原文

Spigot 变更日志

Spigot
CraftBukkit
Bukkit

Spigot 命令 & 权限

/restart
权限节点: bukkit.command.restart
这个命令会尝试让服务器重启,你必须在 spigot.yml 里正确填写 "restart-script"
默认: 管理员

/tps
权限节点: bukkit.command.tps
这个命令将会显示TPS(每秒 tick 数)的最近的 1分钟、5分钟、15分钟的平均值
默认: 管理员

/timings on (在 builds #1261-#1537 间被停用)
权限节点: bukkit.command.timings
This command will turn on server benchmark timings, without requiring a restart.
默认: 管理员

/timings off (在 builds #1319-#1537 间被停用)
权限节点: bukkit.command.timings
这个命令会关闭服务器跑分 timings,不需要重启
默认: 管理员

/timings merged
权限节点: bukkit.command.timings
这个命令将会把 timings 写入磁盘,名为 timnigsX.txt 的文本文件将存储在 /timings 目录下。
默认: 管理员

/timings separate
权限节点: bukkit.command.timings
这个命令将会为活跃的插件产生 timings 报告,但是会分为多个单独的文件。
默认: 管理员

/timings paste
权限节点: bukkit.command.timings
不像是写入文件,这将把结果粘贴进 Ubuntu Paste,可以在 Aikar's Timings Viewer 查看。
默认: 管理员

/timings reset
权限节点: bukkit.command.timings
这个命令将会重置之前的 timings 结果。
默认: 管理员

Spigot Maven

Maven Repository
Spigot 有 maven repository
https://hub.spigotmc.org/nexus/# ... shots~browsestorage

pom.xml
这是一份从Spigot仓库获得API的示例 pom.xml。
你应该只用其中的一个
如果你需要使用SpigotAPI,那么你只需要填写 SpigotAPI
将以下代码放入 pom.xml
  1. <repositories>
  2.     <repository>
  3.         <id>spigot-repo</id>
  4.         <url>[url]https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>[/url]
  5.     </repository>
  6. </repositories>
  7. <dependencies>
  8.     <!--Spigot API-->
  9.     <dependency>
  10.         <groupId>org.spigotmc</groupId>
  11.         <artifactId>spigot-api</artifactId>
  12.         <version>1.12-R0.1-SNAPSHOT</version>
  13.         <scope>provided</scope>
  14.     </dependency>
  15.     <!--Bukkit API-->
  16.     <dependency>
  17.         <groupId>org.bukkit</groupId>
  18.         <artifactId>bukkit</artifactId>
  19.         <version>1.12-R0.1-SNAPSHOT</version>
  20.         <scope>provided</scope>
  21.     </dependency>
  22. </dependencies>
复制代码
如果你使用 NMS,你需要将 CraftBukkit 也加入 dependency
  1. <dependency>
  2.     <groupId>org.bukkit</groupId>
  3.     <artifactId>craftbukkit</artifactId>
  4.     <version>1.12-R0.1-SNAPSHOT</version>
  5.     <scope>provided</scope>
  6. </dependency>
复制代码
你需要运行 BuildTools 来确保将其加入你的依赖项
如果你需要 Spigot 版本包含的 NMS,你需要这个
  1. <dependency> <!-- Spigot (this includes Spigot API, Bukkit API, Craftbukkit and NMS) -->
  2.     <groupId>org.spigotmc</groupId>
  3.     <artifactId>spigot</artifactId>
  4.     <version>1.12-R0.1-SNAPSHOT</version>
  5.     <scope>provided</scope>
  6. </dependency>
复制代码
你需要使用 BuildTools

[groupid=1330]PluginsCDTribe[/groupid]