diff --git a/readme.md b/readme.md new file mode 100644 index 00000000..947b4ff7 --- /dev/null +++ b/readme.md @@ -0,0 +1,165 @@ +# KSpigot + +## About + +KSpigot is a kotlin extension for the popular [spigot server software](https://spigotmc.org/) for minecraft. +KSpigot adds functionality missing in spigot and partly makes it possible to do it the kotlin way. Most of KSpigot's extensions are stable. + +IMPORTANT: +Extensions marked with NMS annotations like `@NMS_GENERAL` or `@NMS_"VERSIONSTRING"` are unstable. + +Extensions marked with the `@UnsafeImplementaion` annotation do not promise to always give the correct result, but are still useful and therefore included in the project. This readme DOES NOT contain any unsafe parts of KSpigot. + +## First of all + +**Create an instance of KSpigot (the constructor requires your plugin instance)**
+Do NOT create multiple instances! +```kotlin +val kSpigot = KSpigot(plugin) +``` + +**Inside of your `onDisable()` method, call:** +```kotlin +kSpigot.shutdown() +``` + +## Examples + +### Simple runnables and schedulers: + +```kotlin +bukkitAsync(kSpigot) { /* short form for async schedulers */ } +``` +```kotlin +bukkitSync(kSpigot) { /* sync some code to bukkits main thread */ } +``` + +```kotlin +bukkitRunnable( + kSpigot, + sync = false, + howoften = 5, + delay = 25, + period = 20 +) { /* runnable code */ } +``` + +### Powerful builders + +#### For items + +```kotlin +val wand = KSpigotItems.buildItem(Material.GOLD_BLOCK) { + + amount = 3 + + addEnchantment(Enchantment.KNOCKBACK, 2) + + itemMeta { + + displayName = "${ChatColor.GOLD}Magic wand" + unbreakable = true + + addLore { + + "This wand is truly special." + + "Try it!" + } + + customModelData = 1001 + + flag(ItemFlag.HIDE_UNBREAKABLE) + + } + +} +``` + +#### For complex chat components + +```kotlin +KSpigotChat.buildComponent { + + text { + text = "You got a friend request! " + color = ChatColor.of("#4FEA40") + bold = true + } + + text { + text = "[Accept]" + color = ChatColor.WHITE + clickEvent = ClickEvent(ClickEvent.Action.RUN_COMMAND, "friend accept Foo") + hoverEventText { + text { text = "Click here to accept the friend request!"; color = ChatColor.RED } + } + } + +} +``` +You can also access the builder by calling +```kotlin +commandSender.sendMessage { /* same in here as above */ } +``` + +#### And more + +A lot of additional things are also suitable for builders. Just like fireworks etc... + +### NBTData support +Typesafe and consistent + +```kotlin +// load nbt +val nbt = entity.nbtData + +// retrieve data via keys +val health = nbt["hearts", NBTDataType.INT] + +// set data for a given key +nbt["custom", NBTDataType.DOUBLE] = 3.3 + +// save data to the entity +entity.nbtData = nbt + +// serialization support +val serializedString = nbt.serialize() +val deserializeMethod1 = NBTData(serializedString) +val deserializeMethod2 = NBTData.deserialize(serializedString) +``` + +### Simple extension methods / values (with kotlin getters) + +```kotlin +entity.isGroundSolid +entity.isInWater + +vector.isFinite + +playerInteractEntityEvent.interactItem +``` + +### Direction API +Handles the hassle of struggling with direction angles for you. + +```kotlin +val cardinal = CardinalDirection.fromLocation(loc) // NORTH, EAST, SOUTH, WEST +val vertical = VerticalDirection.fromLocation(loc) // UP, DOWN, STRAIGHT + +// convert to BlockFace +val blockFace = cardinal.facing +``` + +### CustomItemIdentifiers +You want to mess with resourcepacks and extend your possibilities?
+Spigot is lacking a representation of custom items (via custom model data). This is what the data class `CustomItemIdentifier` is for! + +```kotlin +val identifier = CustomItemIdentifier(itemStack) +// or +val identifier = CustomItemIdentifier(1001, Material.IRON_NUGGET) + +// get an itemstack with the custom model data applied +val stack = identifier.itemStack +``` + +> Any questions? Feel free to contact me! \ No newline at end of file diff --git a/src/main/kotlin/net/axay/kspigot/extensions/GeneralExtensions.kt b/src/main/kotlin/net/axay/kspigot/extensions/GeneralExtensions.kt new file mode 100644 index 00000000..a00fced4 --- /dev/null +++ b/src/main/kotlin/net/axay/kspigot/extensions/GeneralExtensions.kt @@ -0,0 +1,19 @@ +package net.axay.kspigot.extensions + +import org.bukkit.Bukkit +import org.bukkit.entity.Player + +/** + * @see Bukkit.getOnlinePlayers + */ +val onlinePlayers: Collection get() = Bukkit.getOnlinePlayers() + +/** + * @see Bukkit.getServer + */ +val server by lazy { Bukkit.getServer() } + +/** + * @see Bukkit.getPluginManager + */ +val pluginManager by lazy { Bukkit.getPluginManager() } \ No newline at end of file diff --git a/src/main/kotlin/net/axay/kspigot/extensions/bukkit/EntityExtensions.kt b/src/main/kotlin/net/axay/kspigot/extensions/bukkit/EntityExtensions.kt new file mode 100644 index 00000000..ffc877a5 --- /dev/null +++ b/src/main/kotlin/net/axay/kspigot/extensions/bukkit/EntityExtensions.kt @@ -0,0 +1,84 @@ +package net.axay.kspigot.extensions.bukkit + +import net.axay.kspigot.extensions.onlinePlayers +import net.axay.kspigot.main.KSpigot +import org.bukkit.Material +import org.bukkit.attribute.Attribute +import org.bukkit.entity.Damageable +import org.bukkit.entity.Entity +import org.bukkit.entity.LivingEntity +import org.bukkit.entity.Player + +/** + * Checks if the entity is completely in water. + */ +val LivingEntity.isInWater: Boolean get() = isFeetInWater && isHeadInWater + +/** + * Checks if the entities' head is in water. + */ +val LivingEntity.isHeadInWater: Boolean get() = this.eyeLocation.block.type == Material.WATER + +/** + * Checks if the entities' feet are in water. + */ +val Entity.isFeetInWater: Boolean get() = this.location.block.type == Material.WATER + +/** + * Checks if the entity stands on solid ground. + */ +val Entity.isGroundSolid: Boolean get() = this.location.add(0.0, -0.01, 0.0).block.type.isSolid + +/** + * Kills the damageable. + */ +fun Damageable.kill() { + health = 0.0 +} + +/** + * Sets the entities' health to the max possible value. + * @throws NullPointerException if the entity does not have a max health value + */ +fun LivingEntity.heal() { + health = getAttribute(Attribute.GENERIC_MAX_HEALTH)?.value + ?: throw NullPointerException("The entity does not have a max health value!") +} + +/** + * Sets the players' foodLevel to the + * max possible value. + */ +fun Player.feed() { + foodLevel = 20 +} + +/** + * Sets the players' saturation to the + * current max possible value. + */ +fun Player.saturate() { + saturation = foodLevel.toFloat() +} + +/** + * Feeds and saturates the player. + */ +fun Player.feedSaturate() { + foodLevel = 20 + saturation = 20f +} + +/** + * Hides the player for all [onlinePlayers]. + */ +fun Player.disappear(kSpigot: KSpigot) { + onlinePlayers.filter { it != this }.forEach { it.hidePlayer(kSpigot, this) } +} + +/** + * Shows the player for all [onlinePlayers]. + */ +fun Player.appear(kSpigot: KSpigot) { + onlinePlayers.filter { it != this }.forEach { it.showPlayer(kSpigot, this) } +} \ No newline at end of file diff --git a/src/main/kotlin/net/axay/kspigot/extensions/bukkit/VectorExtensions.kt b/src/main/kotlin/net/axay/kspigot/extensions/bukkit/VectorExtensions.kt deleted file mode 100644 index 1c7795cc..00000000 --- a/src/main/kotlin/net/axay/kspigot/extensions/bukkit/VectorExtensions.kt +++ /dev/null @@ -1,5 +0,0 @@ -package net.axay.kspigot.extensions.bukkit - -import org.bukkit.util.Vector - -val Vector.isFinite: Boolean get() = x.isFinite() && y.isFinite() && z.isFinite() \ No newline at end of file diff --git a/src/main/kotlin/net/axay/kspigot/extensions/entities/EntityExtensions.kt b/src/main/kotlin/net/axay/kspigot/extensions/entities/EntityExtensions.kt deleted file mode 100644 index 52d80d5a..00000000 --- a/src/main/kotlin/net/axay/kspigot/extensions/entities/EntityExtensions.kt +++ /dev/null @@ -1,8 +0,0 @@ -package net.axay.kspigot.extensions.entities - -import org.bukkit.Material -import org.bukkit.entity.Entity - -val Entity.isInWater: Boolean get() = this.location.block.type == Material.WATER - -val Entity.isGroundSolid: Boolean get() = this.location.add(0.0, -0.01, 0.0).block.type.isSolid \ No newline at end of file diff --git a/src/main/kotlin/net/axay/kspigot/extensions/events/InteractEventExtensions.kt b/src/main/kotlin/net/axay/kspigot/extensions/events/InteractEventExtensions.kt index 1128c6b2..5d374177 100644 --- a/src/main/kotlin/net/axay/kspigot/extensions/events/InteractEventExtensions.kt +++ b/src/main/kotlin/net/axay/kspigot/extensions/events/InteractEventExtensions.kt @@ -28,14 +28,18 @@ val PlayerInteractEntityEvent.interactItem: ItemStack? @UnsafeImplementation val PlayerInteractEvent.clickedBlockExceptAir: Block? get() { - val p: Player = this.player - return when (this.action) { - Action.RIGHT_CLICK_BLOCK -> this.clickedBlock - Action.RIGHT_CLICK_AIR -> { + return clickedBlock ?: kotlin.run { + return@run if (this.action == Action.RIGHT_CLICK_AIR) { + + val p: Player = this.player + + // check for sight blocking entities for (nearbyEntity: Entity in p.getNearbyEntities(5.0, 5.0, 5.0)) - if (p.hasLineOfSight(nearbyEntity)) return null + if (p.hasLineOfSight(nearbyEntity)) return@run null + + // get first block in line of sight which is not air p.getLineOfSight(null, 5).find { block -> !block.type.isAir } - } - else -> null + + } else null } } diff --git a/src/main/kotlin/net/axay/kspigot/runnables/KRunnableHolder.kt b/src/main/kotlin/net/axay/kspigot/runnables/KSpigotRunnables.kt similarity index 81% rename from src/main/kotlin/net/axay/kspigot/runnables/KRunnableHolder.kt rename to src/main/kotlin/net/axay/kspigot/runnables/KSpigotRunnables.kt index d1dce4f9..a487e2f2 100644 --- a/src/main/kotlin/net/axay/kspigot/runnables/KRunnableHolder.kt +++ b/src/main/kotlin/net/axay/kspigot/runnables/KSpigotRunnables.kt @@ -44,14 +44,14 @@ fun bukkitRunnable( if (sync) { if (delay != null && delay >= 1) - Bukkit.getScheduler().runTaskLater(kSpigot.plugin, mergedRunnable, delay) + Bukkit.getScheduler().runTaskLater(kSpigot, mergedRunnable, delay) else - Bukkit.getScheduler().runTask(kSpigot.plugin, mergedRunnable) + Bukkit.getScheduler().runTask(kSpigot, mergedRunnable) } else { if (delay != null && delay >= 1) - Bukkit.getScheduler().runTaskLaterAsynchronously(kSpigot.plugin, mergedRunnable, delay) + Bukkit.getScheduler().runTaskLaterAsynchronously(kSpigot, mergedRunnable, delay) else - Bukkit.getScheduler().runTaskAsynchronously(kSpigot.plugin, mergedRunnable) + Bukkit.getScheduler().runTaskAsynchronously(kSpigot, mergedRunnable) } } else if (howoften > 1) { @@ -91,16 +91,16 @@ fun bukkitRunnable( kSpigot.kRunnableHolder.runnableEndCallbacks[bukkitRunnable] = endCallback if (sync) - bukkitRunnable.runTaskTimer(kSpigot.plugin, realDelay, realPeriod) + bukkitRunnable.runTaskTimer(kSpigot, realDelay, realPeriod) else - bukkitRunnable.runTaskTimerAsynchronously(kSpigot.plugin, realDelay, realPeriod) + bukkitRunnable.runTaskTimerAsynchronously(kSpigot, realDelay, realPeriod) } } fun bukkitSync(kSpigot: KSpigot, runnable: () -> Unit) - = Bukkit.getScheduler().runTask(kSpigot.plugin, runnable) + = Bukkit.getScheduler().runTask(kSpigot, runnable) fun bukkitAsync(kSpigot: KSpigot, runnable: () -> Unit) - = Bukkit.getScheduler().runTaskAsynchronously(kSpigot.plugin, runnable) \ No newline at end of file + = Bukkit.getScheduler().runTaskAsynchronously(kSpigot, runnable) \ No newline at end of file diff --git a/src/main/kotlin/net/axay/kspigot/utils/KSpigotGeometry.kt b/src/main/kotlin/net/axay/kspigot/utils/KSpigotGeometry.kt new file mode 100644 index 00000000..e599c03d --- /dev/null +++ b/src/main/kotlin/net/axay/kspigot/utils/KSpigotGeometry.kt @@ -0,0 +1,67 @@ +@file:Suppress("unused") + +package net.axay.kspigot.utils + +import org.bukkit.Location +import org.bukkit.util.Vector + +/** + * LOCATION + */ + +// INCREASE +// all +infix fun Location.increase(distance: Number) = add(distance, distance, distance) +infix fun Location.increase(vec: Vector) = add(vec) +// single +infix fun Location.increaseX(distance: Number) = add(distance, 0.0, 0.0) +infix fun Location.increaseY(distance: Number) = add(0.0, distance, 0.0) +infix fun Location.increaseZ(distance: Number) = add(0.0, 0.0, distance) +// pair +infix fun Location.increaseXY(distance: Number) = add(distance, distance, 0.0) +infix fun Location.increaseYZ(distance: Number) = add(0.0, distance, distance) +infix fun Location.increaseXZ(distance: Number) = add(distance, 0.0, distance) + +// REDUCE +// all +infix fun Location.reduce(distance: Number) = substract(distance, distance, distance) +infix fun Location.reduce(vec: Vector) = subtract(vec) +// single +infix fun Location.reduceX(distance: Number) = substract(distance, 0.0, 0.0) +infix fun Location.reduceY(distance: Number) = substract(0.0, distance, 0.0) +infix fun Location.reduceZ(distance: Number) = substract(0.0, 0.0, distance) +// pair +infix fun Location.reduceXY(distance: Number) = substract(distance, distance, 0.0) +infix fun Location.reduceYZ(distance: Number) = substract(0.0, distance, distance) +infix fun Location.reduceXZ(distance: Number) = substract(distance, 0.0, distance) + +// extensions + +fun Location.add(x: Number, y: Number, z: Number) = add(x.toDouble(), y.toDouble(), z.toDouble()) +fun Location.substract(x: Number, y: Number, z: Number) = subtract(x.toDouble(), y.toDouble(), z.toDouble()) + +// operator functions +operator fun Location.plus(vec: Vector) = add(vec) +operator fun Location.minus(vec: Vector) = subtract(vec) +operator fun Location.plus(loc: Location) = add(loc) +operator fun Location.minus(loc: Location) = subtract(loc) + +/* + VECTOR + */ + +val Vector.isFinite: Boolean get() = x.isFinite() && y.isFinite() && z.isFinite() + +// fast construct +fun vec(x: Number = 0.0, y: Number = 0.0, z: Number = 0.0) = Vector(x.toDouble(), y.toDouble(), z.toDouble()) +fun vecXY(x: Number, y: Number) = vec(x, y) +fun vecXZ(x: Number, z: Number) = vec(x, z = z) +fun vecYZ(y: Number, z: Number) = vec(y = y, z = z) +fun vecX(x: Number) = vec(x) +fun vecY(y: Number) = vec(y = y) +fun vecZ(z: Number) = vec(z = z) + +// operator functions +operator fun Vector.plus(vec: Vector) = add(vec) +operator fun Vector.minus(vec: Vector) = subtract(vec) +operator fun Vector.times(vec: Vector) = multiply(vec) \ No newline at end of file diff --git a/src/main/kotlin/net/axay/kspigot/utils/RegisterableCommand.kt b/src/main/kotlin/net/axay/kspigot/utils/RegisterableCommand.kt index 8b0e6b9c..bf38fe32 100644 --- a/src/main/kotlin/net/axay/kspigot/utils/RegisterableCommand.kt +++ b/src/main/kotlin/net/axay/kspigot/utils/RegisterableCommand.kt @@ -14,7 +14,7 @@ interface RegisterableCommand : CommandExecutor { * false if not */ fun registerCommand(commandName: String, kSpigot: KSpigot): Boolean { - kSpigot.plugin.getCommand(commandName)?.let { + kSpigot.getCommand(commandName)?.let { it.setExecutor(this) if (this is TabCompleter) it.tabCompleter = this diff --git a/src/main/kotlin/net/axay/kspigot/utils/RegisterableListener.kt b/src/main/kotlin/net/axay/kspigot/utils/RegisterableListener.kt index 1dac2525..512c88e6 100644 --- a/src/main/kotlin/net/axay/kspigot/utils/RegisterableListener.kt +++ b/src/main/kotlin/net/axay/kspigot/utils/RegisterableListener.kt @@ -10,6 +10,6 @@ interface RegisterableListener : Listener { * Registers this listener * for the given instance of [kSpigot]. */ - fun registerListener(kSpigot: KSpigot) = Bukkit.getPluginManager().registerEvents(this, kSpigot.plugin) + fun registerListener(kSpigot: KSpigot) = Bukkit.getPluginManager().registerEvents(this, kSpigot) } \ No newline at end of file