diff --git a/hub/.idea/compiler.xml b/hub/.idea/compiler.xml
new file mode 100644
index 0000000..61d818a
--- /dev/null
+++ b/hub/.idea/compiler.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hub/.idea/encodings.xml b/hub/.idea/encodings.xml
new file mode 100644
index 0000000..15a15b2
--- /dev/null
+++ b/hub/.idea/encodings.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/hub/.idea/hub.iml b/hub/.idea/hub.iml
new file mode 100644
index 0000000..641679f
--- /dev/null
+++ b/hub/.idea/hub.iml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hub/.idea/misc.xml b/hub/.idea/misc.xml
new file mode 100644
index 0000000..32e1ef9
--- /dev/null
+++ b/hub/.idea/misc.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hub/.idea/workspace.xml b/hub/.idea/workspace.xml
new file mode 100644
index 0000000..a8de78c
--- /dev/null
+++ b/hub/.idea/workspace.xml
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1565448624260
+
+
+ 1565448624260
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hub/hub.iml b/hub/hub.iml
new file mode 100644
index 0000000..3231cf9
--- /dev/null
+++ b/hub/hub.iml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/hub/pom.xml b/hub/pom.xml
new file mode 100644
index 0000000..a547b05
--- /dev/null
+++ b/hub/pom.xml
@@ -0,0 +1,135 @@
+
+
+ 4.0.0
+
+ cc.fyre
+ hub
+ 1.0-SNAPSHOT
+
+
+ 1.3.41
+
+
+
+ src/main/kotlin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ ${kotlin.version}
+
+
+ compile
+ compile
+
+ compile
+
+
+
+
+
+
+
+
+
+ test-compile
+ test-compile
+
+ test-compile
+
+
+
+
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.5.1
+
+
+
+ default-compile
+ none
+
+
+
+ default-testCompile
+ none
+
+
+ java-compile
+ compile
+ compile
+
+
+ java-test-compile
+ test-compile
+ testCompile
+
+
+
+
+ 1.8
+
+
+ org.projectlombok
+ lombok
+ 1.16.16
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 2.6
+
+
+ make-assembly
+ package
+ single
+
+ false
+
+ jar-with-dependencies
+
+
+
+
+
+
+
+
+
+
+
+ net.hylist
+ spigot-server
+ 1.7.10-R0.1-SNAPSHOT
+ provided
+
+
+
+ cc.fyre.stark
+ bukkit
+ 1.3-SNAPSHOT
+ provided
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+ 1.3.41
+
+
+ org.mongodb
+ mongo-java-driver
+ 3.10.2
+
+
+
+
+
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/Hub.kt b/hub/src/main/kotlin/cc/fyre/hub/Hub.kt
new file mode 100644
index 0000000..b5a4fa6
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/Hub.kt
@@ -0,0 +1,88 @@
+package cc.fyre.hub
+
+import cc.fyre.hub.cosmetic.CosmeticListeners
+import cc.fyre.hub.cosmetic.Cosmetics
+import cc.fyre.hub.cosmetic.CosmeticsTickRunnable
+import cc.fyre.hub.listener.HubListeners
+import cc.fyre.hub.listener.PreventionListeners
+import cc.fyre.hub.mongo.Mongo
+import cc.fyre.hub.mongo.MongoCredentials
+import cc.fyre.hub.scoreboard.HubScoreGetter
+import org.bukkit.entity.ExperienceOrb
+import org.bukkit.entity.Item
+import org.bukkit.entity.LivingEntity
+import org.bukkit.entity.Player
+import org.bukkit.plugin.java.JavaPlugin
+
+class Hub : JavaPlugin() {
+
+ lateinit var mongo: Mongo
+ lateinit var cosmetics: Cosmetics
+
+ override fun onEnable() {
+ instance = this
+
+ saveDefaultConfig()
+ loadDatabase()
+ loadEngineAdapters()
+ registerListeners()
+
+ cosmetics = Cosmetics()
+
+ server.scheduler.runTaskTimerAsynchronously(this, CosmeticsTickRunnable(), 1L, 1L)
+
+ cleanupWorld()
+ }
+
+ private fun loadDatabase() {
+ val builder = MongoCredentials.Builder()
+ .host(config.getString("Mongo.Host"))
+ .port(config.getInt("Mongo.Port"))
+
+ if (config.contains("Mongo.Authentication.Username")) {
+ builder.username(config.getString("Mongo.Authentication.Username"))
+ }
+
+ if (config.contains("Mongo.Authentication.Password")) {
+ builder.password(config.getString("Mongo.Authentication.Password"))
+ }
+
+ mongo = Mongo("hub")
+ mongo.load(builder.build())
+ }
+
+ private fun loadEngineAdapters() {
+ Stark.instance.tabEngine.layoutProvider = null
+
+ Stark.instance.scoreboardEngine.configuration = ScoreboardConfiguration(
+ TitleGetter.forStaticString("&6&lKihar &f&lNetwork"),
+ HubScoreGetter()
+ )
+ }
+
+ private fun registerListeners() {
+ val pluginManager = server.pluginManager
+ pluginManager.registerEvents(HubListeners(), this)
+ pluginManager.registerEvents(CosmeticListeners(), this)
+ pluginManager.registerEvents(PreventionListeners(), this)
+ }
+
+ private fun cleanupWorld() {
+ server.worlds[0].time = 4000
+
+ for (entity in server.worlds[0].entities) {
+ if (entity is Player) {
+ continue
+ }
+
+ if (entity is LivingEntity || entity is Item || entity is ExperienceOrb) {
+ entity.remove()
+ }
+ }
+ }
+
+ companion object {
+ lateinit var instance: Hub
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/HubItems.kt b/hub/src/main/kotlin/cc/fyre/hub/HubItems.kt
new file mode 100644
index 0000000..00080a8
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/HubItems.kt
@@ -0,0 +1,25 @@
+package cc.fyre.hub
+
+import cc.fyre.stark.util.ItemBuilder
+import org.bukkit.ChatColor
+import org.bukkit.Material
+
+object HubItems {
+
+ val SELECTOR = ItemBuilder.of(Material.WATCH)
+ .name("${ChatColor.YELLOW}${ChatColor.BOLD}Server Selector")
+ .build()
+
+ val COSMETICS = ItemBuilder.of(Material.FEATHER)
+ .name("${ChatColor.LIGHT_PURPLE}${ChatColor.BOLD}Cosmetics")
+ .build()
+
+ val ENDER_BUTT = ItemBuilder.of(Material.ENDER_PEARL)
+ .name("${ChatColor.YELLOW}${ChatColor.BOLD}Enderbutt")
+ .build()
+
+ val EMOTE_BOX = ItemBuilder.of(Material.CHEST)
+ .name("${ChatColor.GREEN}${ChatColor.BOLD}Emote Box")
+ .build()
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/HubLang.kt b/hub/src/main/kotlin/cc/fyre/hub/HubLang.kt
new file mode 100644
index 0000000..401e29f
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/HubLang.kt
@@ -0,0 +1,33 @@
+package cc.fyre.hub
+
+import org.apache.commons.lang.StringUtils
+import org.bukkit.ChatColor
+
+object HubLang {
+
+ /**
+ * `&7&l» ` - Arrow used on the left side of item display names
+ * Named left arrow due to its usage on the left side of items, despite the fact
+ * the arrow is actually pointing to the right.
+ */
+ val LEFT_ARROW = ChatColor.BLUE.toString() + "» "
+
+ val LEFT_ARROW_NAKED = "»"
+
+ /**
+ * ` &7&l«` - Arrow used on the right side of item display names
+ * Named right arrow due to its usage on the right side of items, despite the fact
+ * the arrow is actually pointing to the left.
+ */
+ val RIGHT_ARROW = ChatColor.BLUE.toString() + " «"
+
+ val RIGHT_ARROW_NAKED = "«"
+
+ /**
+ * Solid line which almost entirely spans the (default) Minecraft
+ * chat box. 53 is chosen for no reason other than its width being
+ * almost equal to that of the chat box.
+ */
+ val LONG_LINE = ChatColor.STRIKETHROUGH.toString() + StringUtils.repeat("-", 53)
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/Cosmetic.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/Cosmetic.kt
new file mode 100644
index 0000000..1d89cfe
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/Cosmetic.kt
@@ -0,0 +1,35 @@
+package cc.fyre.hub.cosmetic
+
+import org.bukkit.entity.Player
+import org.bukkit.event.Listener
+import org.bukkit.inventory.ItemStack
+
+interface Cosmetic : Listener {
+
+ fun getIcon(): ItemStack
+
+ fun getName(): String
+
+ fun getDescription(): List
+
+ fun getPermission(): String
+
+ fun hiddenIfNotPermitted(): Boolean {
+ return false
+ }
+
+ fun canBeToggled(): Boolean {
+ return true
+ }
+
+ fun onEnable(player: Player) {}
+
+ fun onDisable(player: Player) {}
+
+ fun onTick(player: Player) {}
+
+ fun getSerializedName(): String {
+ return getName().replace(" ", "_")
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticCategory.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticCategory.kt
new file mode 100644
index 0000000..b107215
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticCategory.kt
@@ -0,0 +1,20 @@
+package cc.fyre.hub.cosmetic
+
+import org.bukkit.entity.Player
+import org.bukkit.material.MaterialData
+
+interface CosmeticCategory {
+
+ fun getIcon(): MaterialData
+
+ fun getName(): String
+
+ fun getCosmetics(): List
+
+ fun getAccessableCosmetics(player: Player): List {
+ return getCosmetics()
+ .filter { cosmetic -> player.hasPermission(cosmetic.getPermission()) }
+ .toList()
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticListeners.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticListeners.kt
new file mode 100644
index 0000000..c1827f6
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticListeners.kt
@@ -0,0 +1,25 @@
+package cc.fyre.hub.cosmetic
+
+import cc.fyre.hub.Hub
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.player.PlayerJoinEvent
+
+class CosmeticListeners : Listener {
+
+ @EventHandler
+ fun onPlayerJoinEvent(event: PlayerJoinEvent) {
+ Hub.instance.server.scheduler.runTaskAsynchronously(Hub.instance) {
+ val profile = Hub.instance.cosmetics.loadProfile(event.player.uniqueId)
+
+ Hub.instance.cosmetics.categories.forEach { category ->
+ category.getAccessableCosmetics(event.player).forEach { cosmetic ->
+ if (profile.isCosmeticEnabled(cosmetic) || !cosmetic.canBeToggled()) {
+ cosmetic.onEnable(event.player)
+ }
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/Cosmetics.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/Cosmetics.kt
new file mode 100644
index 0000000..2a59fe0
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/Cosmetics.kt
@@ -0,0 +1,108 @@
+package cc.fyre.hub.cosmetic
+
+import cc.fyre.hub.Hub
+import cc.fyre.hub.cosmetic.categories.HiddenCosmeticCategory
+import cc.fyre.hub.cosmetic.categories.ParticlesCosmeticCategory
+import cc.fyre.hub.cosmetic.categories.WearablesCosmeticCategory
+import cc.fyre.hub.cosmetic.menu.CosmeticCategoriesMenu
+import com.mongodb.client.MongoCollection
+import com.mongodb.client.model.ReplaceOptions
+import org.bson.Document
+import org.bukkit.ChatColor
+import org.bukkit.entity.Player
+import java.util.*
+
+class Cosmetics {
+
+ private val collection: MongoCollection = Hub.instance.mongo.database.getCollection("profiles")
+
+ val categories: List = arrayListOf(
+ WearablesCosmeticCategory(),
+ ParticlesCosmeticCategory(),
+ HiddenCosmeticCategory()
+ )
+
+ val profiles: MutableMap = hashMapOf()
+
+ init {
+ categories.forEach { category ->
+ category.getCosmetics().forEach { cosmetic ->
+ Hub.instance.server.pluginManager.registerEvents(cosmetic, Hub.instance)
+ }
+ }
+ }
+
+ fun openMenu(player: Player) {
+ if (player.hasPermission("hub.cosmetics")) {
+ CosmeticCategoriesMenu().openMenu(player)
+ } else {
+ player.sendMessage("${ChatColor.RED}You don't own any cosmetics. Purchase a rank on our store ${ChatColor.BOLD}store.kihar.net ${ChatColor.RED}to get access to cosmetics.")
+ }
+ }
+
+ fun isCosmeticEnabled(player: Player, cosmetic: Cosmetic): Boolean {
+ val profile = profiles[player.uniqueId]
+ return profile?.isCosmeticEnabled(cosmetic) ?: false
+ }
+
+ fun toggleCosmetic(player: Player, cosmetic: Cosmetic) {
+ val profile = profiles[player.uniqueId]
+
+ if (profile != null) {
+ profile.toggleCosmetic(cosmetic)
+
+ if (profile.isCosmeticEnabled(cosmetic)) {
+ cosmetic.onEnable(player)
+ } else {
+ cosmetic.onDisable(player)
+ }
+
+ // disable other cosmetics in the same category
+ for (category in Hub.instance.cosmetics.categories) {
+ if (!category.getCosmetics().contains(cosmetic)) {
+ continue
+ }
+
+ for (enabledCheck in category.getCosmetics()) {
+ if (enabledCheck != cosmetic && profile.isCosmeticEnabled(enabledCheck)) {
+ profile.toggleCosmetic(enabledCheck)
+ }
+ }
+ }
+
+ Hub.instance.server.scheduler.runTaskAsynchronously(Hub.instance) {
+ saveProfile(player.uniqueId, profile)
+ }
+ }
+ }
+
+ fun loadProfile(uuid: UUID): CosmeticsProfile {
+ val profile = CosmeticsProfile()
+ val document = collection.find(Document("uuid", uuid.toString())).first()
+
+ if (document != null) {
+ categories.forEach { category ->
+ category.getCosmetics().forEach { cosmetic ->
+ if (document.containsKey(cosmetic.getSerializedName())) {
+ profile.map[cosmetic.getSerializedName()] = document.getBoolean(cosmetic.getSerializedName())
+ }
+ }
+ }
+ }
+
+ profiles[uuid] = profile
+
+ return profile
+ }
+
+ private fun saveProfile(uuid: UUID, profile: CosmeticsProfile) {
+ val document = Document("uuid", uuid.toString())
+
+ profile.map.forEach { (t, u) ->
+ document[t] = u
+ }
+
+ collection.replaceOne(Document("uuid", uuid.toString()), document, ReplaceOptions().upsert(true))
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticsProfile.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticsProfile.kt
new file mode 100644
index 0000000..f5d7007
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticsProfile.kt
@@ -0,0 +1,23 @@
+package cc.fyre.hub.cosmetic
+
+class CosmeticsProfile {
+
+ val map: MutableMap = hashMapOf()
+
+ fun isCosmeticEnabled(cosmetic: Cosmetic): Boolean {
+ if (map.containsKey(cosmetic.getSerializedName())) {
+ return map[cosmetic.getSerializedName()]!!
+ }
+
+ return false
+ }
+
+ fun toggleCosmetic(cosmetic: Cosmetic) {
+ if (map.containsKey(cosmetic.getSerializedName())) {
+ map[cosmetic.getSerializedName()] = !map[cosmetic.getSerializedName()]!!
+ } else {
+ map[cosmetic.getSerializedName()] = true
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticsTickRunnable.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticsTickRunnable.kt
new file mode 100644
index 0000000..52172a1
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/CosmeticsTickRunnable.kt
@@ -0,0 +1,23 @@
+package cc.fyre.hub.cosmetic
+
+import cc.fyre.hub.Hub
+
+/**
+ * Every cosmetic should calculate their tick executions
+ * based on the tick interval of this runnable.
+ */
+class CosmeticsTickRunnable : Runnable {
+
+ override fun run() {
+ Hub.instance.server.onlinePlayers.forEach { player ->
+ Hub.instance.cosmetics.categories.forEach { category ->
+ category.getCosmetics().forEach { cosmetic ->
+ if (Hub.instance.cosmetics.isCosmeticEnabled(player, cosmetic)) {
+ cosmetic.onTick(player)
+ }
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/HiddenCosmeticCategory.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/HiddenCosmeticCategory.kt
new file mode 100644
index 0000000..1326281
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/HiddenCosmeticCategory.kt
@@ -0,0 +1,27 @@
+package cc.fyre.hub.cosmetic.categories
+
+import cc.fyre.hub.cosmetic.Cosmetic
+import cc.fyre.hub.cosmetic.CosmeticCategory
+import cc.fyre.hub.cosmetic.categories.hidden.EmotesBoxCosmetic
+import org.bukkit.Material
+import org.bukkit.material.MaterialData
+
+class HiddenCosmeticCategory : CosmeticCategory {
+
+ private val cosmetics = arrayListOf(
+ EmotesBoxCosmetic()
+ )
+
+ override fun getIcon(): MaterialData {
+ return MaterialData(Material.AIR)
+ }
+
+ override fun getName(): String {
+ return "Hidden"
+ }
+
+ override fun getCosmetics(): List {
+ return ArrayList(cosmetics)
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/ParticlesCosmeticCategory.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/ParticlesCosmeticCategory.kt
new file mode 100644
index 0000000..17848d5
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/ParticlesCosmeticCategory.kt
@@ -0,0 +1,31 @@
+package cc.fyre.hub.cosmetic.categories
+
+import cc.fyre.hub.cosmetic.Cosmetic
+import cc.fyre.hub.cosmetic.CosmeticCategory
+import cc.fyre.hub.cosmetic.categories.particles.WaterRings
+import cc.fyre.hub.cosmetic.categories.particles.GodModeCosmetic
+import cc.fyre.hub.cosmetic.categories.particles.LavaRings
+import org.bukkit.Material
+import org.bukkit.material.MaterialData
+
+class ParticlesCosmeticCategory : CosmeticCategory {
+
+ private val cosmetics = arrayListOf(
+ GodModeCosmetic(),
+ LavaRings(),
+ WaterRings()
+ )
+
+ override fun getIcon(): MaterialData {
+ return MaterialData(Material.RECORD_4)
+ }
+
+ override fun getName(): String {
+ return "Particles"
+ }
+
+ override fun getCosmetics(): List {
+ return ArrayList(cosmetics)
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/WearablesCosmeticCategory.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/WearablesCosmeticCategory.kt
new file mode 100644
index 0000000..93316e2
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/WearablesCosmeticCategory.kt
@@ -0,0 +1,36 @@
+package cc.fyre.hub.cosmetic.categories
+
+import cc.fyre.hub.cosmetic.Cosmetic
+import cc.fyre.hub.cosmetic.CosmeticCategory
+import cc.fyre.hub.cosmetic.categories.wearables.RankArmourCosmetic
+import org.bukkit.Color
+import org.bukkit.Material
+import org.bukkit.material.MaterialData
+
+class WearablesCosmeticCategory : CosmeticCategory {
+
+ private val cosmetics = arrayListOf(
+ RankArmourCosmetic("Owner", "rank.owner", true, Color.fromRGB(156, 10, 8)),
+ RankArmourCosmetic("Manager", "rank.manager", true, Color.fromRGB(196, 14, 47)),
+ RankArmourCosmetic("Admin", "rank.admin", true, Color.fromRGB(209, 35, 29)),
+ RankArmourCosmetic("Mod", "rank.moderator", true, Color.PURPLE),
+ RankArmourCosmetic("T-Mod", "rank.trial-mod", true, Color.fromRGB(15, 214, 214)),
+ RankArmourCosmetic("Kihar", "rank.kihar", false, Color.fromRGB(0,170,170)),
+ RankArmourCosmetic("Platinum", "rank.platinum", false, Color.fromRGB(252, 144, 3)),
+ RankArmourCosmetic("Premium", "rank.premium", false, Color.fromRGB(85,85,255)),
+ RankArmourCosmetic("Basic", "rank.basic", false, Color.fromRGB(23, 227, 77))
+ )
+
+ override fun getIcon(): MaterialData {
+ return MaterialData(Material.PUMPKIN)
+ }
+
+ override fun getName(): String {
+ return "Wearables"
+ }
+
+ override fun getCosmetics(): List {
+ return ArrayList(cosmetics)
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/EmotesBoxCosmetic.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/EmotesBoxCosmetic.kt
new file mode 100644
index 0000000..30d75c2
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/EmotesBoxCosmetic.kt
@@ -0,0 +1,39 @@
+package cc.fyre.hub.cosmetic.categories.hidden
+
+import cc.fyre.hub.HubItems
+import cc.fyre.hub.cosmetic.Cosmetic
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.inventory.ItemStack
+
+/**
+ * Gives a player the [cc.fyre.hub.HubItems.EMOTE_BOX] item,
+ * which opens a menu that allows players to display emotes.
+ */
+class EmotesBoxCosmetic : Cosmetic {
+
+ override fun getIcon(): ItemStack {
+ return ItemStack(Material.CHEST)
+ }
+
+ override fun getName(): String {
+ return "Emotes Box"
+ }
+
+ override fun getDescription(): List {
+ return emptyList()
+ }
+
+ override fun getPermission(): String {
+ return "hub.cosmetics.emote-box"
+ }
+
+ override fun canBeToggled(): Boolean {
+ return false
+ }
+
+ override fun onEnable(player: Player) {
+ player.inventory.setItem(7, HubItems.EMOTE_BOX)
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/emote/Emote.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/emote/Emote.kt
new file mode 100644
index 0000000..6a88445
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/emote/Emote.kt
@@ -0,0 +1,16 @@
+package cc.fyre.hub.cosmetic.categories.hidden.emote
+
+import org.bukkit.Location
+import org.bukkit.material.MaterialData
+
+interface Emote {
+
+ fun getDisplayName(): String
+
+ fun getDescription(): List
+
+ fun getIcon(): MaterialData
+
+ fun playEffect(location: Location)
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/emote/Emotes.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/emote/Emotes.kt
new file mode 100644
index 0000000..3b173a5
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/emote/Emotes.kt
@@ -0,0 +1,11 @@
+package cc.fyre.hub.cosmetic.categories.hidden.emote
+
+import cc.fyre.hub.cosmetic.categories.hidden.emote.impl.HeartEmote
+
+object Emotes {
+
+ val emotes = arrayListOf(
+ HeartEmote()
+ )
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/emote/impl/HeartEmote.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/emote/impl/HeartEmote.kt
new file mode 100644
index 0000000..b77f812
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/hidden/emote/impl/HeartEmote.kt
@@ -0,0 +1,29 @@
+package cc.fyre.hub.cosmetic.categories.hidden.emote.impl
+
+import cc.fyre.hub.cosmetic.categories.hidden.emote.Emote
+import cc.fyre.hub.util.ParticleMeta
+import cc.fyre.hub.util.ParticleUtil
+import org.bukkit.Effect
+import org.bukkit.Location
+import org.bukkit.Material
+import org.bukkit.material.MaterialData
+
+class HeartEmote : Emote {
+
+ override fun getDisplayName(): String {
+ return "Heart"
+ }
+
+ override fun getDescription(): List {
+ return arrayListOf("&6Perform a &c❤ &6emote.")
+ }
+
+ override fun getIcon(): MaterialData {
+ return MaterialData(Material.RED_ROSE)
+ }
+
+ override fun playEffect(location: Location) {
+ ParticleUtil.sendsParticleToAll(ParticleMeta(location, Effect.HEART))
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/particles/GodModeCosmetic.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/particles/GodModeCosmetic.kt
new file mode 100644
index 0000000..b4bec71
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/particles/GodModeCosmetic.kt
@@ -0,0 +1,32 @@
+package cc.fyre.hub.cosmetic.categories.particles
+
+import cc.fyre.hub.cosmetic.Cosmetic
+import org.bukkit.Material
+import org.bukkit.inventory.ItemStack
+
+/**
+ * Displays particles under the player's feet that makes
+ * it seem like the player is flying.
+ */
+class GodModeCosmetic : Cosmetic {
+
+ override fun getIcon(): ItemStack {
+ return ItemStack(Material.FEATHER)
+ }
+
+ override fun getName(): String {
+ return "God Mode"
+ }
+
+ override fun getDescription(): List {
+ return arrayListOf(
+ "&6Appear as if you're floating",
+ "&6on a cloud. (Look down)"
+ )
+ }
+
+ override fun getPermission(): String {
+ return "hub.cosmetics.god-mode"
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/particles/LavaRings.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/particles/LavaRings.kt
new file mode 100644
index 0000000..c9a343e
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/particles/LavaRings.kt
@@ -0,0 +1,65 @@
+package cc.fyre.hub.cosmetic.categories.particles
+
+import cc.fyre.hub.cosmetic.Cosmetic
+import cc.fyre.hub.util.ParticleMeta
+import cc.fyre.hub.util.ParticleUtil
+import org.bukkit.Effect
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.player.PlayerQuitEvent
+import org.bukkit.inventory.ItemStack
+import java.util.*
+
+class LavaRings : Cosmetic {
+
+ private val ticks = hashMapOf()
+
+ override fun getIcon(): ItemStack {
+ return ItemStack(Material.RECORD_4)
+ }
+
+ override fun getName(): String {
+ return "Lava Rings"
+ }
+
+ override fun getDescription(): List {
+ return arrayListOf(
+ "&6Surround yourself with",
+ "&6rings made of lava."
+ )
+ }
+
+ override fun getPermission(): String {
+ return "rank.platinum"
+ }
+
+ override fun onTick(player: Player) {
+ var ticks = this.ticks.putIfAbsent(player.uniqueId, 0) ?: 0
+
+ if (ticks >= 32) {
+ ticks = -1
+ }
+
+ this.ticks[player.uniqueId] = ticks + 1
+
+ val location = player.location.clone().add(0.1, 0.0, 0.1)
+ val angle = ticks * ((2 * Math.PI) / 32)
+ val cos = Math.cos(angle)
+ val sin = Math.sin(angle)
+
+ val bottomRingLocation = location.clone().add(0.8 * cos, 0.6, 0.8 * sin)
+ val topRingLocation = location.clone().add(0.8 * cos, 1.4, 0.8 * sin)
+
+ for (i in 0 until 5) {
+ ParticleUtil.sendsParticleToAll(ParticleMeta(bottomRingLocation, Effect.LAVADRIP))
+ ParticleUtil.sendsParticleToAll(ParticleMeta(topRingLocation, Effect.LAVADRIP))
+ }
+ }
+
+ @EventHandler
+ fun onPlayerQuitEvent(event: PlayerQuitEvent) {
+ ticks.remove(event.player.uniqueId)
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/particles/WaterRings.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/particles/WaterRings.kt
new file mode 100644
index 0000000..1a75865
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/particles/WaterRings.kt
@@ -0,0 +1,84 @@
+package cc.fyre.hub.cosmetic.categories.particles
+
+import cc.fyre.hub.cosmetic.Cosmetic
+import cc.fyre.hub.util.ParticleMeta
+import cc.fyre.hub.util.ParticleUtil
+import org.bukkit.Effect
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.player.PlayerQuitEvent
+import org.bukkit.inventory.ItemStack
+import java.util.*
+
+class WaterRings : Cosmetic {
+
+ private val ticks = hashMapOf()
+
+ override fun getIcon(): ItemStack {
+ return ItemStack(Material.RECORD_12)
+ }
+
+ override fun getName(): String {
+ return "Water Rings"
+ }
+
+ override fun getDescription(): List {
+ return arrayListOf(
+ "&6Surround yourself with",
+ "&6rings made of water."
+ )
+ }
+
+ override fun getPermission(): String {
+ return "rank.premium"
+ }
+
+ override fun onTick(player: Player) {
+ var ticks = this.ticks.putIfAbsent(player.uniqueId, 0) ?: 0
+
+ if (ticks >= 40) {
+ ticks = -1
+ }
+
+ this.ticks[player.uniqueId] = ticks + 1
+
+ val location = player.location.clone().add(0.1, 0.0, 0.1)
+ val angle = ticks * ((2 * Math.PI) / 40)
+ val cos = Math.cos(angle)
+ val sin = Math.sin(angle)
+
+ val particleMetaList = arrayListOf()
+
+ particleMetaList.add(ParticleMeta(
+ location.clone().add(1.0 * cos, 0.5 + (1.0 * cos), 1.0 * sin),
+ Effect.WATERDRIP
+ ))
+
+ particleMetaList.add(ParticleMeta(
+ location.clone().add(1.0 * cos, 1.0 + (1.0 * cos), 1.0 * sin),
+ Effect.WATERDRIP
+ ))
+
+ particleMetaList.add(ParticleMeta(
+ location.clone().add(1.0 * cos, 1.5 + (1.0 * cos), 1.0 * sin),
+ Effect.WATERDRIP
+ ))
+
+ ParticleUtil.sendsParticleToAll(*particleMetaList.toTypedArray())
+
+// val xSlantedRingLocation = location.clone().add(1.1 * cos, 0.9 + (0.55 * cos), 1.1 * sin)
+// val zSlantedRingLocation = location.clone().add(1.1 * cos, 0.9 + (1.1 * sin), 1.1 * sin)
+
+// ParticleUtil.sendsParticleToAll(ParticleMeta(verticalRingLocation, Effect.COLOURED_DUST, 255.0F, 0.0F, 0.0F, 1.0F, 0))
+// ParticleUtil.sendsParticleToAll(ParticleMeta(horizontalRingLocation, Effect.COLOURED_DUST, 255.0F, 0.0F, 0.0F, 1.0F, 0))
+// ParticleUtil.sendsParticleToAll(ParticleMeta(xSlantedRingLocation, Effect.COLOURED_DUST, 255.0F, 0.0F, 0.0F, 1.0F, 0))
+// ParticleUtil.sendsParticleToAll(ParticleMeta(zSlantedRingLocation, Effect.COLOURED_DUST, 255.0F, 0.0F, 0.0F, 1.0F, 0))
+ }
+
+ @EventHandler
+ fun onPlayerQuitEvent(event: PlayerQuitEvent) {
+ ticks.remove(event.player.uniqueId)
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/wearables/RankArmourCosmetic.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/wearables/RankArmourCosmetic.kt
new file mode 100644
index 0000000..b352289
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/categories/wearables/RankArmourCosmetic.kt
@@ -0,0 +1 @@
+package cc.fyre.hub.cosmetic.categories.wearables
import cc.fyre.hub.cosmetic.Cosmetic
import org.bukkit.Color
import org.bukkit.Material
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.LeatherArmorMeta
/**
* Equips the player with colored armour based on their rank.
*/
class RankArmourCosmetic(private val rankName: String, private val permission: String, private val hidden: Boolean, val color: Color) : Cosmetic {
override fun getIcon(): ItemStack {
val itemStack = ItemStack(Material.LEATHER_HELMET)
val itemMeta = itemStack.itemMeta as LeatherArmorMeta
itemMeta.color = color
itemStack.itemMeta = itemMeta
return itemStack
}
override fun getName(): String {
return "$rankName Rank Armour"
}
override fun getDescription(): List {
return arrayListOf(
"&6Get a set of armour matching",
"&6your rank's color."
)
}
override fun getPermission(): String {
return permission
}
override fun hiddenIfNotPermitted(): Boolean {
return hidden
}
override fun onEnable(player: Player) {
val armour = arrayOf(
ItemStack(Material.LEATHER_HELMET),
ItemStack(Material.LEATHER_CHESTPLATE),
ItemStack(Material.LEATHER_LEGGINGS),
ItemStack(Material.LEATHER_BOOTS)
)
for (armourPiece in armour) {
val meta = armourPiece.itemMeta as LeatherArmorMeta
meta.color = color
armourPiece.itemMeta = meta
}
player.inventory.armorContents = armour
player.updateInventory()
player.closeInventory()
}
override fun onDisable(player: Player) {
player.inventory.armorContents = arrayOfNulls(4)
player.updateInventory()
player.closeInventory()
}
}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticButton.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticButton.kt
new file mode 100644
index 0000000..7eb4b56
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticButton.kt
@@ -0,0 +1,73 @@
+package cc.fyre.hub.cosmetic.menu
+
+import cc.fyre.hub.Hub
+import cc.fyre.hub.cosmetic.Cosmetic
+import cc.fyre.hub.cosmetic.categories.wearables.RankArmourCosmetic
+import cc.fyre.stark.engine.menu.Button
+import org.bukkit.ChatColor
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.event.inventory.ClickType
+import org.bukkit.inventory.meta.ItemMeta
+import org.bukkit.inventory.meta.LeatherArmorMeta
+
+class CosmeticButton(private val cosmetic: Cosmetic) : Button() {
+
+ override fun getMaterial(player: Player): Material? {
+ return cosmetic.getIcon().type
+ }
+
+ override fun getDamageValue(player: Player): Byte {
+ return cosmetic.getIcon().data.data
+ }
+
+ override fun applyMetadata(player: Player, itemMeta: ItemMeta): ItemMeta? {
+ if (cosmetic is RankArmourCosmetic) {
+ (itemMeta as LeatherArmorMeta).color = cosmetic.color
+ }
+
+ return itemMeta
+ }
+
+ override fun getName(player: Player): String? {
+ return if (player.hasPermission(cosmetic.getPermission())) {
+ val color = if (Hub.instance.cosmetics.isCosmeticEnabled(player, cosmetic)) {
+ ChatColor.GREEN.toString()
+ } else {
+ ChatColor.RED.toString()
+ }
+
+ color + ChatColor.BOLD + cosmetic.getName()
+ } else {
+ "${ChatColor.RED}${ChatColor.BOLD}" + cosmetic.getName()
+ }
+ }
+
+ override fun getDescription(player: Player): List? {
+ val description = arrayListOf()
+ description.add("")
+
+ if (player.hasPermission(cosmetic.getPermission())) {
+ for (line in cosmetic.getDescription()) {
+ description.add(line)
+ }
+
+ description.add("")
+ description.add("${ChatColor.YELLOW}Click to toggle this cosmetic.")
+ } else {
+ description.add("${ChatColor.RED}${ChatColor.BOLD}COSMETIC LOCKED")
+ description.add("${ChatColor.RED}You don't have access to")
+ description.add("${ChatColor.RED}this cosmetic. To get access")
+ description.add("${ChatColor.RED}purchase a rank on our store.")
+ }
+
+ return description
+ }
+
+ override fun clicked(player: Player, slot: Int, clickType: ClickType) {
+ if (player.hasPermission(cosmetic.getPermission())) {
+ Hub.instance.cosmetics.toggleCosmetic(player, cosmetic)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticCategoriesMenu.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticCategoriesMenu.kt
new file mode 100644
index 0000000..56dcc8c
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticCategoriesMenu.kt
@@ -0,0 +1,31 @@
+package cc.fyre.hub.cosmetic.menu
+
+import cc.fyre.hub.Hub
+import cc.fyre.stark.engine.menu.Button
+import cc.fyre.stark.engine.menu.Menu
+import org.bukkit.Material
+import org.bukkit.entity.Player
+
+class CosmeticCategoriesMenu : Menu() {
+
+ init {
+ placeholder = true
+ updateAfterClick = true
+ }
+
+ override fun getTitle(player: Player): String {
+ return "Cosmetics Menu"
+ }
+
+ override fun getButtons(player: Player): Map {
+ val buttons = hashMapOf()
+
+ buttons[11] = CosmeticCategoryButton(Hub.instance.cosmetics.categories[0])
+ buttons[15] = CosmeticCategoryButton(Hub.instance.cosmetics.categories[1])
+
+ buttons[26] = Button.placeholder(Material.STAINED_GLASS_PANE, 15.toByte(), " ")
+
+ return buttons
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticCategoryButton.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticCategoryButton.kt
new file mode 100644
index 0000000..786cddb
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticCategoryButton.kt
@@ -0,0 +1,48 @@
+package cc.fyre.hub.cosmetic.menu
+
+import cc.fyre.hub.cosmetic.CosmeticCategory
+import cc.fyre.stark.engine.menu.Button
+import org.bukkit.ChatColor
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.event.inventory.ClickType
+
+class CosmeticCategoryButton(private val category: CosmeticCategory) : Button() {
+
+ override fun getMaterial(player: Player): Material? {
+ return category.getIcon().itemType
+ }
+
+ override fun getDamageValue(player: Player): Byte {
+ return category.getIcon().data
+ }
+
+ override fun getName(player: Player): String? {
+ return "${ChatColor.YELLOW}${ChatColor.BOLD}${category.getName()}"
+ }
+
+ override fun getDescription(player: Player): List? {
+ val description = arrayListOf()
+ description.add("")
+
+ val cosmetics = category.getCosmetics()
+
+ cosmetics.forEach {
+ if (player.hasPermission(it.getPermission())) {
+ description.add("${ChatColor.GRAY}* ${ChatColor.GREEN}${it.getName()}")
+ } else {
+ description.add("${ChatColor.GRAY}* ${ChatColor.RED}${it.getName()}")
+ }
+ }
+
+ description.add("")
+ description.add("${ChatColor.YELLOW}Click to browse this category.")
+
+ return description
+ }
+
+ override fun clicked(player: Player, slot: Int, clickType: ClickType) {
+ CosmeticsMenu(category).openMenu(player)
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticsMenu.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticsMenu.kt
new file mode 100644
index 0000000..1e78a93
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/CosmeticsMenu.kt
@@ -0,0 +1,59 @@
+package cc.fyre.hub.cosmetic.menu
+
+import cc.fyre.hub.cosmetic.CosmeticCategory
+import cc.fyre.stark.engine.menu.Button
+import cc.fyre.stark.engine.menu.Menu
+import org.bukkit.Material
+import org.bukkit.entity.Player
+
+class CosmeticsMenu(private val category: CosmeticCategory) : Menu() {
+
+ init {
+ placeholder = true
+ updateAfterClick = true
+ }
+
+ override fun getTitle(player: Player): String {
+ return "Cosmetics : ${category.getName()}"
+ }
+
+ override fun getButtons(player: Player): Map {
+ val buttons = hashMapOf()
+
+ var slotIndex = 0
+
+ category.getCosmetics().forEach { cosmetic ->
+ if (cosmetic.canBeToggled()) {
+ buttons[slots[slotIndex++]] = CosmeticButton(cosmetic)
+ }
+ }
+
+ slotIndex--
+
+ // fill remaining row slots
+ for (i in 1 .. 8) {
+ if (!slots.contains(slots[slotIndex] + i)) {
+ break
+ }
+
+ buttons[slots[slotIndex] + i] = Button.placeholder(Material.AIR)
+ }
+
+ // make bottom glass border row
+ buttons[getSlot(9, Math.ceil((slotIndex + 2) / 9.0).toInt())] = Button.placeholder(Material.STAINED_GLASS_PANE, 15, " ")
+
+ return buttons
+ }
+
+ companion object {
+ private val slots = arrayListOf()
+
+ init {
+ slots.addAll(10 .. 16)
+ slots.addAll(19 .. 25)
+ slots.addAll(28 .. 34)
+ slots.addAll(37 .. 43)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/EmoteBoxMenu.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/EmoteBoxMenu.kt
new file mode 100644
index 0000000..8dc413c
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/EmoteBoxMenu.kt
@@ -0,0 +1,25 @@
+package cc.fyre.hub.cosmetic.menu
+
+import cc.fyre.hub.cosmetic.categories.hidden.emote.Emotes
+import cc.fyre.stark.engine.menu.Button
+import cc.fyre.stark.engine.menu.Menu
+import org.bukkit.entity.Player
+
+class EmoteBoxMenu : Menu() {
+
+ override fun getTitle(player: Player): String {
+ return "Emote Box"
+ }
+
+ override fun getButtons(player: Player): Map {
+ val buttons = hashMapOf()
+
+ var i = 0
+ for (emote in Emotes.emotes) {
+ buttons[i++] = EmoteButton(emote)
+ }
+
+ return buttons
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/EmoteButton.kt b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/EmoteButton.kt
new file mode 100644
index 0000000..e679a38
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/cosmetic/menu/EmoteButton.kt
@@ -0,0 +1,43 @@
+package cc.fyre.hub.cosmetic.menu
+
+import cc.fyre.hub.cosmetic.categories.hidden.emote.Emote
+import cc.fyre.stark.engine.menu.Button
+import org.bukkit.ChatColor
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.event.inventory.ClickType
+
+class EmoteButton(private val emote: Emote) : Button() {
+
+ override fun getMaterial(player: Player): Material? {
+ return emote.getIcon().itemType
+ }
+
+ override fun getDamageValue(player: Player): Byte {
+ return emote.getIcon().data
+ }
+
+ override fun getName(player: Player): String? {
+ return "${ChatColor.YELLOW}${emote.getDisplayName()}"
+ }
+
+ override fun getDescription(player: Player): List? {
+ val description = arrayListOf()
+ description.add("")
+
+ for (line in emote.getDescription()) {
+ description.add(line)
+ }
+
+ description.add("")
+ description.add("${ChatColor.YELLOW}Click to perform this emote.")
+
+ return description
+ }
+
+ override fun clicked(player: Player, slot: Int, clickType: ClickType) {
+ player.closeInventory()
+ emote.playEffect(player.eyeLocation.clone().add(player.location.direction))
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/listener/HubListeners.kt b/hub/src/main/kotlin/cc/fyre/hub/listener/HubListeners.kt
new file mode 100644
index 0000000..098c6cf
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/listener/HubListeners.kt
@@ -0,0 +1,136 @@
+package cc.fyre.hub.listener
+
+import cc.fyre.hub.Hub
+import cc.fyre.hub.HubItems
+import cc.fyre.hub.cosmetic.menu.EmoteBoxMenu
+import cc.fyre.hub.menu.ServerSelectorMenu
+import mkremins.fanciful.FancyMessage
+import org.bukkit.ChatColor
+import org.bukkit.Sound
+import org.bukkit.entity.EnderPearl
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.block.Action
+import org.bukkit.event.entity.ProjectileLaunchEvent
+import org.bukkit.event.player.PlayerInteractEvent
+import org.bukkit.event.player.PlayerJoinEvent
+import org.spigotmc.event.entity.EntityDismountEvent
+
+class HubListeners : Listener {
+
+ companion object {
+ private val PREFIX = FancyMessage("* ").color(ChatColor.GRAY).style(ChatColor.BOLD)
+ }
+
+ @EventHandler
+ fun onPlayerJoinEvent(event: PlayerJoinEvent) {
+ val player = event.player
+
+ PREFIX.clone()
+ .then("Welcome to the ").color(ChatColor.YELLOW).style(ChatColor.BOLD)
+ .then("Kihar Network").color(ChatColor.GOLD).style(ChatColor.BOLD).then("!").color(ChatColor.YELLOW).style(ChatColor.BOLD)
+ .send(player)
+
+ PREFIX.clone()
+ .then("Website: ").color(ChatColor.YELLOW).style(ChatColor.BOLD)
+ .then("https://www.kihar.net").color(ChatColor.LIGHT_PURPLE).style(ChatColor.BOLD).link("https://www.kihar.net")
+ .send(player)
+
+ PREFIX.clone()
+ .then("Discord: ").color(ChatColor.YELLOW).style(ChatColor.BOLD)
+ .then("https://discord.gg/4V7phmq").color(ChatColor.LIGHT_PURPLE).style(ChatColor.BOLD).link("https://discord.gg/4V7phmq")
+ .send(player)
+
+ PREFIX.clone()
+ .then("Teamspeak: ").color(ChatColor.YELLOW).style(ChatColor.BOLD)
+ .then("ts.kihar.net").color(ChatColor.LIGHT_PURPLE).style(ChatColor.BOLD).link("ts.kihar.net")
+ .send(player)
+
+ PREFIX.clone()
+ .then("Twitter: ").color(ChatColor.YELLOW).style(ChatColor.BOLD)
+ .then("@KiharMC").color(ChatColor.LIGHT_PURPLE).style(ChatColor.BOLD).link("https://www.twitter.com/@KiharMC")
+ .send(player)
+
+ player.inventory.armorContents = arrayOfNulls(4)
+ player.inventory.contents = arrayOfNulls(36)
+ player.inventory.heldItemSlot = 0
+ player.health = 20.0
+ player.foodLevel = 20
+ player.exp = 0.0F
+
+ player.inventory.setItem(0, HubItems.SELECTOR)
+ player.inventory.setItem(4, HubItems.COSMETICS)
+ player.inventory.setItem(8, HubItems.ENDER_BUTT)
+
+ player.updateInventory()
+
+ player.teleport(Hub.instance.server.worlds[0].spawnLocation)
+ }
+
+ @EventHandler
+ fun onPlayerInteractEvent(event: PlayerInteractEvent) {
+ if (event.action == Action.RIGHT_CLICK_AIR || event.action == Action.RIGHT_CLICK_BLOCK) {
+ val itemInHand = event.player.itemInHand
+ if (itemInHand != null) {
+ when (itemInHand) {
+ HubItems.SELECTOR -> {
+ ServerSelectorMenu().openMenu(event.player)
+ }
+ HubItems.COSMETICS -> {
+ Hub.instance.cosmetics.openMenu(event.player)
+ }
+ HubItems.ENDER_BUTT -> {
+ if (event.action == Action.RIGHT_CLICK_BLOCK) {
+ event.isCancelled = true
+ }
+ }
+ HubItems.EMOTE_BOX -> {
+ EmoteBoxMenu().openMenu(event.player)
+ }
+ }
+ }
+ }
+ }
+
+ @EventHandler
+ fun onProjectileLaunchEvent(event: ProjectileLaunchEvent) {
+ if (event.entity is EnderPearl && event.entity.shooter is Player) {
+ val player = event.entity.shooter as Player
+
+ if (player.vehicle != null) {
+ val vehicle = player.vehicle
+ player.eject()
+ vehicle.remove()
+ }
+
+ event.entity.velocity = player.location.direction.normalize().multiply(1.5F)
+ event.entity.passenger = player
+
+ player.world.playSound(player.location, Sound.ENDERMAN_TELEPORT, 1.0F, 1.0F)
+
+ // wait 1 tick before giving item back
+ Hub.instance.server.scheduler.runTaskLater(Hub.instance, {
+ player.inventory.setItem(8, HubItems.ENDER_BUTT)
+ player.updateInventory()
+ }, 1L)
+ }
+ }
+
+ @EventHandler
+ fun onEntityDismountEvent(event: EntityDismountEvent) {
+ if (event.entity is Player && event.entity.vehicle is EnderPearl) {
+ val vehicle = event.entity.vehicle
+
+ event.entity.eject()
+
+ // wait 1 tick before removing entity
+ Hub.instance.server.scheduler.runTaskLater(Hub.instance, {
+ if (!vehicle.isDead) {
+ vehicle.remove()
+ }
+ }, 1L)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/listener/PreventionListeners.kt b/hub/src/main/kotlin/cc/fyre/hub/listener/PreventionListeners.kt
new file mode 100644
index 0000000..e2838ea
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/listener/PreventionListeners.kt
@@ -0,0 +1,188 @@
+package cc.fyre.hub.listener
+
+import org.bukkit.GameMode
+import org.bukkit.Material
+import org.bukkit.entity.EntityType
+import org.bukkit.entity.Player
+import org.bukkit.event.EventHandler
+import org.bukkit.event.Listener
+import org.bukkit.event.block.*
+import org.bukkit.event.entity.*
+import org.bukkit.event.inventory.CraftItemEvent
+import org.bukkit.event.inventory.InventoryClickEvent
+import org.bukkit.event.inventory.PrepareItemCraftEvent
+import org.bukkit.event.player.*
+import org.bukkit.event.weather.ThunderChangeEvent
+import org.bukkit.event.weather.WeatherChangeEvent
+
+class PreventionListeners : Listener {
+
+ @EventHandler
+ fun onPlayerJoin(event: PlayerJoinEvent) {
+ event.joinMessage = null
+ }
+
+ @EventHandler
+ fun onPlayerKick(event: PlayerKickEvent) {
+ event.leaveMessage = null
+ }
+
+ @EventHandler
+ fun onPlayerQuit(event: PlayerQuitEvent) {
+ event.quitMessage = null
+ }
+
+ @EventHandler
+ fun onProjectileHit(event: ProjectileHitEvent) {
+ if (event.entityType == EntityType.ARROW) {
+ event.entity.remove()
+ }
+ }
+
+ @EventHandler
+ fun onEntityDamageEvent(event: EntityDamageEvent) {
+ if (event.entity is Player) {
+ event.isCancelled = true
+
+ if (event.cause == EntityDamageEvent.DamageCause.VOID || event.entity.location.y < 0) {
+ event.entity.teleport(event.entity.world.spawnLocation)
+ }
+ }
+ }
+
+ @EventHandler
+ fun onFoodLevelChangeEvent(event: FoodLevelChangeEvent) {
+ event.isCancelled = true
+ }
+
+ @EventHandler
+ fun onPlayerExpChangedEvent(event: PlayerExpChangeEvent) {
+ event.player.exp = 0.0F
+ }
+
+ @EventHandler
+ fun onThunderChange(event: ThunderChangeEvent) {
+ event.isCancelled = true
+ }
+
+ @EventHandler
+ fun onWeatherChange(event: WeatherChangeEvent) {
+ event.isCancelled = true
+ }
+
+ @EventHandler
+ fun onCreatureSpawn(event: CreatureSpawnEvent) {
+ if (!(event.spawnReason == CreatureSpawnEvent.SpawnReason.SPAWNER_EGG ||
+ event.spawnReason == CreatureSpawnEvent.SpawnReason.CUSTOM)) {
+ event.isCancelled = true
+ }
+ }
+
+ @EventHandler
+ fun onPlayerDeath(event: PlayerDeathEvent) {
+ event.deathMessage = null
+ event.droppedExp = 0
+ }
+
+ @EventHandler
+ fun onBlockSpread(event: BlockSpreadEvent) {
+ event.isCancelled = true
+ }
+
+ @EventHandler
+ fun onLeavesDecay(event: LeavesDecayEvent) {
+ event.isCancelled = true
+ }
+
+ @EventHandler
+ fun onBlockFade(event: BlockFadeEvent) {
+ event.isCancelled = true
+ }
+
+ @EventHandler
+ fun onBlockForm(event: BlockFormEvent) {
+ event.isCancelled = true
+ }
+
+ @EventHandler
+ fun onPlayerPortal(event: PlayerPortalEvent) {
+ event.isCancelled = true
+ }
+
+ @EventHandler
+ fun onBlockBreakEvent(event: BlockBreakEvent) {
+ if (!canPerformAction(event.player)) {
+ event.isCancelled = true
+ }
+ }
+
+ @EventHandler
+ fun onBlockPlaceEvent(event: BlockPlaceEvent) {
+ if (!canPerformAction(event.player)) {
+ event.isCancelled = true
+ }
+ }
+
+ @EventHandler
+ fun onPlayerBucketFillEvent(event: PlayerBucketFillEvent) {
+ if (!canPerformAction(event.player)) {
+ event.isCancelled = true
+ }
+ }
+
+ @EventHandler
+ fun onPlayerBucketEmptyEvent(event: PlayerBucketEmptyEvent) {
+ if (!canPerformAction(event.player)) {
+ event.isCancelled = true
+ }
+ }
+
+ @EventHandler
+ fun onPlayerDropItemEvent(event: PlayerDropItemEvent) {
+ if (!canPerformAction(event.player)) {
+ event.isCancelled = true
+ }
+ }
+
+ @EventHandler
+ fun onPlayerPickupItemEvent(event: PlayerPickupItemEvent) {
+ if (!canPerformAction(event.player)) {
+ event.isCancelled = true
+ }
+ }
+
+ @EventHandler
+ fun onInventoryClickEvent(event: InventoryClickEvent) {
+ if (!canPerformAction(event.whoClicked as Player)) {
+ if (event.clickedInventory == event.whoClicked.inventory) {
+ event.isCancelled = true
+ }
+ }
+ }
+
+ @EventHandler
+ fun onPlayerInteract(event: PlayerInteractEvent) {
+ if (event.action == Action.PHYSICAL) {
+ event.isCancelled = true
+ }
+ }
+
+ @EventHandler
+ fun onPrepareCraft(event: PrepareItemCraftEvent) {
+ event.inventory.result = null
+ }
+
+ @EventHandler
+ fun onCraft(event: CraftItemEvent) {
+ event.isCancelled = true
+ }
+
+ private fun canPerformAction(player: Player): Boolean {
+ if (!player.isOp || !player.hasMetadata("Build") || player.gameMode != GameMode.CREATIVE) {
+ return false
+ }
+
+ return true
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/menu/ServerSelectorMenu.kt b/hub/src/main/kotlin/cc/fyre/hub/menu/ServerSelectorMenu.kt
new file mode 100644
index 0000000..fa2d94a
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/menu/ServerSelectorMenu.kt
@@ -0,0 +1,67 @@
+package cc.fyre.hub.menu
+
+import cc.fyre.stark.Stark
+import cc.fyre.stark.engine.menu.Button
+import cc.fyre.stark.engine.menu.Menu
+import org.bukkit.Material
+import org.bukkit.entity.Player
+
+class ServerSelectorMenu : Menu() {
+
+ init {
+ autoUpdate = true
+ updateAfterClick = true
+ }
+
+ override fun getTitle(player: Player): String {
+ return "Select a server to join"
+ }
+
+ override fun getButtons(player: Player): Map {
+ val buttons = hashMapOf()
+
+ buttons[11] = ServerTypeButton(
+ Stark.instance.core.servers.getServerByName("Practice-NA"),
+ ServerTypeMeta(
+ Material.DIAMOND_SWORD,
+ "Practice-NA",
+ arrayListOf(
+ "&7* &61v1s, 2v2s, Team Fights!",
+ "&7* &6Leaderboards, Clan Wars!",
+ "&7* &6Hosted Events, Party Events!"
+ )
+ )
+ )
+
+ buttons[13] = ServerTypeButton(
+ Stark.instance.core.servers.getServerByName("KitMap"),
+ ServerTypeMeta(
+ Material.ENDER_PEARL,
+ "KitMap",
+ arrayListOf(
+ "&7* &65 Minute KOTHs!",
+ "&7* &624/7 PvP, Killstreaks!",
+ "&7* &6Bases, Obstacles!"
+ )
+ )
+ )
+
+ buttons[15] = ServerTypeButton(
+ Stark.instance.core.servers.getServerByName("MineSG"),
+ ServerTypeMeta(
+ Material.CHEST,
+ "MineSG",
+ arrayListOf(
+ "&7* &6Solo and Duo Queues!",
+ "&7* &650 Players Per Game!",
+ "&7* &6Last Man Standing Wins!"
+ )
+ )
+ )
+
+ buttons[26] = Button.placeholder(Material.AIR)
+
+ return buttons
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/menu/ServerTypeButton.kt b/hub/src/main/kotlin/cc/fyre/hub/menu/ServerTypeButton.kt
new file mode 100644
index 0000000..f46190c
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/menu/ServerTypeButton.kt
@@ -0,0 +1,88 @@
+package cc.fyre.hub.menu
+
+import cc.fyre.hub.HubLang
+import cc.fyre.stark.core.server.Server
+import cc.fyre.stark.engine.menu.Button
+import cc.fyre.stark.util.BungeeUtil
+import org.bukkit.ChatColor
+import org.bukkit.Material
+import org.bukkit.entity.Player
+import org.bukkit.event.inventory.ClickType
+import java.util.*
+
+class ServerTypeButton(private val optionalServer: Optional, private val meta: ServerTypeMeta) : Button() {
+
+ override fun getMaterial(player: Player): Material? {
+ return if (optionalServer.isPresent) {
+ val server = optionalServer.get()
+
+ if (server.isOnline()) {
+ meta.material
+ } else {
+ Material.REDSTONE_BLOCK
+ }
+ } else {
+ Material.REDSTONE_BLOCK
+ }
+ }
+
+ override fun getName(player: Player): String? {
+ return if (optionalServer.isPresent) {
+ val server = optionalServer.get()
+
+ if (server.isOnline()) {
+ "${ChatColor.GREEN}${ChatColor.BOLD}${server.serverName}"
+ } else {
+ "${ChatColor.RED}${ChatColor.BOLD}${server.serverName}"
+ }
+ } else {
+ "${ChatColor.RED}${ChatColor.BOLD}${meta.fallbackName}"
+ }
+ }
+
+ override fun getDescription(player: Player): List? {
+ val description = arrayListOf()
+ description.add("")
+
+ for (line in meta.description) {
+ description.add(line)
+ }
+
+ description.add("")
+
+ if (optionalServer.isPresent) {
+ val server = optionalServer.get()
+
+ if (server.isOnline()) {
+ description.add(0, "${ChatColor.YELLOW}Players: ${ChatColor.WHITE}(${server.playerCount}/${server.maxSlots})")
+
+ if (server.whitelisted) {
+ description.add("${ChatColor.YELLOW}This server is whitelisted.")
+ } else {
+ description.add("${ChatColor.GRAY}${HubLang.LEFT_ARROW_NAKED} ${ChatColor.YELLOW}Click to join the queue! ${ChatColor.GRAY}${HubLang.RIGHT_ARROW_NAKED}")
+ }
+ } else {
+ description.add("${ChatColor.YELLOW}This server is offline.")
+ }
+ } else {
+ description.add("${ChatColor.YELLOW}This server is offline.")
+ }
+
+ return description
+ }
+
+ override fun clicked(player: Player, slot: Int, clickType: ClickType) {
+ if (!optionalServer.isPresent || !optionalServer.get().isOnline()) {
+ player.sendMessage("${ChatColor.YELLOW}That server is offline.")
+ } else {
+ val server = optionalServer.get()
+
+ if (server.whitelisted) {
+ player.sendMessage("${ChatColor.YELLOW}That server is whitelisted.")
+ } else {
+ BungeeUtil.sendToServer(player, server.serverName)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/menu/ServerTypeMeta.kt b/hub/src/main/kotlin/cc/fyre/hub/menu/ServerTypeMeta.kt
new file mode 100644
index 0000000..b5ea3f3
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/menu/ServerTypeMeta.kt
@@ -0,0 +1,5 @@
+package cc.fyre.hub.menu
+
+import org.bukkit.Material
+
+data class ServerTypeMeta(val material: Material, val fallbackName: String, val description: List)
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/mongo/Mongo.kt b/hub/src/main/kotlin/cc/fyre/hub/mongo/Mongo.kt
new file mode 100644
index 0000000..2926ae1
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/mongo/Mongo.kt
@@ -0,0 +1,31 @@
+package cc.fyre.hub.mongo
+
+import com.mongodb.MongoClient
+import com.mongodb.MongoClientOptions
+import com.mongodb.MongoCredential
+import com.mongodb.ServerAddress
+import java.io.Closeable
+
+class Mongo(private val dbName: String) : Closeable {
+
+ lateinit var client: MongoClient
+ lateinit var database: com.mongodb.client.MongoDatabase
+
+ fun load(credentials: MongoCredentials) {
+ client = if (credentials.shouldAuthenticate()) {
+ val serverAddress = ServerAddress(credentials.host, credentials.port)
+ val credential = MongoCredential.createCredential(credentials.username!!, "admin", credentials.password!!.toCharArray())
+
+ MongoClient(serverAddress, credential, MongoClientOptions.builder().build())
+ } else {
+ MongoClient(credentials.host, credentials.port)
+ }
+
+ database = client.getDatabase(dbName)
+ }
+
+ override fun close() {
+ client.close()
+ }
+
+}
diff --git a/hub/src/main/kotlin/cc/fyre/hub/mongo/MongoCredentials.kt b/hub/src/main/kotlin/cc/fyre/hub/mongo/MongoCredentials.kt
new file mode 100644
index 0000000..109c967
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/mongo/MongoCredentials.kt
@@ -0,0 +1,41 @@
+package cc.fyre.hub.mongo
+
+data class MongoCredentials(
+ var host: String = "localhost",
+ var port: Int = 27017,
+ var username: String? = null,
+ var password: String? = null) {
+
+ fun shouldAuthenticate(): Boolean {
+ return username != null && (password != null && password!!.isNotEmpty() && password!!.isNotBlank())
+ }
+
+ class Builder {
+ val credentials: MongoCredentials = MongoCredentials()
+
+ fun host(host: String): Builder {
+ credentials.host = host
+ return this
+ }
+
+ fun port(port: Int): Builder {
+ credentials.port = port
+ return this
+ }
+
+ fun username(username: String): Builder {
+ credentials.username = username
+ return this
+ }
+
+ fun password(password: String): Builder {
+ credentials.password = password
+ return this
+ }
+
+ fun build(): MongoCredentials {
+ return credentials
+ }
+ }
+
+}
diff --git a/hub/src/main/kotlin/cc/fyre/hub/scoreboard/HubScoreGetter.kt b/hub/src/main/kotlin/cc/fyre/hub/scoreboard/HubScoreGetter.kt
new file mode 100644
index 0000000..daf77eb
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/scoreboard/HubScoreGetter.kt
@@ -0,0 +1,32 @@
+package cc.fyre.hub.scoreboard
+
+import cc.fyre.stark.Stark
+import cc.fyre.stark.engine.scoreboard.ScoreGetter
+import org.bukkit.ChatColor
+import org.bukkit.entity.Player
+import java.util.*
+
+class HubScoreGetter : ScoreGetter {
+
+ override fun getScores(scores: LinkedList, player: Player) {
+ val profile = Stark.instance.core.getProfileHandler().getByUUID(player.uniqueId)!!
+ val rank = profile.getRank()
+
+ scores.add("&a&7&m--------------------")
+ scores.add("&fYour Rank:")
+
+ if (rank.default) {
+ scores.add("${ChatColor.GREEN}${rank.displayName}")
+ } else {
+ scores.add("${rank.gameColor}${rank.displayName}")
+ }
+
+ scores.add("")
+ scores.add("&fOnline:")
+ scores.add("&6${Stark.instance.core.servers.globalCount}&r")
+ scores.add("")
+ scores.add("&6kihar.net")
+ scores.add("&b&7&m--------------------")
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/util/ParticleMeta.kt b/hub/src/main/kotlin/cc/fyre/hub/util/ParticleMeta.kt
new file mode 100644
index 0000000..5480dbe
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/util/ParticleMeta.kt
@@ -0,0 +1,28 @@
+package cc.fyre.hub.util
+
+import org.bukkit.Effect
+import org.bukkit.Location
+
+data class ParticleMeta(val location: Location, val effect: Effect) {
+
+ constructor(location: Location,
+ effect: Effect,
+ offsetX: Float,
+ offsetY: Float,
+ offsetZ: Float,
+ speed: Float,
+ amount: Int) : this(location, effect) {
+ this.offsetX = offsetX
+ this.offsetY = offsetY
+ this.offsetZ = offsetZ
+ this.speed = speed
+ this.amount = amount
+ }
+
+ var offsetX = 0.0F
+ var offsetY = 0.0F
+ var offsetZ = 0.0F
+ var speed = 1.0F
+ var amount = 1
+
+}
\ No newline at end of file
diff --git a/hub/src/main/kotlin/cc/fyre/hub/util/ParticleUtil.kt b/hub/src/main/kotlin/cc/fyre/hub/util/ParticleUtil.kt
new file mode 100644
index 0000000..3f231d4
--- /dev/null
+++ b/hub/src/main/kotlin/cc/fyre/hub/util/ParticleUtil.kt
@@ -0,0 +1,40 @@
+package cc.fyre.hub.util
+
+import net.minecraft.server.v1_7_R4.PacketPlayOutWorldParticles
+import org.bukkit.Location
+import org.bukkit.craftbukkit.v1_7_R4.CraftWorld
+
+/**
+ * A class to simplify sending particles to players.
+ *
+ * Also created to fix the issue of bukkit sometimes
+ * not sending the particle or sending the particle
+ * in varying speeds / sizes.
+ */
+object ParticleUtil {
+
+ // name x y z offX offY offZ speed count
+ fun sendsParticleToAll(vararg particleMetas: ParticleMeta) {
+ val packets = arrayListOf>()
+
+ for (meta in particleMetas) {
+ packets.add(meta.location to PacketPlayOutWorldParticles(
+ meta.effect.getName(),
+ meta.location.x.toFloat(),
+ meta.location.y.toFloat(),
+ meta.location.z.toFloat(),
+ meta.offsetX,
+ meta.offsetY,
+ meta.offsetZ,
+ meta.speed,
+ meta.amount))
+ }
+
+ packets.forEach { pair ->
+ (pair.first.world as CraftWorld).handle.playerMap.forEachNearby(pair.first.x, pair.first.y, pair.first.z, 64.0, true) { player ->
+ player.playerConnection.sendPacket(pair.second)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/hub/src/main/resources/config.yml b/hub/src/main/resources/config.yml
new file mode 100644
index 0000000..5f2d617
--- /dev/null
+++ b/hub/src/main/resources/config.yml
@@ -0,0 +1,8 @@
+Mongo:
+ Host: "127.0.0.1"
+ Port: 27017
+ DbName: "hub"
+ Authentication:
+ Enabled: false
+ Username: "admin"
+ Password: ""
\ No newline at end of file
diff --git a/hub/src/main/resources/plugin.yml b/hub/src/main/resources/plugin.yml
new file mode 100644
index 0000000..a742099
--- /dev/null
+++ b/hub/src/main/resources/plugin.yml
@@ -0,0 +1,6 @@
+name: Hub
+version: ${project.version}
+description: Hub plugin
+author: joeleoli
+main: cc.fyre.hub.Hub
+depend: [Stark]
\ No newline at end of file
diff --git a/hub/target/classes/config.yml b/hub/target/classes/config.yml
new file mode 100644
index 0000000..5f2d617
--- /dev/null
+++ b/hub/target/classes/config.yml
@@ -0,0 +1,8 @@
+Mongo:
+ Host: "127.0.0.1"
+ Port: 27017
+ DbName: "hub"
+ Authentication:
+ Enabled: false
+ Username: "admin"
+ Password: ""
\ No newline at end of file
diff --git a/hub/target/classes/plugin.yml b/hub/target/classes/plugin.yml
new file mode 100644
index 0000000..a742099
--- /dev/null
+++ b/hub/target/classes/plugin.yml
@@ -0,0 +1,6 @@
+name: Hub
+version: ${project.version}
+description: Hub plugin
+author: joeleoli
+main: cc.fyre.hub.Hub
+depend: [Stark]
\ No newline at end of file