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