>
+ ) : EventNormal() {
+
+ init {
+ async(!Bukkit.isPrimaryThread())
+ }
+
+ fun addName(key: String, value: Any) {
+ name[key] = value.toString()
+ }
+
+ fun addLore(key: String, value: Any) {
+ val list = lore.computeIfAbsent(key) { arrayListOf() } as ArrayList
+ list.add(value.toString())
+ }
+
+ fun addLore(key: String, value: List) {
+ value.forEach { addLore(key, it) }
+ }
+ }
+
+ class Post(
+ val player: Player?,
+ val itemStream: ItemStream,
+ val name: Map,
+ val lore: Map>
+ ) : EventNormal() {
+
+ init {
+ async(!Bukkit.isPrimaryThread())
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ink/ptms/zaphkiel/api/event/ItemRebuildEvent.kt b/src/main/java/ink/ptms/zaphkiel/api/event/ItemRebuildEvent.kt
new file mode 100644
index 0000000..92d4dfa
--- /dev/null
+++ b/src/main/java/ink/ptms/zaphkiel/api/event/ItemRebuildEvent.kt
@@ -0,0 +1,19 @@
+package ink.ptms.zaphkiel.api.event
+
+import ink.ptms.zaphkiel.api.Item
+import ink.ptms.zaphkiel.api.ItemStream
+import io.izzel.taboolib.module.event.EventCancellable
+import org.bukkit.Bukkit
+import org.bukkit.entity.Player
+
+/**
+ * @Author sky
+ * @Since 2019-12-16 10:46
+ */
+class ItemRebuildEvent(val player: Player?, val itemStream: ItemStream, refresh: Boolean) : EventCancellable() {
+
+ init {
+ isCancelled = !refresh
+ async(!Bukkit.isPrimaryThread())
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ink/ptms/zaphkiel/api/internal/ItemKey.kt b/src/main/java/ink/ptms/zaphkiel/api/internal/ItemKey.kt
new file mode 100644
index 0000000..9572b0f
--- /dev/null
+++ b/src/main/java/ink/ptms/zaphkiel/api/internal/ItemKey.kt
@@ -0,0 +1,10 @@
+package ink.ptms.zaphkiel.api.internal
+
+/**
+ * @Author sky
+ * @Since 2019-12-16 12:44
+ */
+enum class ItemKey(val key: String) {
+
+ ID("a"), HASH("b"), DATA("c")
+}
\ No newline at end of file
diff --git a/src/main/java/ink/ptms/zaphkiel/api/internal/ItemList.kt b/src/main/java/ink/ptms/zaphkiel/api/internal/ItemList.kt
new file mode 100644
index 0000000..e3af2b0
--- /dev/null
+++ b/src/main/java/ink/ptms/zaphkiel/api/internal/ItemList.kt
@@ -0,0 +1,103 @@
+package ink.ptms.zaphkiel.api.internal
+
+import com.google.common.collect.Lists
+import com.google.common.collect.Maps
+import ink.ptms.zaphkiel.ZaphkielAPI
+import ink.ptms.zaphkiel.api.Item
+import io.izzel.taboolib.module.inject.TListener
+import io.izzel.taboolib.module.lite.SimpleIterator
+import io.izzel.taboolib.util.item.ItemBuilder
+import io.izzel.taboolib.util.item.Items
+import org.bukkit.Bukkit
+import org.bukkit.Material
+import org.bukkit.entity.HumanEntity
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.inventory.InventoryClickEvent
+import org.bukkit.inventory.Inventory
+import org.bukkit.inventory.InventoryHolder
+import org.bukkit.inventory.ItemStack
+import org.bukkit.inventory.meta.ItemMeta
+import java.util.function.Consumer
+
+/**
+ * @Author 坏黑
+ * @Since 2019-01-03 20:25
+ */
+@TListener(cancel = "cancel")
+class ItemList : Listener {
+
+ fun cancel() {
+ Bukkit.getOnlinePlayers().stream().filter { player -> player.openInventory.topInventory.holder is ItemListHolder }.forEach { it.closeInventory() }
+ }
+
+ @EventHandler
+ fun onClick(e: InventoryClickEvent) {
+ if (e.inventory.holder is ItemListHolder) {
+ e.isCancelled = true
+ // 空物品
+ if (Items.isNull(e.currentItem)) {
+ return
+ }
+ // 点击物品
+ if ((e.inventory.holder as ItemListHolder).items.containsKey(e.rawSlot)) {
+ val item = ZaphkielAPI.registeredItem[(e.inventory.holder as ItemListHolder).items[e.rawSlot]]
+ if (item != null) {
+ val itemStack = item.build(e.whoClicked as Player).save()
+ itemStack.amount = if (e.click.isShiftClick) itemStack.type.maxStackSize else 1
+ e.whoClicked.inventory.addItem(itemStack)
+ }
+ }
+ // 上一页
+ if (e.rawSlot == 47) {
+ open(e.whoClicked as Player, (e.inventory.holder as ItemListHolder).page - 1)
+ }
+ // 下一页
+ if (e.rawSlot == 51) {
+ open(e.whoClicked as Player, (e.inventory.holder as ItemListHolder).page + 1)
+ }
+ }
+ }
+
+ class ItemListHolder(val page: Int) : InventoryHolder {
+ val items = Maps.newHashMap()!!
+
+ override fun getInventory(): Inventory {
+ return Bukkit.createInventory(null, 0)
+ }
+ }
+
+ companion object {
+
+ fun open(player: Player, page: Int) {
+ val itemsAll = ZaphkielAPI.registeredItem.keys.toList()
+ val holder = ItemListHolder(page)
+ val maxPage = Math.floor(itemsAll.size / 28.0).toInt() + 1
+ val inventory = Bukkit.createInventory(holder, 54, "Zaphkiel ItemList : " + (page + 1) + "/" + maxPage)
+ val items = SimpleIterator(itemsAll).listIterator(page * 28, (page + 1) * 28)
+ for (i in items.indices) {
+ try {
+ val item = ZaphkielAPI.registeredItem[items[i]]!!.build(player).save()
+ val itemMeta = item.itemMeta
+ val lore = if (itemMeta!!.hasLore()) itemMeta.lore else Lists.newArrayList()
+ lore!!.add("")
+ lore.add("§7序号: §8" + items[i])
+ itemMeta.lore = lore
+ item.itemMeta = itemMeta
+ inventory.setItem(Items.INVENTORY_CENTER[i], item)
+ } catch (t: Throwable) {
+ inventory.setItem(Items.INVENTORY_CENTER[i], ItemBuilder(Material.BARRIER).name("§4Invalid Item: " + items[i]).build())
+ }
+ holder.items[Items.INVENTORY_CENTER[i]] = items[i].toString()
+ }
+ if (page > 0) {
+ inventory.setItem(47, ItemBuilder(Material.ARROW).name("§f上一页").build())
+ }
+ if (page < maxPage - 1) {
+ inventory.setItem(51, ItemBuilder(Material.ARROW).name("§f下一页").build())
+ }
+ player.openInventory(inventory)
+ }
+ }
+}
diff --git a/src/main/java/ink/ptms/zaphkiel/api/internal/ScriptAPI.kt b/src/main/java/ink/ptms/zaphkiel/api/internal/ScriptAPI.kt
new file mode 100644
index 0000000..fbd10be
--- /dev/null
+++ b/src/main/java/ink/ptms/zaphkiel/api/internal/ScriptAPI.kt
@@ -0,0 +1,32 @@
+package ink.ptms.zaphkiel.api.internal
+
+import io.izzel.taboolib.util.Commands
+import org.bukkit.Bukkit
+import org.bukkit.command.CommandSender
+import org.bukkit.entity.Player
+
+/**
+ * @Author sky
+ * @Since 2019-12-15 22:30
+ */
+object ScriptAPI {
+
+ fun command(sender: CommandSender, command: String) {
+ Commands.dispatchCommand(sender, command)
+ }
+
+ fun commandOP(sender: CommandSender, command: String) {
+ val op = sender.isOp
+ sender.isOp = true
+ try {
+ Commands.dispatchCommand(sender, command)
+ } catch (t: Throwable) {
+ t.printStackTrace()
+ }
+ sender.isOp = op
+ }
+
+ fun commandConsole(command: String) {
+ Commands.dispatchCommand(Bukkit.getConsoleSender(), command)
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ink/ptms/zaphkiel/api/internal/StructureList.kt b/src/main/java/ink/ptms/zaphkiel/api/internal/StructureList.kt
new file mode 100644
index 0000000..68335eb
--- /dev/null
+++ b/src/main/java/ink/ptms/zaphkiel/api/internal/StructureList.kt
@@ -0,0 +1,58 @@
+package ink.ptms.zaphkiel.api.internal
+
+import io.izzel.taboolib.util.Variables
+
+/**
+ * @Author sky
+ * @Since 2019-12-15 14:55
+ */
+class StructureList(source: List) {
+
+ val cache = source.map { Variables(it).find() }.toList()
+
+ fun buildTrim(vars: Map>): List {
+ val list = build(vars).toMutableList()
+ while (list.isNotEmpty() && list.last().isEmpty()) {
+ list.removeAt(list.size - 1)
+ }
+ return list
+ }
+
+ fun build(vars: Map>): List {
+ val out = arrayListOf()
+ val cache = cache.toMutableList()
+ while (cache.isNotEmpty()) {
+ var more = false
+ var pass = false
+ val builder = StringBuilder()
+ cache[0].variableList.forEach { variable ->
+ if (variable.isVariable) {
+ if (variable.text.endsWith("...")) {
+ val list = vars[variable.text.substring(0, variable.text.length - 3)]
+ if (list?.isEmpty() != false) {
+ pass = true
+ return@forEach
+ }
+ if (list.isNotEmpty()) {
+ builder.append(list.removeAt(0))
+ }
+ if (list.isNotEmpty()) {
+ more = true
+ }
+ } else {
+ builder.append(vars[variable.text]?.firstOrNull() ?: "")
+ }
+ } else {
+ builder.append(variable.text)
+ }
+ }
+ if (!more) {
+ cache.removeAt(0)
+ }
+ if (!pass) {
+ out.add(builder.toString())
+ }
+ }
+ return out
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ink/ptms/zaphkiel/api/internal/StructureSingle.kt b/src/main/java/ink/ptms/zaphkiel/api/internal/StructureSingle.kt
new file mode 100644
index 0000000..2c9e8c9
--- /dev/null
+++ b/src/main/java/ink/ptms/zaphkiel/api/internal/StructureSingle.kt
@@ -0,0 +1,26 @@
+package ink.ptms.zaphkiel.api.internal
+
+import io.izzel.taboolib.util.Variables
+
+/**
+ * @Author sky
+ * @Since 2019-12-15 14:55
+ */
+class StructureSingle(source: String) {
+
+ val cache = Variables(source).find()!!
+
+ fun buildTrim(vars: Map): String {
+ return build(vars).trim()
+ }
+
+ fun build(vars: Map): String {
+ return cache.variableList.joinToString("") {
+ if (it.isVariable) {
+ vars[it.text] ?: ""
+ } else {
+ it.text
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ink/ptms/zaphkiel/module/ItemEvents.kt b/src/main/java/ink/ptms/zaphkiel/module/ItemEvents.kt
new file mode 100644
index 0000000..0c8dd59
--- /dev/null
+++ b/src/main/java/ink/ptms/zaphkiel/module/ItemEvents.kt
@@ -0,0 +1,87 @@
+package ink.ptms.zaphkiel.module
+
+import ink.ptms.zaphkiel.Zaphkiel
+import ink.ptms.zaphkiel.ZaphkielAPI
+import io.izzel.taboolib.module.inject.TListener
+import io.izzel.taboolib.util.item.Items
+import org.bukkit.event.EventHandler
+import org.bukkit.event.EventPriority
+import org.bukkit.event.Listener
+import org.bukkit.event.block.Action
+import org.bukkit.event.player.*
+
+/**
+ * @Author sky
+ * @Since 2019-12-15 22:22
+ */
+@TListener
+class ItemEvents : Listener {
+
+ @EventHandler(ignoreCancelled = true)
+ fun e(e: PlayerDropItemEvent) {
+ val item = ZaphkielAPI.read(e.itemDrop.itemStack)
+ if (item.isExtension()) {
+ item.getZaphkielItem().eval("onDrop", e)
+ }
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ fun e(e: PlayerItemBreakEvent) {
+ val item = ZaphkielAPI.read(e.brokenItem)
+ if (item.isExtension()) {
+ item.getZaphkielItem().eval("onBreak", e)
+ }
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ fun e(e: PlayerItemConsumeEvent) {
+ val item = ZaphkielAPI.read(e.item)
+ if (item.isExtension()) {
+ item.getZaphkielItem().eval("onConsume", e)
+ }
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ fun e(e: PlayerItemDamageEvent) {
+ val item = ZaphkielAPI.read(e.item)
+ if (item.isExtension()) {
+ item.getZaphkielItem().eval("onItemDamage", e)
+ }
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ fun e(e: PlayerSwapHandItemsEvent) {
+ if (Items.nonNull(e.mainHandItem)) {
+ val item = ZaphkielAPI.read(e.mainHandItem!!)
+ if (item.isExtension()) {
+ item.getZaphkielItem().eval("onSwapHand", e)
+ }
+ }
+ if (Items.nonNull(e.offHandItem)) {
+ val item = ZaphkielAPI.read(e.offHandItem!!)
+ if (item.isExtension()) {
+ item.getZaphkielItem().eval("onSwapHand", e)
+ }
+ }
+ }
+
+ @EventHandler
+ fun e(e: PlayerInteractEvent) {
+ if (Items.nonNull(e.item)) {
+ val item = ZaphkielAPI.read(e.item!!)
+ if (item.isVanilla()) {
+ return
+ }
+ when (e.action) {
+ Action.LEFT_CLICK_AIR, Action.LEFT_CLICK_BLOCK -> {
+ item.getZaphkielItem().eval("onLeftCLick", e)
+ }
+ Action.RIGHT_CLICK_AIR, Action.RIGHT_CLICK_BLOCK -> {
+ item.getZaphkielItem().eval("onRightClick", e)
+ }
+ else -> {
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/ink/ptms/zaphkiel/module/ItemRefresher.kt b/src/main/java/ink/ptms/zaphkiel/module/ItemRefresher.kt
new file mode 100644
index 0000000..5584a3f
--- /dev/null
+++ b/src/main/java/ink/ptms/zaphkiel/module/ItemRefresher.kt
@@ -0,0 +1,66 @@
+package ink.ptms.zaphkiel.module
+
+import ink.ptms.zaphkiel.Zaphkiel
+import ink.ptms.zaphkiel.ZaphkielAPI
+import io.izzel.taboolib.module.inject.TListener
+import io.izzel.taboolib.module.inject.TSchedule
+import org.bukkit.Bukkit
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.EventPriority
+import org.bukkit.event.Listener
+import org.bukkit.event.inventory.InventoryOpenEvent
+import org.bukkit.event.player.*
+
+/**
+ * @Author sky
+ * @Since 2019-12-16 10:40
+ */
+@TListener
+class ItemRefresher : Listener {
+
+ @EventHandler
+ fun e(e: PlayerJoinEvent) {
+ ZaphkielAPI.rebuild(e.player, e.player.inventory)
+ }
+
+ @EventHandler
+ fun e(e: PlayerRespawnEvent) {
+ ZaphkielAPI.rebuild(e.player, e.player.inventory)
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ fun e(e: PlayerDropItemEvent) {
+ ZaphkielAPI.rebuild(e.player, e.itemDrop.itemStack).run {
+ if (this.isFromRebuild) {
+ e.itemDrop.setItemStack(this.save())
+ }
+ }
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ fun e(e: PlayerPickupItemEvent) {
+ ZaphkielAPI.rebuild(e.player, e.item.itemStack).run {
+ if (this.isFromRebuild) {
+ e.item.setItemStack(this.save())
+ }
+ }
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ fun e(e: InventoryOpenEvent) {
+ if (e.inventory.location != null) {
+ Bukkit.getScheduler().runTaskAsynchronously(Zaphkiel.getPlugin(), Runnable {
+ ZaphkielAPI.rebuild(e.player as Player, e.inventory)
+ })
+ }
+ }
+
+ companion object {
+
+ @TSchedule(period = 100, async = true)
+ fun tick() {
+ Bukkit.getOnlinePlayers().forEach { player -> ZaphkielAPI.rebuild(player, player.inventory) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/resources/display/def.yml b/src/main/resources/display/def.yml
new file mode 100644
index 0000000..58b38b2
--- /dev/null
+++ b/src/main/resources/display/def.yml
@@ -0,0 +1,8 @@
+display0:
+ # 模板名称
+ name: '&7 '
+ # 模板描述
+ lore:
+ - '&9 &8'
+ - ''
+ - '&f'
\ No newline at end of file
diff --git a/src/main/resources/item/def.yml b/src/main/resources/item/def.yml
new file mode 100644
index 0000000..a970141
--- /dev/null
+++ b/src/main/resources/item/def.yml
@@ -0,0 +1,28 @@
+item0:
+ # 模板
+ display: display0
+ # 材质
+ icon: DRIED_KELP
+ # 名称
+ name:
+ # 名称
+ NAME: '&7黑黑黑羊肉'
+ # 描述
+ lore:
+ # 类型
+ TYPE: '&9食物'
+ # 描述
+ LORE:
+ - '&f黑羊身上撕下来的皮肉'
+ # 数据
+ data:
+ # 重量
+ weight: 0.01
+ # 耐久
+ durability: 3
+ # 事件
+ event:
+ # 消耗时
+ onConsume: |-
+ event.setCancelled(true)
+ event.getPlayer().setFoodLevel(event.getPlayer().getFoodLevel() + 2)
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..220e1e0
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,5 @@
+name: Zaphkiel
+main: ink.ptms.zaphkiel.Zaphkiel
+author: 坏黑
+
+version: ${version}
\ No newline at end of file
diff --git a/src/test/java/ink/ptms/zaphkiel/WeightHook.java b/src/test/java/ink/ptms/zaphkiel/WeightHook.java
new file mode 100644
index 0000000..6f07907
--- /dev/null
+++ b/src/test/java/ink/ptms/zaphkiel/WeightHook.java
@@ -0,0 +1,18 @@
+package ink.ptms.zaphkiel;
+
+import ink.ptms.zaphkiel.api.event.ItemBuildEvent;
+import ink.ptms.zaphkiel.api.event.ItemRebuildEvent;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+
+/**
+ * @Author sky
+ * @Since 2019-12-15 20:20
+ */
+public class WeightHook implements Listener {
+
+ @EventHandler
+ public void e(ItemBuildEvent.Pre e) {
+ e.addLore("WEIGHT", e.getItemStream().getZaphkielData().get("weight").asDouble());
+ }
+}