mirror of
https://github.com/GayPizzaSpecifications/foundation.git
synced 2025-08-02 13:10:55 +00:00
Major refactoring to use Koin.
This commit is contained in:
parent
f8178c2307
commit
13479b1ae3
@ -82,6 +82,9 @@ subprojects {
|
||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
|
||||
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
|
||||
implementation("com.charleskorn.kaml:kaml:0.38.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.1")
|
||||
|
@ -1,3 +1,5 @@
|
||||
dependencies {
|
||||
// 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
|
||||
|
||||
import cloud.kubelet.foundation.core.command.*
|
||||
import cloud.kubelet.foundation.core.devupdate.DevUpdateServer
|
||||
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.Component
|
||||
import net.kyori.adventure.text.TextComponent
|
||||
import org.bukkit.GameMode
|
||||
import org.bukkit.command.CommandExecutor
|
||||
import org.bukkit.command.TabCompleter
|
||||
import org.bukkit.event.EventHandler
|
||||
import org.bukkit.event.Listener
|
||||
import org.bukkit.plugin.java.JavaPlugin
|
||||
import cloud.kubelet.foundation.core.abstraction.Feature
|
||||
import cloud.kubelet.foundation.core.abstraction.FoundationPlugin
|
||||
import cloud.kubelet.foundation.core.features.backup.BackupFeature
|
||||
import cloud.kubelet.foundation.core.features.dev.DevFeature
|
||||
import cloud.kubelet.foundation.core.features.player.PlayerFeature
|
||||
import cloud.kubelet.foundation.core.features.stats.StatsFeature
|
||||
import cloud.kubelet.foundation.core.features.update.UpdateFeature
|
||||
import cloud.kubelet.foundation.core.features.world.WorldFeature
|
||||
import org.koin.dsl.module
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class FoundationCorePlugin : JavaPlugin(), Listener {
|
||||
internal val persistentStores = ConcurrentHashMap<String, PersistentStore>()
|
||||
class FoundationCorePlugin : FoundationPlugin() {
|
||||
private lateinit var _pluginDataPath: Path
|
||||
private lateinit var chatLogStore: PersistentStore
|
||||
private lateinit var devUpdateServer: DevUpdateServer
|
||||
override val features: List<Feature>
|
||||
get() = listOf(
|
||||
BackupFeature(),
|
||||
DevFeature(),
|
||||
PlayerFeature(),
|
||||
StatsFeature(),
|
||||
UpdateFeature(),
|
||||
WorldFeature(),
|
||||
)
|
||||
|
||||
var pluginDataPath: Path
|
||||
/**
|
||||
@ -38,104 +38,15 @@ class FoundationCorePlugin : JavaPlugin(), Listener {
|
||||
_pluginDataPath = value
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(this, name) }
|
||||
override fun module() = module {
|
||||
single { this@FoundationCorePlugin }
|
||||
}
|
||||
|
||||
override fun onEnable() {
|
||||
// Create core plugin directory.
|
||||
pluginDataPath = dataFolder.toPath()
|
||||
val backupPath = pluginDataPath.resolve(BACKUPS_DIRECTORY)
|
||||
|
||||
// Create Foundation plugin directories.
|
||||
pluginDataPath.toFile().mkdir()
|
||||
backupPath.toFile().mkdir()
|
||||
|
||||
// Register this as an event listener.
|
||||
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
|
||||
super.onEnable()
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,6 @@ object Util {
|
||||
private val whitespace: Component = Component.text(' ')
|
||||
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 {
|
||||
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.Util
|
||||
@ -26,15 +26,6 @@ class BackupCommand(
|
||||
override fun onCommand(
|
||||
sender: CommandSender, command: Command, label: String, args: Array<String>
|
||||
): Boolean {
|
||||
if (!FoundationCorePlugin.BACKUP_ENABLED) {
|
||||
sender.sendMessage(
|
||||
Component
|
||||
.text("Backup is not enabled.")
|
||||
.color(TextColor.fromHexString("#FF0000"))
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
if (RUNNING.get()) {
|
||||
sender.sendMessage(
|
||||
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.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.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
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")
|
||||
|
||||
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()) {
|
||||
sender.sendMessage("Invalid Command Usage.")
|
||||
return true
|
||||
@ -17,7 +23,7 @@ class PersistentStoreCommand(private val plugin: FoundationCorePlugin) : Command
|
||||
|
||||
when (args[0]) {
|
||||
"stats" -> {
|
||||
plugin.persistentStores.forEach { (name, store) ->
|
||||
statsFeature.persistentStores.forEach { (name, store) ->
|
||||
val counts = store.transact {
|
||||
entityTypes.associateWith { type -> getAll(type).size() }.toSortedMap()
|
||||
}
|
||||
@ -36,7 +42,7 @@ class PersistentStoreCommand(private val plugin: FoundationCorePlugin) : Command
|
||||
|
||||
val storeName = args[1]
|
||||
val entityTypeName = args[2]
|
||||
val store = plugin.getPersistentStore(storeName)
|
||||
val store = statsFeature.getPersistentStore(storeName)
|
||||
store.transact {
|
||||
val entities = getAll(entityTypeName).take(3)
|
||||
for (entity in entities) {
|
||||
@ -55,7 +61,7 @@ class PersistentStoreCommand(private val plugin: FoundationCorePlugin) : Command
|
||||
|
||||
val storeName = args[1]
|
||||
val entityTypeName = args[2]
|
||||
val store = plugin.getPersistentStore(storeName)
|
||||
val store = statsFeature.getPersistentStore(storeName)
|
||||
store.transact {
|
||||
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
|
||||
|
@ -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.CommandExecutor
|
||||
import org.bukkit.command.CommandSender
|
||||
@ -15,4 +14,4 @@ class UpdateCommand : CommandExecutor {
|
||||
UpdateService.updatePlugins(sender)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
@ -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 kotlin.io.path.name
|
||||
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 {
|
||||
fun updatePlugins(sender: CommandSender, onFinish: (() -> Unit)? = null) {
|
||||
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.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())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user