diff --git a/README.md b/README.md index 0a89ada..c302d39 100644 --- a/README.md +++ b/README.md @@ -4,83 +4,29 @@ An extensible custom item minecraft plugin * every item is a class file(runtime load) * can write custom item feature * every item has Automatically generated detailed configuration(from name to potion effect) +* support **kotlin DSL** and **kotlin Script(alse Superitem.kts)** + (see wiki for example) +## First Glance +Save it as hello.superitem.kts in plugins/Superitem/items to use +```kotlin +require(ItemInfo(Material.NAME_TAG, + "&cHello SuperItem", + listOf("&aWelcome to use SuperItem", + "&a欢迎使用Superitem", + "&c This Item doesn't have other effect"))) +``` +## For More information +see [Wiki] ## For Chinese see README.zh.md +## Thanks +* [LibraryManager] (shadowed) For runtime dependencies management +* [PowerNBT] (shadowed) For NBT support ## License -you should leave link to [this](https://github.com/way-zer/SuperItem/) and keep the author name in command help +you should leave link to [this] and keep the author name in command help feel free to fork and pull requests -## How to write an item -```kotlin - package cf.wayzer.example - - //for full see items/src/main/kotin/Damascus_knife - class Damascus_knife : Item() { - lateinit var effect: Effect - - override fun loadFeatures() { - require(ItemInfo(Material.IRON_SWORD, "§3大马士革刀", - listOf("§b§o特殊的冶炼方式", "§b§o让大马士革刀的纹路中", "§b§o含有一种奇特的化学物质", "§b§o小小的砍伤足以致人死地"))) - require(Recipe("2@0;0;265;0;265;265;280;42;0")) - require(Effect( - Effect.EffectData(PotionEffectType.POISON, 300) - )) - } - - @EventHandler(priority = EventPriority.LOWEST) - fun onAttack(e: EntityDamageByEntityEvent) { - if (e.isCancelled) return - if (e.damager is Player) { - val p = e.damager as Player - if (isItem(p.inventory.itemInMainHand) && get().hasPermission(p)) { - if (!e.entity.isDead) { - get().setEffect(e.entity as LivingEntity) - } - } - } - } - } - -``` -1. create an class extends *Item* -2. override function loadFeatures(): - in this function,you should require features you need(Permission and Texture is default required) -3. Item is *Listener*,which means you can write your eventHandler - -## How to write custom Feature -### What is a *Feature* -* Feature can use for a kind of feature.(custom Recipe,custom Durability or so on) -* Feature is a bridge of Item and configuration -### Example -if it isn't *Item*,you should put it into Package *lib* -```kotlin -package cf.wayzer.example.lib - -class Durability(override val defaultData: Int) : Feature(), Feature.HasListener { - override val listener: Listener - get() = mListener - - //Feature custom function(API) - fun setDurability(item: ItemStack, now: Int = data) {} - - fun getDurability(item: ItemStack): Int {} - - companion object { - private class MListener : Listener { - @EventHandler(ignoreCancelled = true) - fun onDurability(e: PlayerItemDamageEvent) { - // ... - } - } - - private val mListener = MListener() - } -} -``` -#### About *defaultData* -you can use either primary type or custom class. -you can return it build by class parameters -#### Other -* If you need listen event,you can implement *HasListener* -* If you need other feature,you can implement *onPostLoad* -* If you need handle disableEvent(such as clearing potion effect),you can implement *OnDisable* \ No newline at end of file +[this]: https://github.com/way-zer/SuperItem/ +[Wiki]: https://github.com/way-zer/SuperItem/wiki +[LibraryManager]: https://github.com/way-zer/LibraryManager +[PowerNBT]: https://github.com/steakteam/PowerNBT \ No newline at end of file diff --git a/README.zh.md b/README.zh.md index 960039f..e72fdbb 100644 --- a/README.zh.md +++ b/README.zh.md @@ -4,10 +4,29 @@ * 每个物品都是一个独立的class文件(运行时加载) * 可以自己编写Feature(见下方说明) * 每个物品都可以自动生成详细的配置文件(从名字到药水效果,取决于Feature) - +* 支持**kotlin DSL** 和 **kotlin Script(也称 Superitem.kts)**(例子详见[Wiki]) ## 授权 -保留[本站](https://github.com/way-zer/SuperItem/)链接并保留指令帮助页的作者信息 +保留[本站]链接并保留指令帮助页的作者信息 欢迎fork并提出pull请求,有任何疑问请提出issue +## 举个栗子 +保存为 hello.superitem.kts 于plugins/Superitem/items, 使用 +```kotlin +require(ItemInfo(Material.NAME_TAG, + "&cHello SuperItem", + listOf("&aWelcome to use SuperItem", + "&a欢迎使用Superitem", + "&c This Item doesn't have other effect"))) +``` +## 感谢 +* [LibraryManager] (shadowed) 提供运行依赖管理 +* [PowerNBT] (shadowed) 提供NBT修改支持 + +[本站]: https://github.com/way-zer/SuperItem/ +[Wiki]: https://github.com/way-zer/SuperItem/wiki +[LibraryManager]: https://github.com/way-zer/LibraryManager +[PowerNBT]: https://github.com/steakteam/PowerNBT + +# ------------过期内容(详见[Wiki])--------------- ## 怎样写一个item ```kotlin package cf.wayzer.example diff --git a/Scripts/Mythicmobs_Support.superitem.kts b/Scripts/Mythicmobs_Support.superitem.kts new file mode 100644 index 0000000..be07ac2 --- /dev/null +++ b/Scripts/Mythicmobs_Support.superitem.kts @@ -0,0 +1,17 @@ +@file:ImportClass("io.lumine.xikage.mythicmobs.api.bukkit.events.MythicDropLoadEvent") + +import cf.wayzer.SuperItem.ItemManager +import io.lumine.xikage.mythicmobs.adapters.bukkit.BukkitItemStack +import io.lumine.xikage.mythicmobs.api.bukkit.events.MythicDropLoadEvent +import io.lumine.xikage.mythicmobs.drops.droppables.ItemDrop + +require(ItemInfo(Material.NAME_TAG,"&cMythicmobs_Support", listOf("&c This Item doesn't have other effect"))) + +listen{ + val name = (it.dropName as String).toUpperCase() + if(!name.startsWith("SI_"))return@listen + val item = ItemManager.getItem(name.substring(3)) + //if(item==null)it.register(NothingDrop(it.config.line,it.config)) + if(item!=null)it.register(ItemDrop(it.config.line,it.config,BukkitItemStack(item.get().newItemStack()))) +} + diff --git a/build.gradle b/build.gradle index 71938b4..c22989b 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,11 @@ plugins { id 'com.github.johnrengelman.shadow' version '5.0.0' id 'java' - id 'org.jetbrains.kotlin.jvm' version "1.3.31" + id 'org.jetbrains.kotlin.jvm' version "1.3.41" + id 'me.qoomon.git-versioning' version '1.2.1' } ext{ - kotlin_version ="1.3.31" + kotlin_version ="1.3.41" mc_version = '1.14-R0.1-SNAPSHOT' bc_version ='1.12-SNAPSHOT' } @@ -18,30 +19,58 @@ allprojects{ maven { url = "https://hub.spigotmc.org/nexus/content/groups/public/" } + maven { + url "https://dl.bintray.com/way-zer/maven" + } } } shadowJar { // minimize() - configurations = [project.configurations.compile] + configurations = [project.configurations.shadow] + relocate("me.dpohvar.powernbt.api","cf.wayzer.util.nbt") + relocate("me.dpohvar.powernbt.utils","cf.wayzer.util.nbt.utils") +// exclude("me/**") +// exclude("example/**") +// exclude("(?!plugin).yml") + include("*.jar") + //for NBT + include("me/dpohvar/powernbt/api/**") + include("me/dpohvar/powernbt/utils/**") + + include("cf/wayzer/**") + include("*.kts") + include("META-INF/**") + include("plugin.yml") } //start dependencies{ + shadow 'cf.wayzer:LibraryManager:1.2' + shadow(files("$rootDir/lib/PowerNBT.jar")) compile "org.jetbrains.kotlin:kotlin-script-util:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-scripting-jvm-host:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + compileOnly "org.spigotmc:spigot-api:$mc_version" compileOnly "net.md-5:bungeecord-chat:$bc_version" - compileOnly files("$rootDir/lib/PowerNBT.jar") // compile "me.dpohvar.powernbt:PowerNBT:0.8.9.2" } -version="1.3.2" +version "1.3-SNAPSHOT" +gitVersioning{ + tag { + pattern = 'v(?[0-9].*)' + versionFormat = '${tagVersion}' + } + commit { + versionFormat = '${version}-${commit.short}' + } +} processResources{ filesMatching(["plugin.yml","bungee.yml"]){ filter{ - it.replace("@version@", project.version.toString()) + it.replace("@version@", version.toString()) } } } @@ -54,10 +83,13 @@ task buildArtifact(type: Zip){ include "**/*.class" into("items") } - from(jar.destinationDir) - archiveName ="SuperItem-${project.version}.zip" - destinationDir =file("$rootDir/artifacts/") -} -artifacts { - buildArtifact +// from(jar.getArchiveFile()){ +// rename(".jar","-developLib.jar") +// } + from("Scripts") + from(shadowJar.getArchiveFile()){ + rename("-all","") + } + archiveFileName = "SuperItem-${rootProject.version}.zip" + destinationDirectory =file("$rootDir/artifacts/") } diff --git a/lib/PowerNBT.jar b/lib/PowerNBT.jar index a645dba..8d7e59e 100644 --- a/lib/PowerNBT.jar +++ b/lib/PowerNBT.jar @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8ccff73622c95d66801e537080d147f947a86fc6dee3b6190ce84a732acdedb5 -size 265386 +oid sha256:86342b059478cd261eae14aac37f5c368d4e0cc38e158e3b8e91f85b4cc91bb6 +size 265909 diff --git a/src/main/kotlin/cf/wayzer/SuperItem/Commander.kt b/src/main/kotlin/cf/wayzer/SuperItem/Commander.kt index 5a8eaf0..7735460 100644 --- a/src/main/kotlin/cf/wayzer/SuperItem/Commander.kt +++ b/src/main/kotlin/cf/wayzer/SuperItem/Commander.kt @@ -6,6 +6,7 @@ import org.bukkit.command.Command import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandSender import org.bukkit.entity.Player +import java.io.File class Commander : CommandExecutor { @@ -24,6 +25,10 @@ class Commander : CommandExecutor { giveItem(s, args) return true } + "load" ->{ + loadItem(s,args) + return true + } } } help(s) @@ -59,6 +64,11 @@ class Commander : CommandExecutor { } if (item.givePlayer(player)) s.sendMessage("§a给予成功") + else{ + item.drop(player.location,player) + player.sendMessage("§a背包已满,已掉落") + s.sendMessage("§e背包已满,已掉落") + } } private fun getItem(s: CommandSender, args: Array) { @@ -118,12 +128,28 @@ class Commander : CommandExecutor { var i = pages * 10 - 10 while (i < list.size && i < pages * 10) { s.sendMessage(String.format("§e%03d §a|§e %-20s §a|§e %s", - i, list[i].name, list[i].get().itemStack.itemMeta?.displayName)) + i, list[i].name, list[i].get().newItemStack().itemMeta.displayName)) i++ } s.sendMessage("§a================ §7$pages/$maxpages §a================") } + private fun loadItem(s:CommandSender, args: Array){ + if (!s.hasPermission("SuperItem.command.load")) { + s.sendMessage("§c没有权限") + return + } + if (args.size < 2) { + s.sendMessage("§c请输入Path") + return + } + if (!File(ItemManager.rootDir,args[1]).exists()){ + s.sendMessage("§c请输入Path") + return + } + TODO("From path to get item name") + } + private fun help(s: CommandSender) { s.sendMessage("§c========= §c§lSuper Item§c =========") s.sendMessage("§a §l-------§5 By: §5§lWay__Zer §a§l-------") @@ -131,6 +157,7 @@ class Commander : CommandExecutor { s.sendMessage("§a§l+§5/SuperItem list <页码> §e打开Item列表") s.sendMessage("§a§l+§5/SuperItem get §e获取Item") s.sendMessage("§a§l+§5/SuperItem give §e给予玩家Item") + s.sendMessage("§a§l+§5/SuperItem load §e加载或重载Item") } } diff --git a/src/main/kotlin/cf/wayzer/SuperItem/ConfigManager.kt b/src/main/kotlin/cf/wayzer/SuperItem/ConfigManager.kt index 790696c..4acf265 100644 --- a/src/main/kotlin/cf/wayzer/SuperItem/ConfigManager.kt +++ b/src/main/kotlin/cf/wayzer/SuperItem/ConfigManager.kt @@ -15,6 +15,7 @@ object ConfigManager{ } fun loadForFeature(item: Item, feature: Feature){ + if(feature.defaultData is Nothing)return val config = item.config if(!config.has(feature.name))config.add(feature.name, gson.toJsonTree(feature.defaultData)) feature.data= gson.fromJson(config.get(feature.name),feature.defaultData::class.java) @@ -43,4 +44,4 @@ object ConfigManager{ private val Item.configFile:File get() = File(rootDir, "$packageName ${File.pathSeparator} $name.json") -} \ No newline at end of file +} diff --git a/src/main/kotlin/cf/wayzer/SuperItem/Item.kt b/src/main/kotlin/cf/wayzer/SuperItem/Item.kt index 0a0ff2f..8ce4e2b 100644 --- a/src/main/kotlin/cf/wayzer/SuperItem/Item.kt +++ b/src/main/kotlin/cf/wayzer/SuperItem/Item.kt @@ -4,6 +4,7 @@ import cf.wayzer.SuperItem.features.CoolDown import cf.wayzer.SuperItem.features.ItemInfo import cf.wayzer.SuperItem.features.Permission import org.bukkit.Bukkit +import org.bukkit.Location import org.bukkit.entity.Player import org.bukkit.event.Event import org.bukkit.event.EventPriority @@ -36,9 +37,9 @@ abstract class Item : Listener { val features : MutableMap,MutableList>> = mutableMapOf() - @Deprecated("保证扩展和安全,请使用get,1.3版本弃用",ReplaceWith("get().itemStack")) + @Deprecated("保证扩展和安全,请使用get,1.3版本弃用",ReplaceWith("get().newItemStack()")) val item - get() = get().itemStack + get() = get().newItemStack() @Deprecated("保证扩展和安全,请使用get,1.3版本弃用",ReplaceWith("get()")) val permission @@ -74,6 +75,17 @@ abstract class Item : Listener { */ inline fun > get(index: Int=0):T = get(T::class.java,index) + /** + * 判断是否有对应feature + */ + fun > has(c : Class):Boolean = features[c]?.isNotEmpty()?:false + + /** + * @see has(c) + * 只能在kotlin下调用 + */ + inline fun > has():Boolean = has(T::class.java) + /** * 给予玩家道具 */ @@ -85,11 +97,15 @@ abstract class Item : Listener { return false } inv.setItem(i, inv.itemInMainHand) - inv.setItemInMainHand(get().itemStack.clone()) + inv.setItemInMainHand(get().newItemStack(p)) p.updateInventory() return true } + fun drop(location: Location,player: Player?=null){ + location.world.dropItem(location,get().newItemStack(player)) + } + /** * 判断物品是否是当前Item的道具 */ diff --git a/src/main/kotlin/cf/wayzer/SuperItem/ItemManager.kt b/src/main/kotlin/cf/wayzer/SuperItem/ItemManager.kt index 750f0a7..86e31fd 100644 --- a/src/main/kotlin/cf/wayzer/SuperItem/ItemManager.kt +++ b/src/main/kotlin/cf/wayzer/SuperItem/ItemManager.kt @@ -3,18 +3,19 @@ package cf.wayzer.SuperItem import cf.wayzer.SuperItem.Main.Companion.main import cf.wayzer.SuperItem.features.NBT import cf.wayzer.SuperItem.features.Permission +import cf.wayzer.SuperItem.scripts.ScriptSupporter import org.bukkit.Material import org.bukkit.inventory.ItemStack import java.io.File import java.net.URLClassLoader import java.util.* import java.util.logging.Level -import kotlin.script.experimental.api.* object ItemManager { private val logger = main.logger private val items = HashMap() private lateinit var ucl:URLClassLoader + lateinit var rootDir:File /** * 从文件夹加载Item @@ -22,12 +23,12 @@ object ItemManager { */ @Throws(Exception::class) fun load() { - val dir = File(Main.main.dataFolder, "items") - if (!dir.exists()) - dir.mkdirs() - ucl = URLClassLoader.newInstance(arrayOf(dir.toURI().toURL()), + rootDir = File(Main.main.dataFolder, "items") + if (!rootDir.exists()) + rootDir.mkdirs() + ucl = URLClassLoader.newInstance(arrayOf(rootDir.toURI().toURL()), ItemManager::class.java.classLoader) - loadDir(dir,"") + loadDir(rootDir,"") Main.main.saveConfig() } @@ -39,39 +40,9 @@ object ItemManager { } else { try { - val item:Item - if(file.name.endsWith("superitem.kts")){ - logger.info("Load Item in async: ${file.name}") - run { - val result = ScriptSupporter.loadFile(file) - result.onSuccess { - val res = result.resultOrNull()!!.returnValue - if(res is ResultValue.Value && res.value is ScriptSupporter.SuperItemScript) { - (res.value as ScriptSupporter.SuperItemScript).register() - result.reports.forEachIndexed { index, rep -> - logger.log(Level.WARNING,"##$index##"+rep.message,rep.exception) - } - return@onSuccess ResultWithDiagnostics.Success(ResultValue.Unit) - } else { - return@onSuccess ResultWithDiagnostics.Failure(ScriptDiagnostic("非物品Kts: ${file.name}")) - } - }.onFailure { - logger.warning("物品Kts加载失败: ") - it.reports.forEachIndexed { index, rep -> - logger.log(Level.WARNING,"##$index##"+rep.message,rep.exception) - } - } - } - }else if (file.name.endsWith(".class")&&!file.name.contains("$")){ - val name = file.nameWithoutExtension - val c = ucl.loadClass("$prefix.$name".substring(1)) - if (c.superclass != Item::class.java) { - logger.warning("非物品Class: ${file.name}") - return - } - item = c.getConstructor().newInstance() as Item - registerItem0(item) - } + val name = file.nameWithoutExtension + val item = loadFile(file,"$prefix.$name".substring(1)) + if(item!=null)registerItem0(item) } catch (e: Exception) { logger.log(Level.SEVERE, "注册物品失败: ${file.nameWithoutExtension}", e) } @@ -79,6 +50,29 @@ object ItemManager { } } + /** + * 供插件内部使用 + */ + @Throws(Exception::class) + fun loadFile(file: File,className: String):Item?{ + var item:Item? = null + if(file.name.endsWith("superitem.kts")){ + ScriptSupporter.init(logger) + logger.info("Load Item in async: ${file.name}") + run { + item = ScriptSupporter.load(file) + } + }else if (file.name.endsWith(".class")&&!file.name.contains("$")){ + val c = ucl.loadClass(className) + if (c.superclass != Item::class.java) { + logger.warning("非物品Class: ${file.name}") + return null + } + return c.getConstructor().newInstance() as Item + } + return item + } + /** * 注册物品,可以从其他插件注册 */ @@ -107,14 +101,20 @@ object ItemManager { if (it is Feature.HasListener) { main.server.pluginManager.registerEvents(it.listener, main) } - if (it is Feature.OnDisable) { - main.addDisableListener(it) - } } + items[item.name] = item main.server.pluginManager.registerEvents(item, main) logger.info("注册物品成功: ${item.name}") } + fun unregisterItem(item: Item){ + items.remove(item.name,item) + item.features.values.flatten().forEach { + if(it is Feature.OnDisable) + it.onDisable(main) + } + } + /** * 通过 ItemStack 获取 Item * 没有对应Item返回 null diff --git a/src/main/kotlin/cf/wayzer/SuperItem/Main.kt b/src/main/kotlin/cf/wayzer/SuperItem/Main.kt index 476e788..1d37420 100644 --- a/src/main/kotlin/cf/wayzer/SuperItem/Main.kt +++ b/src/main/kotlin/cf/wayzer/SuperItem/Main.kt @@ -1,6 +1,9 @@ package cf.wayzer.SuperItem +import cf.wayzer.libraryManager.Dependency +import cf.wayzer.libraryManager.LibraryManager import org.bukkit.plugin.java.JavaPlugin +import java.nio.file.Paths import java.util.logging.Level class Main : JavaPlugin() { @@ -17,23 +20,25 @@ class Main : JavaPlugin() { } override fun onDisable() { - set.forEach { - it.onDisable(this) - } + ItemManager.getItems().toList().forEach(ItemManager::unregisterItem) } companion object { + const val kotlinVersion = "1.3.41" lateinit var main: Main private set - } - - private val set = mutableSetOf() - - /** - * 添加插件Disable的Listener - * 供Feature使用 - */ - fun addDisableListener(listener: Feature.OnDisable) { - set.add(listener) + init { + LibraryManager(Paths.get("./libs/")).apply { + addMavenCentral() + require(Dependency("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")) + require(Dependency("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1")) + require(Dependency("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")) + require(Dependency("org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion")) + require(Dependency("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")) + require(Dependency("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion")) + require(Dependency("org.jetbrains:annotations:13.0")) + loadToClasspath() + } + } } } diff --git a/src/main/kotlin/cf/wayzer/SuperItem/ScriptSupporter.kt b/src/main/kotlin/cf/wayzer/SuperItem/ScriptSupporter.kt deleted file mode 100644 index 176880c..0000000 --- a/src/main/kotlin/cf/wayzer/SuperItem/ScriptSupporter.kt +++ /dev/null @@ -1,50 +0,0 @@ -package cf.wayzer.SuperItem - -import org.bukkit.Bukkit -import java.io.File -import kotlin.reflect.KClass -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.* -import kotlin.script.experimental.host.toScriptSource -import kotlin.script.experimental.jvm.JvmScriptCompilationConfigurationBuilder -import kotlin.script.experimental.jvm.jvm -import kotlin.script.experimental.jvm.updateClasspath -import kotlin.script.experimental.jvm.util.classpathFromClass -import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost -import kotlin.script.experimental.jvmhost.baseClassLoader -import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate -import kotlin.script.experimental.jvmhost.jvm - -object ScriptSupporter { - private fun JvmScriptCompilationConfigurationBuilder.dependenciesFromClass(classLoader: ClassLoader,vararg classes:KClass) { - classes.flatMap {c-> - classpathFromClass(classLoader,c)?:let { - val clp = "${c.java.canonicalName.replace('.', '/')}.class" - val url = classLoader.getResource(clp) - url?.toURI()?.schemeSpecificPart?.let { listOf(File(it.removePrefix("file:").split("!")[0])) } - }?: emptyList() - }.let(this::updateClasspath) - } - object Configuration:ScriptCompilationConfiguration({ - jvm{ - dependenciesFromClass(javaClass.classLoader,Bukkit::class,Item::class) - } - defaultImports(Item::class) - defaultImports.append("cf.wayzer.SuperItem.features.*") - }) - @KotlinScript( - displayName = "SuperItem Kotlin Script", - fileExtension = "superitem.kts", - compilationConfiguration = Configuration::class - ) - abstract class SuperItemScript(name:String):Item.Builder("scripts",name) - fun loadFile(f: File):ResultWithDiagnostics{ - val conf = createJvmCompilationConfigurationFromTemplate () - return BasicJvmScriptingHost().eval(f.toScriptSource(),conf, ScriptEvaluationConfiguration { - jvm{ - baseClassLoader(ScriptSupporter::class.java.classLoader) - } - constructorArgs(f.name.split(".")[0].toUpperCase()) - }) - } -} \ No newline at end of file diff --git a/src/main/kotlin/cf/wayzer/SuperItem/events/ItemStackHandleEvent.kt b/src/main/kotlin/cf/wayzer/SuperItem/events/ItemStackHandleEvent.kt new file mode 100644 index 0000000..e877c98 --- /dev/null +++ b/src/main/kotlin/cf/wayzer/SuperItem/events/ItemStackHandleEvent.kt @@ -0,0 +1,14 @@ +package cf.wayzer.SuperItem.events + +import org.bukkit.entity.Player +import org.bukkit.event.Event +import org.bukkit.event.HandlerList +import org.bukkit.inventory.ItemStack + +data class ItemStackHandleEvent (var itemStack:ItemStack,val player:Player?):Event() { + override fun getHandlers()=staticHandlers + + companion object{ + private val staticHandlers = HandlerList() + } +} \ No newline at end of file diff --git a/src/main/kotlin/cf/wayzer/SuperItem/features/DataStore.kt b/src/main/kotlin/cf/wayzer/SuperItem/features/DataStore.kt new file mode 100644 index 0000000..0801b85 --- /dev/null +++ b/src/main/kotlin/cf/wayzer/SuperItem/features/DataStore.kt @@ -0,0 +1,28 @@ +package cf.wayzer.SuperItem.features + +import cf.wayzer.SuperItem.Feature +import cf.wayzer.SuperItem.Main +import org.bukkit.metadata.FixedMetadataValue +import org.bukkit.metadata.Metadatable + +object DataStore { + interface DataStore{ + fun set(o: Metadatable,value: T?) + fun get(o:Metadatable,defaultValue:T):T + } + class MetaStore(override val defaultData: Nothing) :Feature(),DataStore{ + private val key:String + get() = "SIMS_${item.name}" + override fun get(o: Metadatable, defaultValue: T):T { + @Suppress("UNCHECKED_CAST") + return if(o.hasMetadata(key)) o.getMetadata(key)[0].value() as T + else defaultValue + } + + override fun set(o: Metadatable, value: T?) { + if(value==null)o.removeMetadata(key, Main.main) + else o.setMetadata(key,FixedMetadataValue(Main.main,value)) + } + } + //TODO other Store: SQL and File +} \ No newline at end of file diff --git a/src/main/kotlin/cf/wayzer/SuperItem/features/ItemInfo.kt b/src/main/kotlin/cf/wayzer/SuperItem/features/ItemInfo.kt index 79e614a..37abdaf 100644 --- a/src/main/kotlin/cf/wayzer/SuperItem/features/ItemInfo.kt +++ b/src/main/kotlin/cf/wayzer/SuperItem/features/ItemInfo.kt @@ -2,7 +2,10 @@ package cf.wayzer.SuperItem.features import cf.wayzer.SuperItem.Feature import cf.wayzer.SuperItem.Main +import cf.wayzer.SuperItem.Main.Companion.main +import cf.wayzer.SuperItem.events.ItemStackHandleEvent import org.bukkit.Material +import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack import org.bukkit.inventory.meta.ItemMeta @@ -21,7 +24,12 @@ class ItemInfo( private val defaultDamage: Short = 0, private val loadOther: (ItemMeta, ItemStack) -> Unit = { _, _ -> } ) : Feature(), Feature.OnPostLoad { - lateinit var itemStack: ItemStack private set + interface ItemStackHandler : ((ItemStack,Player?)->Unit) + /** + * ItemStack初始化模板 + */ + lateinit var itemStackTemplate: ItemStack private set + private val itemStackHandlers = mutableSetOf() override val defaultData: Data get() = Data(defaultMaterial, defaultDamage, defaultName, defaultLore) @@ -33,9 +41,30 @@ class ItemInfo( val lore: List ) + /** + * Create an ItemStack using Template and Handlers + * @param p the player crafting + */ + fun newItemStack(p:Player?=null):ItemStack{ + val itemStack = itemStackTemplate.clone() + itemStackHandlers.forEach { it(itemStack,p) } + main.server.pluginManager.callEvent(ItemStackHandleEvent(itemStack,p)) + itemStack.itemMeta = itemStack.itemMeta.apply { + displayName = displayName.replace("&","§") + lore = lore.map { it.replace("&","§") } + } + return itemStack + } + + /** + * Handler when an itemStack creates + * Don't work with Recipe and Texture + */ + fun registerHandler(handler: ItemStackHandler)=itemStackHandlers.add(handler) + override fun onPostLoad(main: Main) { @Suppress("DEPRECATION") - itemStack = ItemStack(data.material, 1, 0,data.data.toByte()) + val itemStack = ItemStack(data.material, 1, 0,data.data.toByte()) val im = itemStack.itemMeta!! im.setDisplayName(data.name) @@ -47,10 +76,10 @@ class ItemInfo( nbt[NBT_TAG_NAME] = item.name NBT.api.write(itemStack, nbt) - this.itemStack = itemStack + this.itemStackTemplate = itemStack } companion object { const val NBT_TAG_NAME="SICN" } -} \ No newline at end of file +} diff --git a/src/main/kotlin/cf/wayzer/SuperItem/features/NBT.kt b/src/main/kotlin/cf/wayzer/SuperItem/features/NBT.kt index 9db1fa0..e27661d 100644 --- a/src/main/kotlin/cf/wayzer/SuperItem/features/NBT.kt +++ b/src/main/kotlin/cf/wayzer/SuperItem/features/NBT.kt @@ -11,6 +11,7 @@ import java.util.logging.Level * 具体设置参照Wiki: https://minecraft.gamepedia.com/Attribute * @sss me.dpohvar.powernbt.api.NBTManager */ +@Suppress("unused") class NBT(override vararg val defaultData: AttributeModifier) : Feature>(), Feature.OnPostLoad { enum class AttributeType(val attributeName: String, val max: Double) { MaxHealth("generic.maxHealth", 1024.0), @@ -51,7 +52,7 @@ class NBT(override vararg val defaultData: AttributeModifier) : Feature().itemStack).compound("tag").list("AttributeModifiers") + val nbt = api.read(item.get().itemStackTemplate).compound("tag").list("AttributeModifiers") data.forEach { if (it.amount < it.type.max) { val node = NBTCompound() diff --git a/src/main/kotlin/cf/wayzer/SuperItem/features/Recipe.kt b/src/main/kotlin/cf/wayzer/SuperItem/features/Recipe.kt index def9933..6b25e80 100644 --- a/src/main/kotlin/cf/wayzer/SuperItem/features/Recipe.kt +++ b/src/main/kotlin/cf/wayzer/SuperItem/features/Recipe.kt @@ -3,20 +3,41 @@ package cf.wayzer.SuperItem.features import cf.wayzer.SuperItem.Feature import cf.wayzer.SuperItem.Main import cf.wayzer.util.RecipeUtil +import org.bukkit.Material +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.inventory.PrepareItemCraftEvent +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.Recipe /** * 为物品绑定合成配方 * 需要在 ItemInfo 后使用 * @see ItemInfo - * @param default 合成配方 + * @param defaultData 合成配方 * @see RecipeUtil */ -class Recipe(override val defaultData: String) : Feature(), Feature.OnPostLoad, Feature.OnDisable { +class Recipe(override val defaultData: String) : Feature(), Feature.OnPostLoad, Feature.OnDisable,Feature.HasListener,Listener { + override val listener: Listener + get() = this + private lateinit var recipe:Recipe override fun onPostLoad(main: Main) { - val recipe = RecipeUtil.getByString(item.get().itemStack, data) + recipe = RecipeUtil.getByString(ItemStack(Material.AIR), data) main.server.addRecipe(recipe) } override fun onDisable(main: Main) { + val iter = main.server.recipeIterator() + iter.forEachRemaining { + if(it==recipe)iter.remove() + } + } + + @EventHandler + fun onCreate(event:PrepareItemCraftEvent){ + if(event.recipe==recipe){ + event.inventory.result = item.get().newItemStack(event.viewers[0] as? Player) + } } } diff --git a/src/main/kotlin/cf/wayzer/SuperItem/features/Texture.kt b/src/main/kotlin/cf/wayzer/SuperItem/features/Texture.kt index 35867bf..3e2f9c2 100644 --- a/src/main/kotlin/cf/wayzer/SuperItem/features/Texture.kt +++ b/src/main/kotlin/cf/wayzer/SuperItem/features/Texture.kt @@ -32,7 +32,7 @@ class Texture(private val default: String? = null) : Feature(), Feature. if (!cF.exists()) { val cfText = """ |type=item - |items=${item.get().itemStack.type.name.toLowerCase()} + |items=${item.get().itemStackTemplate.type.name.toLowerCase()} |texture=${item.name} |nbt.SICN=${item.name} """.trimMargin() diff --git a/src/main/kotlin/cf/wayzer/SuperItem/scripts/CompilationConfiguration.kt b/src/main/kotlin/cf/wayzer/SuperItem/scripts/CompilationConfiguration.kt new file mode 100644 index 0000000..b96baeb --- /dev/null +++ b/src/main/kotlin/cf/wayzer/SuperItem/scripts/CompilationConfiguration.kt @@ -0,0 +1,42 @@ +package cf.wayzer.SuperItem.scripts + +import cf.wayzer.SuperItem.Item +import org.bukkit.Bukkit +import org.bukkit.Material +import java.io.File +import kotlin.reflect.KClass +import kotlin.script.experimental.api.* +import kotlin.script.experimental.jvm.JvmScriptCompilationConfigurationBuilder +import kotlin.script.experimental.jvm.jvm +import kotlin.script.experimental.jvm.updateClasspath +import kotlin.script.experimental.jvm.util.classpathFromClass + +object CompilationConfiguration: ScriptCompilationConfiguration({ + jvm { + dependenciesFromClass(Bukkit::class, Item::class) + } + defaultImports(Item::class, ImportClass::class,Material::class) + defaultImports.append("cf.wayzer.SuperItem.features.*") + refineConfiguration{ + onAnnotations(ImportClass::class){ context-> + val annotations = context.collectedData?.get(ScriptCollectedData.foundAnnotations)?.filter { it.annotationClass== ImportClass::class }?: listOf() + val classes = annotations.map { Class.forName((it as ImportClass).name).kotlin} + val diagnostics = classes.map { ScriptDiagnostic("[Info]PluginDependency: ${it.java.name}",ScriptDiagnostic.Severity.INFO) } + ScriptCompilationConfiguration(context.compilationConfiguration) { + jvm { + dependenciesFromClass(*classes.toTypedArray()) + } + }.asSuccess(diagnostics) + } + } +}) +private fun JvmScriptCompilationConfigurationBuilder.dependenciesFromClass(vararg classes: KClass) { + classes.flatMap {c-> + val classLoader = c.java.classLoader + classpathFromClass(classLoader,c) ?:let { + val clp = "${c.java.canonicalName.replace('.', '/')}.class" + val url = classLoader.getResource(clp) + url?.toURI()?.schemeSpecificPart?.let { listOf(File(it.removePrefix("file:").split("!")[0])) } + }?: emptyList() + }.let(this::updateClasspath) +} \ No newline at end of file diff --git a/src/main/kotlin/cf/wayzer/SuperItem/scripts/ImportClass.kt b/src/main/kotlin/cf/wayzer/SuperItem/scripts/ImportClass.kt new file mode 100644 index 0000000..df6f640 --- /dev/null +++ b/src/main/kotlin/cf/wayzer/SuperItem/scripts/ImportClass.kt @@ -0,0 +1,7 @@ +package cf.wayzer.SuperItem.scripts + +@Target(AnnotationTarget.FILE) +@Repeatable +annotation class ImportClass( + val name:String +) \ No newline at end of file diff --git a/src/main/kotlin/cf/wayzer/SuperItem/scripts/ScriptLoader.kt b/src/main/kotlin/cf/wayzer/SuperItem/scripts/ScriptLoader.kt new file mode 100644 index 0000000..6fa89b5 --- /dev/null +++ b/src/main/kotlin/cf/wayzer/SuperItem/scripts/ScriptLoader.kt @@ -0,0 +1,58 @@ +package cf.wayzer.SuperItem.scripts + +import cf.wayzer.SuperItem.Item +import cf.wayzer.SuperItem.Main +import java.io.File +import java.util.logging.Level +import java.util.logging.Logger +import kotlin.script.experimental.api.* +import kotlin.script.experimental.host.ScriptingHostConfiguration +import kotlin.script.experimental.host.configurationDependencies +import kotlin.script.experimental.host.toScriptSource +import kotlin.script.experimental.jvm.JvmDependency +import kotlin.script.experimental.jvm.baseClassLoader +import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration +import kotlin.script.experimental.jvm.jvm +import kotlin.script.experimental.jvm.util.classpathFromClassloader +import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost +import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate + +class ScriptLoader { + lateinit var logger: Logger + private val hostConfiguration by lazy { ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration){ + configurationDependencies(JvmDependency(classpathFromClassloader(Main::class.java.classLoader) ?:listOf())) + }} + private val compilationConfiguration by lazy {createJvmCompilationConfigurationFromTemplate (hostConfiguration) } + + fun load(file: File):Item?{ + var item:Item?=null + val result = load0(file) + result.onSuccess { + val res = result.resultOrNull()!!.returnValue + if(res is ResultValue.Value && res.value is Item) { + item = (res.value as Item) + result.reports.filterNot { it.severity== ScriptDiagnostic.Severity.DEBUG }.forEachIndexed { index, rep -> + logger.log(Level.WARNING,"##$index##"+rep.message,rep.exception) + } + return@onSuccess ResultWithDiagnostics.Success(ResultValue.Unit) + } else { + return@onSuccess ResultWithDiagnostics.Failure(ScriptDiagnostic("非物品Kts: ${file.name}")) + } + }.onFailure { + logger.warning("物品Kts加载失败: ") + it.reports.forEachIndexed { index, rep -> + logger.log(Level.WARNING,"##$index##"+rep.message,rep.exception) + } + } + return item + } + + private fun load0(f: File):ResultWithDiagnostics{ + return BasicJvmScriptingHost(hostConfiguration).eval(f.toScriptSource(),compilationConfiguration, ScriptEvaluationConfiguration { + jvm { + baseClassLoader(ScriptSupporter::class.java.classLoader) + } + constructorArgs(f.name.split(".")[0].toUpperCase()) + }) + } +} \ No newline at end of file diff --git a/src/main/kotlin/cf/wayzer/SuperItem/scripts/ScriptSupporter.kt b/src/main/kotlin/cf/wayzer/SuperItem/scripts/ScriptSupporter.kt new file mode 100644 index 0000000..fc44512 --- /dev/null +++ b/src/main/kotlin/cf/wayzer/SuperItem/scripts/ScriptSupporter.kt @@ -0,0 +1,39 @@ +package cf.wayzer.SuperItem.scripts + +import cf.wayzer.SuperItem.Item +import cf.wayzer.SuperItem.Main +import cf.wayzer.libraryManager.Dependency +import cf.wayzer.libraryManager.LibraryManager +import java.io.File +import java.net.URLClassLoader +import java.nio.file.Paths +import java.util.logging.Logger + +object ScriptSupporter { + lateinit var loader:ScriptLoader + private var inited=false + fun init(logger:Logger){ + if(inited)return + LibraryManager(Paths.get("./libs/")).apply { + addMavenCentral() + require(Dependency("org.jetbrains.kotlin:kotlin-script-runtime:${Main.kotlinVersion}")) + require(Dependency("org.jetbrains.kotlin:kotlin-scripting-common:${Main.kotlinVersion}")) + require(Dependency("org.jetbrains.intellij.deps:trove4j:1.0.20181211")) + require(Dependency("org.jetbrains.kotlin:kotlin-daemon-client:${Main.kotlinVersion}")) + require(Dependency("org.jetbrains.kotlin:kotlin-scripting-jvm:${Main.kotlinVersion}")) + require(Dependency("org.jetbrains.kotlin:kotlin-scripting-jvm-host:${Main.kotlinVersion}")) + require(Dependency("org.jetbrains.kotlin:kotlin-scripting-compiler:${Main.kotlinVersion}")) + require(Dependency("org.jetbrains.kotlin:kotlin-scripting-compiler-impl:${Main.kotlinVersion}")) + require(Dependency("org.jetbrains.kotlin:kotlin-compiler:${Main.kotlinVersion}")) + require(Dependency("org.jetbrains.kotlin:kotlin-script-util:${Main.kotlinVersion}")) + }.loadToClassLoader(Main::class.java.classLoader as URLClassLoader) + loader = ScriptLoader() + loader.logger=logger + inited =true + } + + fun load(file: File): Item?{ + if(!inited)throw IllegalStateException("Must init first!!") + return loader.load(file) + } +} \ No newline at end of file diff --git a/src/main/kotlin/cf/wayzer/SuperItem/scripts/SuperItemScript.kt b/src/main/kotlin/cf/wayzer/SuperItem/scripts/SuperItemScript.kt new file mode 100644 index 0000000..139340e --- /dev/null +++ b/src/main/kotlin/cf/wayzer/SuperItem/scripts/SuperItemScript.kt @@ -0,0 +1,11 @@ +package cf.wayzer.SuperItem.scripts + +import cf.wayzer.SuperItem.Item +import kotlin.script.experimental.annotations.KotlinScript + +@KotlinScript( + displayName = "SuperItem Kotlin Script", + fileExtension = "superitem.kts", + compilationConfiguration = CompilationConfiguration::class +) +open class SuperItemScript(name:String): Item.Builder("scripts",name) \ No newline at end of file diff --git a/src/main/resources/META-INF/kotlin/script/templates/cf.wayzer.SuperItem.scripts.SuperItemScript b/src/main/resources/META-INF/kotlin/script/templates/cf.wayzer.SuperItem.scripts.SuperItemScript new file mode 100644 index 0000000..e69de29