├── .gitignore ├── README.md ├── pom.xml ├── src └── main │ ├── java │ └── com │ │ └── deanveloper │ │ └── kotlintest │ │ ├── KotlinListener.kt │ │ ├── KotlinPlugin.kt │ │ ├── PlayerData.kt │ │ └── command │ │ ├── AsyncTaskCmd.kt │ │ ├── EchoCmd.kt │ │ ├── PlayerCmd.kt │ │ └── SillyCmd.kt │ └── resources │ ├── config.yml │ └── plugin.yml └── target └── classes ├── config.yml └── plugin.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /src/test 2 | /KotlinPlugin.iml 3 | /.idea 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin makes things easier 2 | Let's take a look at each basic plugin subsystem... 3 | 4 | ----- 5 | ### Listeners 6 | 7 | ##### Null Safety 8 | Listeners are pretty similar to how we do them in Java. Although in some events, `null` is very common and very annoying to handle, a great example being in PlayerInteractEvent. With Kotlin, its null-safety makes everything much easier. The following in Java: 9 | ```Java 10 | String s = "Unnamed"; 11 | if(e.hasItem() && e.getItem().hasItemMeta() && e.getItem().getItemMeta().hasDisplayName()) { 12 | s = e.getItem().getItemMeta().getDisplayName(); 13 | } 14 | ``` 15 | can be condensed all the way down to a single line of code: 16 | ```Kotlin 17 | val s = e.item?.itemMeta?.displayName ?: "Unnamed" 18 | ``` 19 | 20 | ##### Infix Functions 21 | Infix functions makes code look much cleaner. Take a common operation done in PlayerMoveEvent, making it so that the calculation is only done if the player moves a block. Normally what you would see is... 22 | ```Java 23 | Location from = e.getFrom(); 24 | Location to = e.getTo(); 25 | if(!(from.getBlockX() == to.getBlockX() && from.getBlockY() == to.getBlockY() && from.getBlockZ() == to.getBlockZ())) { 26 | e.getPlayer().damage(0.0); 27 | } 28 | ``` 29 | Some people would make it look a bit cleaner by doing this... 30 | ```Java 31 | public boolean equalsBlock(Location from, Location to) { 32 | return from.getBlockX() == to.getBlockX() && from.getBlockY() == to.getBlockY() && from.getBlockZ() == to.getBlockZ() 33 | } 34 | public void onMove(PlayerMoveEvent e) { 35 | if(!equalsBlock(e.getFrom(), e.getTo()) { 36 | e.getPlayer().damage(0.0); 37 | } 38 | } 39 | ``` 40 | But with Kotlin, we can make it look even CLEANER with an infix function! 41 | ```Kotlin 42 | infix fun Location.equalsBlock(other: Location) = 43 | this.blockX == other.blockX && this.blockY == other.blockY && this.blockZ == other.blockZ 44 | 45 | public fun onMove(e: PlayerMoveEvent) { 46 | if(!(e.from equalsBlock e.to)) { 47 | e.player.damage(0.0); 48 | } 49 | } 50 | ``` 51 | 52 | **(File for reference: [KotlinListener.kt](https://github.com/unon1100/KotlinPlugin/blob/master/src/main/java/com/deanveloper/kotlintest/KotlinListener.kt))** 53 | 54 | ------ 55 | ### Commands 56 | 57 | ##### Echo 58 | Commands are now also much easier. An echo command can be made in one line without use of other external libraries (such as google guava's `Joiner` class). In Kotlin, we can just do `sender!!.sendMessage(args?.joinToString(separator=" "))` to echo the arguments back to the sender. 59 | 60 | **(File for reference: [KotlinEchoCmd.kt](https://github.com/unon1100/KotlinPlugin/blob/master/src/main/java/com/deanveloper/kotlintest/KotlinEchoCmd.kt))** 61 | 62 | ##### Safe Casting 63 | Also, Kotlin has safe casting as well. This makes running player-specific funtions on a `CommandSender` very easy! 64 | ```Kotlin 65 | (sender as? Player)?.chat("I just ran the /player command!") ?: sender?.sendMessage("You aren't a player :(") 66 | ``` 67 | **(File for reference: [KotlinPlayerCmd.kt](https://github.com/unon1100/KotlinPlugin/blob/master/src/main/java/com/deanveloper/kotlintest/KotlinPlayerCmd.kt))** 68 | 69 | ----- 70 | ##### Before you say I missed something... 71 | Check out the [wiki](https://github.com/unon1100/KotlinPlugin/wiki) 72 | ### Epilogue 73 | I hope you found this useful! Kotlin is very useful for creating Bukkit plugins, and I hope to see some really cool plugins in the future made with this language. If you found this useful, feel free to star the repo and check it out whenever you want. I'll definitely add more to it in the future! 74 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.deanveloper 8 | BukkitPluginTest 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | spigot-repo 14 | https://hub.spigotmc.org/nexus/content/repositories/snapshots/ 15 | 16 | 17 | jitpack.io 18 | https://jitpack.io 19 | 20 | 21 | 22 | 23 | 24 | org.spigotmc 25 | spigot-api 26 | 1.8.8-R0.1-SNAPSHOT 27 | provided 28 | 29 | 30 | com.github.jkcclemens 31 | khttp 32 | -SNAPSHOT 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/main/java/com/deanveloper/kotlintest/KotlinListener.kt: -------------------------------------------------------------------------------- 1 | package com.deanveloper.kotlintest 2 | 3 | import org.bukkit.Location 4 | import org.bukkit.Material 5 | import org.bukkit.event.EventHandler 6 | import org.bukkit.event.Listener 7 | import org.bukkit.event.block.Action 8 | import org.bukkit.event.player.PlayerInteractEvent 9 | import org.bukkit.event.player.PlayerMoveEvent 10 | 11 | /** 12 | * Example Events 13 | * 14 | * @author Dean B 15 | */ 16 | object KotlinListener : Listener { 17 | 18 | infix fun Location.equalsBlock(other: Location) = 19 | this.blockX == other.blockX && this.blockY == other.blockY && this.blockZ == other.blockZ 20 | 21 | @EventHandler 22 | public fun onStickClick(e: PlayerInteractEvent) { 23 | if(e.action == Action.PHYSICAL) return; //make sure it isn't a pressure plate or tripwire 24 | 25 | if(e.item?.type == Material.STICK) { 26 | e.player.sendMessage("You clicked a stick named [${e.item.itemMeta?.displayName ?: "Unnamed"}]") 27 | } 28 | } 29 | 30 | @EventHandler 31 | public fun onMove(e: PlayerMoveEvent) { 32 | //prevent too many calculations from being calculated by only checking when players change blocks 33 | if(!(e.from equalsBlock e.to)) { // infix function equalsBlock shown above 34 | e.player.damage(0.0); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/com/deanveloper/kotlintest/KotlinPlugin.kt: -------------------------------------------------------------------------------- 1 | package com.deanveloper.kotlintest 2 | 3 | import com.deanveloper.kotlintest.command.EchoCmd 4 | import com.deanveloper.kotlintest.command.PlayerCmd 5 | import com.deanveloper.kotlintest.command.SillyCmd 6 | import com.deanveloper.kotlintest.command.AsyncTaskCmd 7 | import org.bukkit.Bukkit 8 | import org.bukkit.plugin.java.JavaPlugin 9 | 10 | /** 11 | * Example Kotlin Plugin 12 | * 13 | * @author Dean B 14 | */ 15 | class KotlinPlugin: JavaPlugin() { 16 | //while this is singleton, a class must be initialized by Bukkit, so we can't use 'object' 17 | companion object { 18 | var instance: KotlinPlugin? = null 19 | private set; 20 | } 21 | 22 | override fun onEnable() { 23 | getCommand("echo").executor = EchoCmd 24 | getCommand("player").executor = PlayerCmd 25 | getCommand("jarjar").executor = AsyncTaskCmd 26 | getCommand("silly").executor = SillyCmd 27 | Bukkit.getPluginManager().registerEvents(KotlinListener, this) 28 | Bukkit.getPluginManager().registerEvents(PlayerData, this) 29 | 30 | Bukkit.getLogger().info("Config Val: ${config.getString("configVal") ?: "[no val listed]"}") 31 | 32 | instance = this; 33 | 34 | Bukkit.getLogger().info("Enabled!") 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/deanveloper/kotlintest/PlayerData.kt: -------------------------------------------------------------------------------- 1 | package com.deanveloper.kotlintest 2 | 3 | import org.bukkit.entity.Player 4 | import org.bukkit.event.EventHandler 5 | import org.bukkit.event.Listener 6 | import org.bukkit.event.player.PlayerJoinEvent 7 | import org.bukkit.event.player.PlayerQuitEvent 8 | import java.util.* 9 | 10 | /** 11 | * Stores Data for each player on the server 12 | * 13 | * @author Dean B 14 | */ 15 | data class PlayerData(val id: UUID, val name: String){ 16 | var silly: Boolean = false; 17 | companion object: Listener { 18 | private val data = HashMap() 19 | 20 | //allows us to do KotlinPlayerData[id] 21 | operator fun get(id: UUID?) = data[id] 22 | operator fun get(p: Player) = PlayerData[p.uniqueId]!! 23 | 24 | @EventHandler 25 | public fun onJoin(e: PlayerJoinEvent) = PlayerData(e.player.uniqueId, e.player.name) 26 | 27 | @EventHandler 28 | public fun onQuit(e: PlayerQuitEvent) = data.remove(e.player.uniqueId) 29 | } 30 | 31 | init { 32 | data[id] = this 33 | } 34 | } -------------------------------------------------------------------------------- /src/main/java/com/deanveloper/kotlintest/command/AsyncTaskCmd.kt: -------------------------------------------------------------------------------- 1 | package com.deanveloper.kotlintest.command; 2 | 3 | import com.deanveloper.kotlintest.KotlinPlugin 4 | import khttp.get 5 | import org.bukkit.Bukkit 6 | import org.bukkit.command.Command 7 | import org.bukkit.command.CommandExecutor 8 | import org.bukkit.command.CommandSender 9 | 10 | /** 11 | * An example of lambdas in Kotlin 12 | * 13 | * @author Dean B 14 | */ 15 | object AsyncTaskCmd : CommandExecutor { 16 | override fun onCommand(sender: CommandSender, cmd: Command, lbl: String, args: Array): Boolean { 17 | val movie: Int; 18 | try { 19 | movie = Integer.getInteger(args[0]); 20 | assert(movie in 1..7) 21 | } catch (e: Exception) { 22 | return false; 23 | } 24 | Bukkit.getScheduler().runTaskAsynchronously(KotlinPlugin.instance) { 25 | val jarjar = get("http://swapi.co/api/people/36").jsonObject 26 | val films = jarjar.getJSONArray("films"); 27 | if(films.contains("http://swapi.co/api/films/$movie/")) { 28 | sender.sendMessage("That movie was bad, jarjar was in it") 29 | } else { 30 | sender.sendMessage("That movie was fine, jarjar wasn't in it") 31 | } 32 | } 33 | 34 | return true; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/deanveloper/kotlintest/command/EchoCmd.kt: -------------------------------------------------------------------------------- 1 | package com.deanveloper.kotlintest.command 2 | 3 | import org.bukkit.command.Command 4 | import org.bukkit.command.CommandExecutor 5 | import org.bukkit.command.CommandSender 6 | 7 | /** 8 | * Echo Command 9 | * 10 | * @author Dean B 11 | */ 12 | object EchoCmd : CommandExecutor { 13 | override fun onCommand(sender: CommandSender?, cmd: Command?, lbl: String?, args: Array?): Boolean { 14 | sender!!.sendMessage(args?.joinToString(separator=" ")) 15 | 16 | return true 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/com/deanveloper/kotlintest/command/PlayerCmd.kt: -------------------------------------------------------------------------------- 1 | package com.deanveloper.kotlintest.command 2 | 3 | import org.bukkit.command.Command 4 | import org.bukkit.command.CommandExecutor 5 | import org.bukkit.command.CommandSender 6 | import org.bukkit.entity.Player 7 | 8 | /** 9 | * Says if you're a player 10 | * 11 | * @author Dean B 12 | */ 13 | object PlayerCmd : CommandExecutor { 14 | override fun onCommand(sender: CommandSender?, cmd: Command?, lbl: String?, args: Array?): Boolean { 15 | (sender as? Player)?.chat("I just ran the /player command")?: sender?.sendMessage("You aren't a player :(") 16 | 17 | return true; 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/java/com/deanveloper/kotlintest/command/SillyCmd.kt: -------------------------------------------------------------------------------- 1 | package com.deanveloper.kotlintest.command 2 | 3 | import com.deanveloper.kotlintest.PlayerData 4 | import org.bukkit.command.Command 5 | import org.bukkit.command.CommandExecutor 6 | import org.bukkit.command.CommandSender 7 | import org.bukkit.entity.Player 8 | 9 | /** 10 | * Sets a boolean for player data 11 | * 12 | * @author Dean B 13 | */ 14 | object SillyCmd : CommandExecutor { 15 | override fun onCommand(sender: CommandSender, cmd: Command, lbl: String, args: Array): Boolean { 16 | assert(sender is Player) 17 | val data = PlayerData[(sender as Player).uniqueId]!! 18 | data.silly = !data.silly 19 | sender.sendMessage("You are now${if(!data.silly) " not" else ""} silly!") 20 | return false; 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | configVal: woah this is so cool -------------------------------------------------------------------------------- /src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | main: com.deanveloper.kotlintest.KotlinPlugin 2 | author: Dean 3 | version: ${project.version} 4 | 5 | command: 6 | echo: 7 | player: 8 | jarjar: 9 | usage: '/jarjar -------------------------------------------------------------------------------- /target/classes/config.yml: -------------------------------------------------------------------------------- 1 | configVal: woah this is so cool -------------------------------------------------------------------------------- /target/classes/plugin.yml: -------------------------------------------------------------------------------- 1 | main: com.deanveloper.kotlintest.KotlinPlugin 2 | author: Dean 3 | version: ${project.version} 4 | 5 | command: 6 | echo: 7 | player: 8 | jarjar: 9 | usage: '/jarjar --------------------------------------------------------------------------------