請認准本教程目錄篇永久鏈接:http://www.cnblogs.com/xtypr/p/nukkit_plugin_start_from_0.html ,未經作者許可轉載本系列文章任何內容的,視為已向作者支付50元人民幣稿酬。作者支付寶賬號:237356812@qq.com。
系列作者:粉鞋大媽 文章出處:http://www.cnblogs.com/xtypr 歡迎轉載、翻譯、收錄,也請保留這段聲明,不勝感激。
詳解YAML與Nukkit配置文件
目錄:Nukkit插件從0開始
這篇文章我將從YAML開始,詳細解析nukkit的各類配置文件,包括plugin.yml、server.properties、nukkit.yml。
YAML與在Java中的實現
YAML是一種數據存儲格式,與XML以及其它格式不同,YAML更容易被人類和機器閱讀。YAML類似於XML和JSON,但是語法比后兩者簡單很多。關於YAML的優缺點可以參考:
- 官網 http://www.yaml.org/
- IBM的文章 http://www.ibm.com/developerworks/cn/xml/x-cn-yamlintro/
- 從360doc找到的文章 http://www.360doc.com/content/15/0228/17/12090552_451540006.shtml。
- 從標點符找到的文章 http://www.biaodianfu.com/yaml.html
YAML較多的用於配置文件中儲存一系列常量,此類配置文件多以.yml或者.yaml為擴展名。YAML使用可打印的Unicode字符,可使用UTF-8或UTF-16。
下面簡單講一講它的語法。
注解與檔案
在YAML中,常常要給一個變量或者整個檔案做注解。可以在YAML文檔的任意一行的末尾或者一個空行中,使用井號#加上注釋的文字來加以注解。這些注解會被編譯器忽略,但是讓人類更容易理解這篇YAML文檔。和一些編程語言不同,YAML不支持多行注釋,你需要在每一行之前加上井號。這有一個例子:
#這是一個單行注解
#又來一個單行注解
var: 233 #這是一行之后的注解
一篇正常的YAML文檔,可以分為幾個檔案。一篇檔案由三個短橫---開始,三個句點...結束,中間包圍着YAML變量。一般的配置文件只有一個檔案,所以檔案開始和結束符號常常省略。
基本變量類型
YAML中的字符串常量有許多表達方式。雖然YAML字符串不常使用引號,但是仍然可以用單引號''或雙引號""包圍來表達一個字符串常量。YAML還有用|或者>開頭的獨特字符串形式。
- 最簡單的形式是不使用任何符號包圍,這時YAML會保留所有原生的字符串。URL字符串因為常有'"/:這樣的字符,用其它格式不適合保存,最適合這種保存方式。如果你保存的字符串是類似於1.0.0的版本號,不用符號包圍時可能會被誤認為是數字而影響讀取,這時候應該使用其它的保存方法。
- 在用單引號包圍的字符串中,表達一個單引號必須用另一個單引號來轉義。比如這個:
'這是一個單引號→ '' ←不是兩個單引號'
- 而使用雙引號包圍字符串時,不同於單引號包圍或者不用引號,YAML允許你在雙引號包圍字符串時使用轉義字符反斜杠\來表達類似於Java的轉義字符的豐富的形式。比如這些:
"雙引號可以使用 \\表示一個反斜杠 \"表示一個雙引號 \'表示一個單引號 \r \n 表示換行符 "
- |可以用來表達多行字符串。這時候YAML會保留換行符。這種情況下,\|這樣的轉義字符不會被轉義。比如這個:
| 大家好,我不是girlbook
這篇文章的出處:http://www.cnblogs.com/xtypr/
很高興你來翻閱粉鞋大媽的文章 - >也可以用來表達多行字符串,但是YAML在讀取時會刪除換行符,將每一行連在一起。比如這個:
> 蛋吸吃掉了第一行的換行符 tny吃掉了第二行的換行符 Norman發現第三行末尾沒換行符
蛋吸吃掉了第一行的換行符tny吃掉了第二行的換行符Norman發現第三行末尾沒換行符
YAML允許你用直接輸入數字的方式來保存一個數字,並且會自動識別是整數還是浮點小數。YAML支持以下的數字表達方式:
- 直接輸入整數表示十進制整數,例如:233
- 用“0”開頭的一串0~7數字表示八進制整數,例如: 0351
- 用“0x”開頭的一串0~9或A~F字符表示十六進制整數,例如:0xE9
- 直接輸入小數表示浮點數,例如:233.333
- 類似於科學計數法的指數,例如:2.33e+33,2e-33
另外,關於布爾值,YAML中使用true或false來表示。空的值可以使用null或~來表示。
序列與散列
YAML允許你使用序列(數組)或者散列表(哈希表)來表示一系列變量。在Java中,這兩種變量會被轉化為List對象和Map對象。YAML允許你使用一個短橫加空格加變量的值來表示一個序列的一個值,使用鍵值加冒號加空格加值來表示一個數組的一組對應關系。請看以下的例子:
- 序列的第一個值
- 序列的第二個值
- 序列的第三個值
var1: 散列中var1的值
var2: 這是var2的值
當你認為換行太占空間時,YAML允許你把序列簡化成[]包圍的形式,散列表簡化為{}包圍的形式。空的[]或{}表示一個空的序列或散列。比如這個:
[序列的第一個值,序列的第二個值,序列的第三個值]
[] #這是一個空的序列
{var1: 散列中var1的值,var2: 這是var2的值}
{} #這是一個空的散列
一個序列/散列的值可以是一個基本變量類型,也可以是另一個序列/散列。當一個序列/散列包含另外的序列的時候,應該使用一個空格的縮進(不是tab鍵)來說明一個序列/散列屬於這個序列/散列。如果使用|或>引導的字符串,你不需要在每行前面加上很多空格來保持縮進。所以這樣的片段在YAML中是合法的:

room1: #需要保持縮進
contents: {bed: 1} #這種方式可以表達散列
lighted: false
description: > #下兩行不需要對齊空格
This is a room
with a large bed.
comments: #散列和散列嵌套的形式
girlbook: ok
marcus: awesome
size: #散列和序列嵌套的形式
- 6
- 5.5
- 5
users: #序列和散列嵌套的形式
- name: xtypr
age: 17
- name: miss_orange
age: 18
room2: false
由於一個YAML檔案應該包含一個散列,所以一個YAML文檔的結構應該是這樣的層次:
變量/序列/散列 -> 檔案 -> 文檔
至此,我們已經可以分析一般的YAML文檔和結構。
Nukkit插件與plugin.yml
Nukkit通過插件的plugin.yml來識別插件的主類、作者、版本等信息。plugin.yml在Nukkit加載插件時會被識別為一個PluginDescription類。我們看看PluginDescription類需要讀取哪些信息:

package cn.nukkit.plugin; /* 此處省略若干行 */ /** * Author: iNevet & MagicDroidX * Nukkit Project */ public class PluginDescription { /* 此處省略若干行*/ private void loadMap(Map<String, Object> plugin) throws PluginException { this.name = ((String) plugin.get("name")).replaceAll("[^A-Za-z0-9 _.-]", ""); if (this.name.equals("")) { throw new PluginException("Invalid PluginDescription name"); } this.name = this.name.replace(" ", "_"); this.version = (String) plugin.get("version"); this.main = (String) plugin.get("main"); Object api = plugin.get("api"); if (api instanceof List) { this.api = (List<String>) api; } else { List<String> list = new ArrayList<>(); list.add((String) api); this.api = list; } if (this.main.startsWith("cn.nukkit.")) { throw new PluginException("Invalid PluginDescription main, cannot start within the cn.nukkit. package"); } if (plugin.containsKey("commands") && plugin.get("commands") instanceof Map) { this.commands = (Map<String, Object>) plugin.get("commands"); } if (plugin.containsKey("depend")) { this.depend = (List<String>) plugin.get("depend"); } if (plugin.containsKey("softdepend")) { this.softDepend = (List<String>) plugin.get("softdepend"); } if (plugin.containsKey("loadbefore")) { this.loadBefore = (List<String>) plugin.get("loadbefore"); } if (plugin.containsKey("website")) { this.website = (String) plugin.get("website"); } if (plugin.containsKey("description")) { this.description = (String) plugin.get("description"); } if (plugin.containsKey("prefix")) { this.prefix = (String) plugin.get("prefix"); } if (plugin.containsKey("load")) { String order = (String) plugin.get("load"); try { this.order = PluginLoadOrder.valueOf(order); } catch (Exception e) { throw new PluginException("Invalid PluginDescription load"); } } if (plugin.containsKey("author")) { this.authors.add((String) plugin.get("author")); } if (plugin.containsKey("authors")) { this.authors.addAll((Collection<? extends String>) plugin.get("authors")); } if (plugin.containsKey("permissions")) { this.permissions = Permission.loadPermissions((Map<String, Object>) plugin.get("permissions")); } } public String getFullName() { return this.name + " v" + this.version; } public List<String> getCompatibleApis() { return api; } public List<String> getAuthors() { return authors; } public String getPrefix() { return prefix; } public Map<String, Object> getCommands() { return commands; } public List<String> getDepend() { return depend; } public String getDescription() { return description; } public List<String> getLoadBefore() { return loadBefore; } public String getMain() { return main; } public String getName() { return name; } public PluginLoadOrder getOrder() { return order; } public List<Permission> getPermissions() { return permissions; } public List<String> getSoftDepend() { return softDepend; } public String getVersion() { return version; } public String getWebsite() { return website; } }
通過分析以上的代碼,我們知道了Nukkit需要讀取插件以下的字段:
- name
- version
- api
- main
- author/authors
- website
- description
- depend
- softdepend
- loadbefore
- prefix
- load
- commands
- permissions
要能夠編寫符合要求的plugin.yml,我們需要從代碼中分析以上字段的功能。比如我們要分析api字段的功能,我們發現在PluginDescription類中有getCompatibleApis()這個方法使用了api序列,進一步在整個Nukkit中尋找使用這個方法的代碼,我們在PluginManager類里發現了這樣的代碼段:

boolean compatible = false; for (String version : description.getCompatibleApis()) { //Check the format: majorVersion.minorVersion.patch if (!Pattern.matches("[0-9]\\.[0-9]\\.[0-9]", version)) { this.server.getLogger().error(this.server.getLanguage().translateString("nukkit.plugin.loadError", new String[]{name, "Wrong API format"})); continue; } String[] versionArray = version.split("\\."); String[] apiVersion = this.server.getApiVersion().split("\\."); //Completely different API version if (!Objects.equals(Integer.valueOf(versionArray[0]), Integer.valueOf(apiVersion[0]))) { continue; } //If the plugin requires new API features, being backwards compatible if (Integer.valueOf(versionArray[1]) > Integer.valueOf(apiVersion[1])) { continue; } compatible = true; break; } if (!compatible) { this.server.getLogger().error(this.server.getLanguage().translateString("nukkit.plugin.loadError", new String[]{name, "%nukkit.plugin.incompatibleAPI"})); }
這段代碼屬於插件管理器,通過getCompatibleApis()方法得到適合的api列表,然后根據與自己的api版本進行對比,如發現可以兼容,就加載這個插件,否則輸出錯誤。至此,我們已經推導出了api字段的作用:用來表示這個插件支持的Nukkit api版本列表。
同理,我們可以得到plugin.yml中所有字段的用途:
- name(字符串),必需,表示這個插件的名字,名字是區分不同插件的標准之一。插件的名字不能包含“nukkit”“minecraft”“mojang”這幾個字符串,而且不應該包含空格。
- version(字符串),必需,表示這個插件的版本號。使用類似於1.0.0這樣的版本號時,應該使用引號包圍來防止誤識別。
- api(字符串序列),必需,表示這個插件支持的Nukkit api版本號列表。插件作者應該調試能支持的api,然后把版本號添加到這個列表。
- main(字符串),必需,表示這個插件的主類。插件的主類不能放在“cn.nukkit”包下。
- author(字符串)/authors(字符串序列),兩個任選一個,表示這個插件的作者/作者列表。
- website(字符串),表示這個插件的網站,插件使用者或者開發者可以訪問這個網站來獲取插件更多的信息,可以是插件發布帖子或者插件官網之類的。
- description(字符串),表示這個插件的一些描述。使用 /version 插件名 查看插件的版本時,會顯示這個描述。
- depend(字符串序列),表示這個插件依賴的插件列表。如果依賴的插件沒有安裝,Nukkit會提示用戶去安裝。
- softdepend
- loadbefore
- prefix(字符串),表示這個插件的消息頭銜,如果不填寫表示這個插件消息頭銜為插件的名字。在Logger輸出日志信息的時候,會用到插件的消息頭銜。
- load
- commands
- permissions
Nukkit與nukkit.yml
Nukkit與server.properties
目錄:Nukkit插件從0開始