├── .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
--------------------------------------------------------------------------------