mirror of
				https://github.com/GayPizzaSpecifications/foundation.git
				synced 2025-11-04 11:39:39 +00:00 
			
		
		
		
	Major refactoring to use Koin.
This commit is contained in:
		@ -82,6 +82,9 @@ subprojects {
 | 
				
			|||||||
    implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
 | 
					    implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
 | 
				
			||||||
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
 | 
					    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    implementation("io.insert-koin:koin-core:3.1.4")
 | 
				
			||||||
 | 
					    testImplementation("io.insert-koin:koin-test:3.1.4")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Serialization
 | 
					    // Serialization
 | 
				
			||||||
    implementation("com.charleskorn.kaml:kaml:0.38.0")
 | 
					    implementation("com.charleskorn.kaml:kaml:0.38.0")
 | 
				
			||||||
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
 | 
					    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
  // TODO: might be able to ship all dependencies in core? are we duplicating classes in JARs?
 | 
					  // TODO: might be able to ship all dependencies in core? are we duplicating classes in JARs?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  implementation("software.amazon.awssdk:s3:2.17.102")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,27 +1,27 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.core
 | 
					package cloud.kubelet.foundation.core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cloud.kubelet.foundation.core.command.*
 | 
					import cloud.kubelet.foundation.core.abstraction.Feature
 | 
				
			||||||
import cloud.kubelet.foundation.core.devupdate.DevUpdateServer
 | 
					import cloud.kubelet.foundation.core.abstraction.FoundationPlugin
 | 
				
			||||||
import cloud.kubelet.foundation.core.persist.PersistentStore
 | 
					import cloud.kubelet.foundation.core.features.backup.BackupFeature
 | 
				
			||||||
import cloud.kubelet.foundation.core.persist.setAllProperties
 | 
					import cloud.kubelet.foundation.core.features.dev.DevFeature
 | 
				
			||||||
import io.papermc.paper.event.player.AsyncChatEvent
 | 
					import cloud.kubelet.foundation.core.features.player.PlayerFeature
 | 
				
			||||||
import net.kyori.adventure.text.Component
 | 
					import cloud.kubelet.foundation.core.features.stats.StatsFeature
 | 
				
			||||||
import net.kyori.adventure.text.TextComponent
 | 
					import cloud.kubelet.foundation.core.features.update.UpdateFeature
 | 
				
			||||||
import org.bukkit.GameMode
 | 
					import cloud.kubelet.foundation.core.features.world.WorldFeature
 | 
				
			||||||
import org.bukkit.command.CommandExecutor
 | 
					import org.koin.dsl.module
 | 
				
			||||||
import org.bukkit.command.TabCompleter
 | 
					 | 
				
			||||||
import org.bukkit.event.EventHandler
 | 
					 | 
				
			||||||
import org.bukkit.event.Listener
 | 
					 | 
				
			||||||
import org.bukkit.plugin.java.JavaPlugin
 | 
					 | 
				
			||||||
import java.nio.file.Path
 | 
					import java.nio.file.Path
 | 
				
			||||||
import java.time.Instant
 | 
					 | 
				
			||||||
import java.util.concurrent.ConcurrentHashMap
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FoundationCorePlugin : JavaPlugin(), Listener {
 | 
					class FoundationCorePlugin : FoundationPlugin() {
 | 
				
			||||||
  internal val persistentStores = ConcurrentHashMap<String, PersistentStore>()
 | 
					 | 
				
			||||||
  private lateinit var _pluginDataPath: Path
 | 
					  private lateinit var _pluginDataPath: Path
 | 
				
			||||||
  private lateinit var chatLogStore: PersistentStore
 | 
					  override val features: List<Feature>
 | 
				
			||||||
  private lateinit var devUpdateServer: DevUpdateServer
 | 
					    get() = listOf(
 | 
				
			||||||
 | 
					      BackupFeature(),
 | 
				
			||||||
 | 
					      DevFeature(),
 | 
				
			||||||
 | 
					      PlayerFeature(),
 | 
				
			||||||
 | 
					      StatsFeature(),
 | 
				
			||||||
 | 
					      UpdateFeature(),
 | 
				
			||||||
 | 
					      WorldFeature(),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var pluginDataPath: Path
 | 
					  var pluginDataPath: Path
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -38,104 +38,15 @@ class FoundationCorePlugin : JavaPlugin(), Listener {
 | 
				
			|||||||
      _pluginDataPath = value
 | 
					      _pluginDataPath = value
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  override fun module() = module {
 | 
				
			||||||
   * Fetch a persistent store by name. Make sure the name is path-safe, descriptive and consistent across server runs.
 | 
					    single { this@FoundationCorePlugin }
 | 
				
			||||||
   */
 | 
					  }
 | 
				
			||||||
  fun getPersistentStore(name: String) = persistentStores.getOrPut(name) { PersistentStore(this, name) }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun onEnable() {
 | 
					  override fun onEnable() {
 | 
				
			||||||
 | 
					    // Create core plugin directory.
 | 
				
			||||||
    pluginDataPath = dataFolder.toPath()
 | 
					    pluginDataPath = dataFolder.toPath()
 | 
				
			||||||
    val backupPath = pluginDataPath.resolve(BACKUPS_DIRECTORY)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Create Foundation plugin directories.
 | 
					 | 
				
			||||||
    pluginDataPath.toFile().mkdir()
 | 
					    pluginDataPath.toFile().mkdir()
 | 
				
			||||||
    backupPath.toFile().mkdir()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Register this as an event listener.
 | 
					    super.onEnable()
 | 
				
			||||||
    server.pluginManager.registerEvents(this, this)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Register commands.
 | 
					 | 
				
			||||||
    registerCommandExecutor("fbackup", BackupCommand(this, backupPath))
 | 
					 | 
				
			||||||
    registerCommandExecutor("fupdate", UpdateCommand())
 | 
					 | 
				
			||||||
    registerCommandExecutor(listOf("survival", "s"), GamemodeCommand(GameMode.SURVIVAL))
 | 
					 | 
				
			||||||
    registerCommandExecutor(listOf("creative", "c"), GamemodeCommand(GameMode.CREATIVE))
 | 
					 | 
				
			||||||
    registerCommandExecutor(listOf("adventure", "a"), GamemodeCommand(GameMode.ADVENTURE))
 | 
					 | 
				
			||||||
    registerCommandExecutor(listOf("spectator", "sp"), GamemodeCommand(GameMode.SPECTATOR))
 | 
					 | 
				
			||||||
    registerCommandExecutor(listOf("leaderboard", "lb"), LeaderboardCommand())
 | 
					 | 
				
			||||||
    registerCommandExecutor("pstore", PersistentStoreCommand(this))
 | 
					 | 
				
			||||||
    registerCommandExecutor("setspawn", SetSpawnCommand())
 | 
					 | 
				
			||||||
    registerCommandExecutor("spawn", SpawnCommand())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    val log = slF4JLogger
 | 
					 | 
				
			||||||
    log.info("Features:")
 | 
					 | 
				
			||||||
    Util.printFeatureStatus(log, "Backup", BACKUP_ENABLED)
 | 
					 | 
				
			||||||
    chatLogStore = getPersistentStore("chat-logs")
 | 
					 | 
				
			||||||
    devUpdateServer = DevUpdateServer(this)
 | 
					 | 
				
			||||||
    devUpdateServer.enable()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  override fun onDisable() {
 | 
					 | 
				
			||||||
    persistentStores.values.forEach { store -> store.close() }
 | 
					 | 
				
			||||||
    persistentStores.clear()
 | 
					 | 
				
			||||||
    devUpdateServer.disable()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private fun registerCommandExecutor(name: String, executor: CommandExecutor) {
 | 
					 | 
				
			||||||
    registerCommandExecutor(listOf(name), executor)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private fun registerCommandExecutor(names: List<String>, executor: CommandExecutor) {
 | 
					 | 
				
			||||||
    for (name in names) {
 | 
					 | 
				
			||||||
      val command = getCommand(name) ?: throw Exception("Failed to get $name command")
 | 
					 | 
				
			||||||
      command.setExecutor(executor)
 | 
					 | 
				
			||||||
      if (executor is TabCompleter) {
 | 
					 | 
				
			||||||
        command.tabCompleter = executor
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // TODO: Disabling chat reformatting until I do something with it and figure out how to make it
 | 
					 | 
				
			||||||
  //  be less disruptive.
 | 
					 | 
				
			||||||
  /*@EventHandler
 | 
					 | 
				
			||||||
  private fun onChatMessage(e: ChatEvent) {
 | 
					 | 
				
			||||||
    return
 | 
					 | 
				
			||||||
    e.isCancelled = true
 | 
					 | 
				
			||||||
    val name = e.player.displayName()
 | 
					 | 
				
			||||||
    val component = Component.empty()
 | 
					 | 
				
			||||||
      .append(leftBracket)
 | 
					 | 
				
			||||||
      .append(name)
 | 
					 | 
				
			||||||
      .append(rightBracket)
 | 
					 | 
				
			||||||
      .append(Component.text(' '))
 | 
					 | 
				
			||||||
      .append(e.message())
 | 
					 | 
				
			||||||
    server.sendMessage(component)
 | 
					 | 
				
			||||||
  }*/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @EventHandler
 | 
					 | 
				
			||||||
  private fun logOnChatMessage(e: AsyncChatEvent) {
 | 
					 | 
				
			||||||
    val player = e.player
 | 
					 | 
				
			||||||
    val message = e.message()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (message !is TextComponent) {
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    val content = message.content()
 | 
					 | 
				
			||||||
    chatLogStore.create("ChatMessageEvent") {
 | 
					 | 
				
			||||||
      setAllProperties(
 | 
					 | 
				
			||||||
        "timestamp" to Instant.now().toEpochMilli(),
 | 
					 | 
				
			||||||
        "player.id" to player.identity().uuid().toString(),
 | 
					 | 
				
			||||||
        "player.name" to player.name,
 | 
					 | 
				
			||||||
        "message.content" to content
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  companion object {
 | 
					 | 
				
			||||||
    private const val BACKUPS_DIRECTORY = "backups"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private val leftBracket: Component = Component.text('[')
 | 
					 | 
				
			||||||
    private val rightBracket: Component = Component.text(']')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const val BACKUP_ENABLED = true
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -11,10 +11,6 @@ object Util {
 | 
				
			|||||||
  private val whitespace: Component = Component.text(' ')
 | 
					  private val whitespace: Component = Component.text(' ')
 | 
				
			||||||
  private val foundationName: Component = Component.text("Foundation")
 | 
					  private val foundationName: Component = Component.text("Foundation")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun printFeatureStatus(logger: Logger, feature: String?, state: Boolean) {
 | 
					 | 
				
			||||||
    logger.info("{}: {}", feature, if (state) "Enabled" else "Disabled")
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  fun formatSystemMessage(message: String): Component {
 | 
					  fun formatSystemMessage(message: String): Component {
 | 
				
			||||||
    return formatSystemMessage(TextColors.AMARANTH_PINK, message)
 | 
					    return formatSystemMessage(TextColors.AMARANTH_PINK, message)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.core.abstraction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.FoundationCorePlugin
 | 
				
			||||||
 | 
					import org.bukkit.command.CommandExecutor
 | 
				
			||||||
 | 
					import org.bukkit.command.TabCompleter
 | 
				
			||||||
 | 
					import org.bukkit.event.Listener
 | 
				
			||||||
 | 
					import org.koin.core.component.KoinComponent
 | 
				
			||||||
 | 
					import org.koin.core.component.inject
 | 
				
			||||||
 | 
					import org.koin.dsl.module
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class Feature : KoinComponent, Listener {
 | 
				
			||||||
 | 
					  private val plugin by inject<FoundationCorePlugin>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  open fun enable() {}
 | 
				
			||||||
 | 
					  open fun disable() {}
 | 
				
			||||||
 | 
					  open fun module() = module {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected fun registerCommandExecutor(name: String, executor: CommandExecutor) {
 | 
				
			||||||
 | 
					    registerCommandExecutor(listOf(name), executor)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected fun registerCommandExecutor(names: List<String>, executor: CommandExecutor) {
 | 
				
			||||||
 | 
					    for (name in names) {
 | 
				
			||||||
 | 
					      val command = plugin.getCommand(name) ?: throw Exception("Failed to get $name command")
 | 
				
			||||||
 | 
					      command.setExecutor(executor)
 | 
				
			||||||
 | 
					      if (executor is TabCompleter) {
 | 
				
			||||||
 | 
					        command.tabCompleter = executor
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.core.abstraction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.bukkit.plugin.java.JavaPlugin
 | 
				
			||||||
 | 
					import org.koin.core.KoinApplication
 | 
				
			||||||
 | 
					import org.koin.core.context.startKoin
 | 
				
			||||||
 | 
					import org.koin.core.context.stopKoin
 | 
				
			||||||
 | 
					import org.koin.core.module.Module
 | 
				
			||||||
 | 
					import org.koin.dsl.module
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					abstract class FoundationPlugin : JavaPlugin() {
 | 
				
			||||||
 | 
					  private lateinit var pluginModule: Module
 | 
				
			||||||
 | 
					  private lateinit var pluginApplication: KoinApplication
 | 
				
			||||||
 | 
					  protected abstract val features: List<Feature>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun onEnable() {
 | 
				
			||||||
 | 
					    pluginModule = module {
 | 
				
			||||||
 | 
					      single { this@FoundationPlugin }
 | 
				
			||||||
 | 
					      single { server }
 | 
				
			||||||
 | 
					      single { config }
 | 
				
			||||||
 | 
					      single { slF4JLogger }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TODO: If we have another plugin using this class, we may need to use context isolation.
 | 
				
			||||||
 | 
					    //  https://insert-koin.io/docs/reference/koin-core/context-isolation
 | 
				
			||||||
 | 
					    pluginApplication = startKoin {
 | 
				
			||||||
 | 
					      modules(pluginModule)
 | 
				
			||||||
 | 
					      modules(module())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    features.forEach {
 | 
				
			||||||
 | 
					      pluginApplication.modules(it.module())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    features.forEach {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        slF4JLogger.info("Enabling feature: ${it.javaClass.simpleName}")
 | 
				
			||||||
 | 
					        it.enable()
 | 
				
			||||||
 | 
					        server.pluginManager.registerEvents(it, this)
 | 
				
			||||||
 | 
					      } catch (e: Exception) {
 | 
				
			||||||
 | 
					        slF4JLogger.error("Failed to enable feature: ${it.javaClass.simpleName}", e)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun onDisable() {
 | 
				
			||||||
 | 
					    features.forEach {
 | 
				
			||||||
 | 
					      it.disable()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stopKoin()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  protected open fun module() = module {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.core.command
 | 
					package cloud.kubelet.foundation.core.features.backup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cloud.kubelet.foundation.core.FoundationCorePlugin
 | 
					import cloud.kubelet.foundation.core.FoundationCorePlugin
 | 
				
			||||||
import cloud.kubelet.foundation.core.Util
 | 
					import cloud.kubelet.foundation.core.Util
 | 
				
			||||||
@ -26,15 +26,6 @@ class BackupCommand(
 | 
				
			|||||||
  override fun onCommand(
 | 
					  override fun onCommand(
 | 
				
			||||||
    sender: CommandSender, command: Command, label: String, args: Array<String>
 | 
					    sender: CommandSender, command: Command, label: String, args: Array<String>
 | 
				
			||||||
  ): Boolean {
 | 
					  ): Boolean {
 | 
				
			||||||
    if (!FoundationCorePlugin.BACKUP_ENABLED) {
 | 
					 | 
				
			||||||
      sender.sendMessage(
 | 
					 | 
				
			||||||
        Component
 | 
					 | 
				
			||||||
          .text("Backup is not enabled.")
 | 
					 | 
				
			||||||
          .color(TextColor.fromHexString("#FF0000"))
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
      return true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (RUNNING.get()) {
 | 
					    if (RUNNING.get()) {
 | 
				
			||||||
      sender.sendMessage(
 | 
					      sender.sendMessage(
 | 
				
			||||||
        Component
 | 
					        Component
 | 
				
			||||||
@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.core.features.backup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.FoundationCorePlugin
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.abstraction.Feature
 | 
				
			||||||
 | 
					import org.koin.core.component.inject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BackupFeature : Feature() {
 | 
				
			||||||
 | 
					  private val plugin by inject<FoundationCorePlugin>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun enable() {
 | 
				
			||||||
 | 
					    // Create backup directory.
 | 
				
			||||||
 | 
					    val backupPath = plugin.pluginDataPath.resolve(BACKUPS_DIRECTORY)
 | 
				
			||||||
 | 
					    backupPath.toFile().mkdir()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    registerCommandExecutor("fbackup", BackupCommand(plugin, backupPath))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  companion object {
 | 
				
			||||||
 | 
					    private const val BACKUPS_DIRECTORY = "backups"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.core.features.dev
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.FoundationCorePlugin
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.abstraction.Feature
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.devupdate.DevUpdateServer
 | 
				
			||||||
 | 
					import org.koin.core.component.inject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DevFeature : Feature() {
 | 
				
			||||||
 | 
					  private val plugin = inject<FoundationCorePlugin>()
 | 
				
			||||||
 | 
					  private lateinit var devUpdateServer: DevUpdateServer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun enable() {
 | 
				
			||||||
 | 
					    devUpdateServer = DevUpdateServer(plugin.value)
 | 
				
			||||||
 | 
					    devUpdateServer.enable()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun disable() {
 | 
				
			||||||
 | 
					    devUpdateServer.disable()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.core.features.player
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.abstraction.Feature
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.command.GamemodeCommand
 | 
				
			||||||
 | 
					import org.bukkit.GameMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PlayerFeature : Feature() {
 | 
				
			||||||
 | 
					  override fun enable() {
 | 
				
			||||||
 | 
					    registerCommandExecutor(listOf("survival", "s"), GamemodeCommand(GameMode.SURVIVAL))
 | 
				
			||||||
 | 
					    registerCommandExecutor(listOf("creative", "c"), GamemodeCommand(GameMode.CREATIVE))
 | 
				
			||||||
 | 
					    registerCommandExecutor(listOf("adventure", "a"), GamemodeCommand(GameMode.ADVENTURE))
 | 
				
			||||||
 | 
					    registerCommandExecutor(listOf("spectator", "sp"), GamemodeCommand(GameMode.SPECTATOR))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.core.command
 | 
					package cloud.kubelet.foundation.core.features.stats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cloud.kubelet.foundation.core.SortOrder
 | 
					import cloud.kubelet.foundation.core.SortOrder
 | 
				
			||||||
import cloud.kubelet.foundation.core.allPlayerStatisticsOf
 | 
					import cloud.kubelet.foundation.core.allPlayerStatisticsOf
 | 
				
			||||||
@ -1,15 +1,21 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.core.command
 | 
					package cloud.kubelet.foundation.core.features.stats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cloud.kubelet.foundation.core.FoundationCorePlugin
 | 
					 | 
				
			||||||
import org.bukkit.command.Command
 | 
					import org.bukkit.command.Command
 | 
				
			||||||
import org.bukkit.command.CommandExecutor
 | 
					import org.bukkit.command.CommandExecutor
 | 
				
			||||||
import org.bukkit.command.CommandSender
 | 
					import org.bukkit.command.CommandSender
 | 
				
			||||||
import org.bukkit.command.TabCompleter
 | 
					import org.bukkit.command.TabCompleter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class PersistentStoreCommand(private val plugin: FoundationCorePlugin) : CommandExecutor, TabCompleter {
 | 
					class PersistentStoreCommand(
 | 
				
			||||||
 | 
					  private val statsFeature: StatsFeature
 | 
				
			||||||
 | 
					) : CommandExecutor, TabCompleter {
 | 
				
			||||||
  private val allSubCommands = mutableListOf("stats", "sample", "delete-all-entities")
 | 
					  private val allSubCommands = mutableListOf("stats", "sample", "delete-all-entities")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<out String>): Boolean {
 | 
					  override fun onCommand(
 | 
				
			||||||
 | 
					    sender: CommandSender,
 | 
				
			||||||
 | 
					    command: Command,
 | 
				
			||||||
 | 
					    label: String,
 | 
				
			||||||
 | 
					    args: Array<out String>
 | 
				
			||||||
 | 
					  ): Boolean {
 | 
				
			||||||
    if (args.isEmpty()) {
 | 
					    if (args.isEmpty()) {
 | 
				
			||||||
      sender.sendMessage("Invalid Command Usage.")
 | 
					      sender.sendMessage("Invalid Command Usage.")
 | 
				
			||||||
      return true
 | 
					      return true
 | 
				
			||||||
@ -17,7 +23,7 @@ class PersistentStoreCommand(private val plugin: FoundationCorePlugin) : Command
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    when (args[0]) {
 | 
					    when (args[0]) {
 | 
				
			||||||
      "stats" -> {
 | 
					      "stats" -> {
 | 
				
			||||||
        plugin.persistentStores.forEach { (name, store) ->
 | 
					        statsFeature.persistentStores.forEach { (name, store) ->
 | 
				
			||||||
          val counts = store.transact {
 | 
					          val counts = store.transact {
 | 
				
			||||||
            entityTypes.associateWith { type -> getAll(type).size() }.toSortedMap()
 | 
					            entityTypes.associateWith { type -> getAll(type).size() }.toSortedMap()
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
@ -36,7 +42,7 @@ class PersistentStoreCommand(private val plugin: FoundationCorePlugin) : Command
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        val storeName = args[1]
 | 
					        val storeName = args[1]
 | 
				
			||||||
        val entityTypeName = args[2]
 | 
					        val entityTypeName = args[2]
 | 
				
			||||||
        val store = plugin.getPersistentStore(storeName)
 | 
					        val store = statsFeature.getPersistentStore(storeName)
 | 
				
			||||||
        store.transact {
 | 
					        store.transact {
 | 
				
			||||||
          val entities = getAll(entityTypeName).take(3)
 | 
					          val entities = getAll(entityTypeName).take(3)
 | 
				
			||||||
          for (entity in entities) {
 | 
					          for (entity in entities) {
 | 
				
			||||||
@ -55,7 +61,7 @@ class PersistentStoreCommand(private val plugin: FoundationCorePlugin) : Command
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        val storeName = args[1]
 | 
					        val storeName = args[1]
 | 
				
			||||||
        val entityTypeName = args[2]
 | 
					        val entityTypeName = args[2]
 | 
				
			||||||
        val store = plugin.getPersistentStore(storeName)
 | 
					        val store = statsFeature.getPersistentStore(storeName)
 | 
				
			||||||
        store.transact {
 | 
					        store.transact {
 | 
				
			||||||
          store.deleteAllEntities(entityTypeName)
 | 
					          store.deleteAllEntities(entityTypeName)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.core.features.stats
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.FoundationCorePlugin
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.abstraction.Feature
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.persist.PersistentStore
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.persist.setAllProperties
 | 
				
			||||||
 | 
					import io.papermc.paper.event.player.AsyncChatEvent
 | 
				
			||||||
 | 
					import net.kyori.adventure.text.TextComponent
 | 
				
			||||||
 | 
					import org.bukkit.event.EventHandler
 | 
				
			||||||
 | 
					import org.koin.core.component.inject
 | 
				
			||||||
 | 
					import java.time.Instant
 | 
				
			||||||
 | 
					import java.util.concurrent.ConcurrentHashMap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class StatsFeature : Feature() {
 | 
				
			||||||
 | 
					  private val plugin = inject<FoundationCorePlugin>()
 | 
				
			||||||
 | 
					  private lateinit var chatLogStore: PersistentStore
 | 
				
			||||||
 | 
					  // TODO: Move persistence stuff to its own module.
 | 
				
			||||||
 | 
					  internal val persistentStores = ConcurrentHashMap<String, PersistentStore>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun enable() {
 | 
				
			||||||
 | 
					    chatLogStore = getPersistentStore("chat-logs")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    registerCommandExecutor("pstore", PersistentStoreCommand(this))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun disable() {
 | 
				
			||||||
 | 
					    persistentStores.values.forEach { store -> store.close() }
 | 
				
			||||||
 | 
					    persistentStores.clear()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Fetch a persistent store by name. Make sure the name is path-safe, descriptive and consistent across server runs.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  fun getPersistentStore(name: String) =
 | 
				
			||||||
 | 
					    persistentStores.getOrPut(name) { PersistentStore(plugin.value, name) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @EventHandler
 | 
				
			||||||
 | 
					  private fun logOnChatMessage(e: AsyncChatEvent) {
 | 
				
			||||||
 | 
					    val player = e.player
 | 
				
			||||||
 | 
					    val message = e.message()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (message !is TextComponent) {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val content = message.content()
 | 
				
			||||||
 | 
					    chatLogStore.create("ChatMessageEvent") {
 | 
				
			||||||
 | 
					      setAllProperties(
 | 
				
			||||||
 | 
					        "timestamp" to Instant.now().toEpochMilli(),
 | 
				
			||||||
 | 
					        "player.id" to player.identity().uuid().toString(),
 | 
				
			||||||
 | 
					        "player.name" to player.name,
 | 
				
			||||||
 | 
					        "message.content" to content
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.core.update
 | 
					package cloud.kubelet.foundation.core.features.update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import kotlinx.serialization.Serializable
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1,6 +1,5 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.core.command
 | 
					package cloud.kubelet.foundation.core.features.update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cloud.kubelet.foundation.core.service.UpdateService
 | 
					 | 
				
			||||||
import org.bukkit.command.Command
 | 
					import org.bukkit.command.Command
 | 
				
			||||||
import org.bukkit.command.CommandExecutor
 | 
					import org.bukkit.command.CommandExecutor
 | 
				
			||||||
import org.bukkit.command.CommandSender
 | 
					import org.bukkit.command.CommandSender
 | 
				
			||||||
@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.core.features.update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.abstraction.Feature
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UpdateFeature : Feature() {
 | 
				
			||||||
 | 
					  override fun enable() {
 | 
				
			||||||
 | 
					    registerCommandExecutor("fupdate", UpdateCommand())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,11 +1,10 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.core.service
 | 
					package cloud.kubelet.foundation.core.features.update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cloud.kubelet.foundation.core.update.UpdateUtil
 | 
					 | 
				
			||||||
import org.bukkit.command.CommandSender
 | 
					import org.bukkit.command.CommandSender
 | 
				
			||||||
import kotlin.io.path.name
 | 
					import kotlin.io.path.name
 | 
				
			||||||
import kotlin.io.path.toPath
 | 
					import kotlin.io.path.toPath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: Switch to classes and use dependency injection with koin.
 | 
					// TODO: Switch to a class and use dependency injection with koin.
 | 
				
			||||||
object UpdateService {
 | 
					object UpdateService {
 | 
				
			||||||
  fun updatePlugins(sender: CommandSender, onFinish: (() -> Unit)? = null) {
 | 
					  fun updatePlugins(sender: CommandSender, onFinish: (() -> Unit)? = null) {
 | 
				
			||||||
    val updateDir = sender.server.pluginsFolder.resolve("update")
 | 
					    val updateDir = sender.server.pluginsFolder.resolve("update")
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
package cloud.kubelet.foundation.core.update
 | 
					package cloud.kubelet.foundation.core.features.update
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import kotlinx.serialization.DeserializationStrategy
 | 
					import kotlinx.serialization.DeserializationStrategy
 | 
				
			||||||
import kotlinx.serialization.builtins.MapSerializer
 | 
					import kotlinx.serialization.builtins.MapSerializer
 | 
				
			||||||
@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					package cloud.kubelet.foundation.core.features.world
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.abstraction.Feature
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.command.GamemodeCommand
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.command.SetSpawnCommand
 | 
				
			||||||
 | 
					import cloud.kubelet.foundation.core.command.SpawnCommand
 | 
				
			||||||
 | 
					import org.bukkit.GameMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WorldFeature : Feature() {
 | 
				
			||||||
 | 
					  override fun enable() {
 | 
				
			||||||
 | 
					    registerCommandExecutor("setspawn", SetSpawnCommand())
 | 
				
			||||||
 | 
					    registerCommandExecutor("spawn", SpawnCommand())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user