diff --git a/gradle.properties b/gradle.properties index 69d5229c..2e914772 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ makeevrserg.java.ktarget=21 # Project makeevrserg.project.name=SoulKeeper makeevrserg.project.group=ru.astrainteractive.soulkeeper -makeevrserg.project.version.string=1.5.0 +makeevrserg.project.version.string=1.6.0 makeevrserg.project.description=Keep your items after death makeevrserg.project.developers=makeevrserg|Makeev Roman|makeevrserg@gmail.com makeevrserg.project.url=https://github.com/Astra-Interactive/SoulKeeper diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 79b83475..d4132e0c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,7 +26,7 @@ kotlin-serialization = "1.10.0" kotlin-serialization-kaml = "0.104.0" kotlin-version = "2.2.0" ktor = "3.4.0" -minecraft-astralibs = "3.32.0" +minecraft-astralibs = "3.33.0" minecraft-bstats = "3.2.1" minecraft-bungee = "1.21-R0.5-SNAPSHOT" minecraft-essentialsx = "2.21.2" diff --git a/instances/bukkit/build.gradle.kts b/instances/bukkit/build.gradle.kts index 84be7e4b..c3a7ddf7 100644 --- a/instances/bukkit/build.gradle.kts +++ b/instances/bukkit/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { implementation(projects.modules.dao) implementation(projects.modules.service) implementation(projects.modules.serviceBukkit) - implementation(projects.modules.commandBukkit) + implementation(projects.modules.command) } minecraftProcessResource { diff --git a/instances/bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/di/RootModule.kt b/instances/bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/di/RootModule.kt index 9ce2b65a..cd2d23c5 100644 --- a/instances/bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/di/RootModule.kt +++ b/instances/bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/di/RootModule.kt @@ -1,6 +1,9 @@ package ru.astrainteractive.soulkeeper.di import org.bukkit.event.HandlerList +import ru.astrainteractive.astralibs.command.api.brigadier.command.MultiplatformCommand +import ru.astrainteractive.astralibs.command.api.brigadier.command.PaperMultiplatformCommands +import ru.astrainteractive.astralibs.command.api.registrar.PaperCommandRegistrarContext import ru.astrainteractive.astralibs.coroutines.DefaultBukkitDispatchers import ru.astrainteractive.astralibs.lifecycle.Lifecycle import ru.astrainteractive.astralibs.lifecycle.LifecyclePlugin @@ -44,8 +47,14 @@ class RootModule(plugin: LifecyclePlugin) { private val commandModule = CommandModule( coreModule = coreModule, - bukkitCoreModule = bukkitCoreModule, - soulsDaoModule = soulsDaoModule + commandRegistrarContext = PaperCommandRegistrarContext( + mainScope = coreModule.mainScope, + plugin = plugin + ), + soulsDaoModule = soulsDaoModule, + serviceModule = serviceModule, + multiplatformCommand = MultiplatformCommand(PaperMultiplatformCommands()), + lifecyclePlugin = plugin, ) private val lifecycles: List diff --git a/instances/neoforge/build.gradle.kts b/instances/neoforge/build.gradle.kts index c8dfe29f..6d79886a 100644 --- a/instances/neoforge/build.gradle.kts +++ b/instances/neoforge/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { shadow(libs.minecraft.kyori.gson) // Local shadow(projects.modules.core) - shadow(projects.modules.commandNeoforge) + shadow(projects.modules.command) shadow(projects.modules.dao) shadow(projects.modules.service) shadow(projects.modules.serviceNeoforge) diff --git a/instances/neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/di/RootModule.kt b/instances/neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/di/RootModule.kt index bce4f393..87198960 100644 --- a/instances/neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/di/RootModule.kt +++ b/instances/neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/di/RootModule.kt @@ -4,6 +4,9 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainCoroutineDispatcher import net.neoforged.fml.loading.FMLPaths +import ru.astrainteractive.astralibs.command.api.brigadier.command.MultiplatformCommand +import ru.astrainteractive.astralibs.command.brigadier.command.NeoForgeMultiplatformCommands +import ru.astrainteractive.astralibs.command.registrar.NeoForgeCommandRegistrarContext import ru.astrainteractive.astralibs.coroutines.NeoForgeMainDispatcher import ru.astrainteractive.astralibs.lifecycle.Lifecycle import ru.astrainteractive.klibs.mikro.core.dispatchers.KotlinDispatchers @@ -67,11 +70,18 @@ class RootModule(private val plugin: Lifecycle) { effectEmitter = forgePlatformServiceModule.effectEmitter ) } - private val commandModule = CommandModule( - coreModule = coreModule, - soulsDaoModule = soulsDaoModule, - plugin = plugin, - ) + private val commandModule by lazy { + CommandModule( + coreModule = coreModule, + soulsDaoModule = soulsDaoModule, + commandRegistrarContext = NeoForgeCommandRegistrarContext( + mainScope = coreModule.mainScope + ), + serviceModule = serviceModule, + multiplatformCommand = MultiplatformCommand(NeoForgeMultiplatformCommands()), + lifecyclePlugin = plugin, + ) + } private val lifecycles: List get() = listOfNotNull( diff --git a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/reload/SoulsReloadCommandRegistrar.kt b/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/reload/SoulsReloadCommandRegistrar.kt deleted file mode 100644 index 48b31c89..00000000 --- a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/reload/SoulsReloadCommandRegistrar.kt +++ /dev/null @@ -1,39 +0,0 @@ -package ru.astrainteractive.soulkeeper.command.reload - -import com.mojang.brigadier.tree.LiteralCommandNode -import io.papermc.paper.command.brigadier.CommandSourceStack -import ru.astrainteractive.astralibs.command.api.registrar.PaperCommandRegistrarContext -import ru.astrainteractive.astralibs.command.api.util.command -import ru.astrainteractive.astralibs.command.api.util.requirePermission -import ru.astrainteractive.astralibs.command.api.util.runs -import ru.astrainteractive.astralibs.kyori.KyoriComponentSerializer -import ru.astrainteractive.astralibs.kyori.unwrap -import ru.astrainteractive.astralibs.lifecycle.LifecyclePlugin -import ru.astrainteractive.klibs.kstorage.api.CachedKrate -import ru.astrainteractive.klibs.kstorage.util.getValue -import ru.astrainteractive.soulkeeper.core.plugin.PluginPermission -import ru.astrainteractive.soulkeeper.core.plugin.PluginTranslation - -internal class SoulsReloadCommandRegistrar( - private val plugin: LifecyclePlugin, - private val registrarContext: PaperCommandRegistrarContext, - translationKrate: CachedKrate, - kyoriKrate: CachedKrate -) : KyoriComponentSerializer by kyoriKrate.unwrap() { - private val translation by translationKrate - - private fun createNode(): LiteralCommandNode { - return command("skreload") { - runs { ctx -> - ctx.requirePermission(PluginPermission.Reload) - ctx.source.sender.sendMessage(translation.general.reload.component) - plugin.onReload() - ctx.source.sender.sendMessage(translation.general.reloadComplete.component) - } - }.build() - } - - fun register() { - registrarContext.registerWhenReady(createNode()) - } -} diff --git a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/soulkrate/SoulKrateCommandRegistrar.kt b/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/soulkrate/SoulKrateCommandRegistrar.kt deleted file mode 100644 index 8ff7aa2a..00000000 --- a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/soulkrate/SoulKrateCommandRegistrar.kt +++ /dev/null @@ -1,74 +0,0 @@ -package ru.astrainteractive.soulkeeper.command.soulkrate - -import com.mojang.brigadier.arguments.IntegerArgumentType -import com.mojang.brigadier.arguments.LongArgumentType -import com.mojang.brigadier.arguments.StringArgumentType -import com.mojang.brigadier.tree.LiteralCommandNode -import io.papermc.paper.command.brigadier.CommandSourceStack -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import kotlinx.serialization.StringFormat -import ru.astrainteractive.astralibs.command.api.registrar.PaperCommandRegistrarContext -import ru.astrainteractive.astralibs.command.api.util.argument -import ru.astrainteractive.astralibs.command.api.util.command -import ru.astrainteractive.astralibs.command.api.util.requireArgument -import ru.astrainteractive.astralibs.command.api.util.requirePermission -import ru.astrainteractive.astralibs.command.api.util.requirePlayer -import ru.astrainteractive.astralibs.command.api.util.runs -import ru.astrainteractive.klibs.mikro.core.logging.JUtiltLogger -import ru.astrainteractive.klibs.mikro.core.logging.Logger -import ru.astrainteractive.soulkeeper.core.plugin.PluginPermission -import ru.astrainteractive.soulkeeper.core.serialization.ItemStackSerializer -import ru.astrainteractive.soulkeeper.module.souls.database.model.StringFormatObject -import ru.astrainteractive.soulkeeper.module.souls.krate.PlayerSoulKrate -import java.io.File -import java.time.Instant -import java.util.UUID - -internal class SoulKrateCommandRegistrar( - private val registrarContext: PaperCommandRegistrarContext, - private val stringFormat: StringFormat, - private val dataFolder: File, - private val ioScope: CoroutineScope -) : Logger by JUtiltLogger("SoulKrateCommandRegistrar") { - private fun createNode(): LiteralCommandNode { - return command("soulkrate") { - argument("uuid", StringArgumentType.string()) { uuidArg -> - argument("instant", LongArgumentType.longArg()) { instantArg -> - argument("index", IntegerArgumentType.integer()) { indexArg -> - runs { ctx -> - ctx.requirePermission(PluginPermission.LoadSouls) - val player = ctx.requirePlayer() - val instant = ctx.requireArgument(instantArg).let(Instant::ofEpochSecond) - val index = ctx.requireArgument(indexArg) - val uuid = ctx.requireArgument(uuidArg).let(UUID::fromString) - ioScope.launch { - val soul = PlayerSoulKrate( - stringFormat = stringFormat, - dataFolder = dataFolder, - createdAt = instant, - ownerUUID = uuid, - readIndex = index - ).getValue() - val items = soul?.items - .orEmpty() - .map(StringFormatObject::raw) - .map(ItemStackSerializer::decodeFromString) - .mapNotNull { itemStackResult -> - itemStackResult - .onFailure { error(it) { "Failed to deserialize item stack" } } - .getOrNull() - } - player.inventory.addItem(*items.toTypedArray()) - } - } - } - } - } - }.build() - } - - fun register() { - registrarContext.registerWhenReady(createNode()) - } -} diff --git a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommand.kt b/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommand.kt deleted file mode 100644 index 5db08954..00000000 --- a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommand.kt +++ /dev/null @@ -1,16 +0,0 @@ -package ru.astrainteractive.soulkeeper.command.souls - -import org.bukkit.command.CommandSender - -internal interface SoulsCommand { - - sealed interface Intent { - data class List(val sender: CommandSender, val page: Int) : Intent - data class Free(val sender: CommandSender, val soulId: Long) : Intent - data class TeleportToSoul(val sender: CommandSender, val soulId: Long) : Intent - } - - companion object { - internal const val PAGE_SIZE = 5 - } -} diff --git a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsListCommandRegistrar.kt b/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsListCommandRegistrar.kt deleted file mode 100644 index 60ccddb8..00000000 --- a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsListCommandRegistrar.kt +++ /dev/null @@ -1,67 +0,0 @@ -package ru.astrainteractive.soulkeeper.command.souls - -import com.mojang.brigadier.arguments.IntegerArgumentType -import com.mojang.brigadier.arguments.LongArgumentType -import com.mojang.brigadier.tree.LiteralCommandNode -import io.papermc.paper.command.brigadier.CommandSourceStack -import ru.astrainteractive.astralibs.command.api.registrar.PaperCommandRegistrarContext -import ru.astrainteractive.astralibs.command.api.util.argument -import ru.astrainteractive.astralibs.command.api.util.command -import ru.astrainteractive.astralibs.command.api.util.literal -import ru.astrainteractive.astralibs.command.api.util.requireArgument -import ru.astrainteractive.astralibs.command.api.util.runs -import ru.astrainteractive.astralibs.kyori.KyoriComponentSerializer -import ru.astrainteractive.astralibs.kyori.unwrap -import ru.astrainteractive.klibs.kstorage.api.CachedKrate - -internal class SoulsListCommandRegistrar( - kyoriKrate: CachedKrate, - private val registrarContext: PaperCommandRegistrarContext, - private val soulsCommandExecutor: SoulsCommandExecutor -) : KyoriComponentSerializer by kyoriKrate.unwrap() { - private fun createNode(): LiteralCommandNode { - return command("souls") { - literal("page") { - argument("page", IntegerArgumentType.integer(0)) { pageArg -> - runs { ctx -> - val page = ctx.requireArgument(pageArg) - SoulsCommand.Intent.List( - sender = ctx.source.sender, - page = page - ).run(soulsCommandExecutor::execute) - } - } - } - literal("free") { - argument("soul_id", LongArgumentType.longArg(0)) { idArg -> - runs { ctx -> - SoulsCommand.Intent.Free( - sender = ctx.source.sender, - soulId = ctx.requireArgument(idArg) - ).run(soulsCommandExecutor::execute) - } - } - } - literal("teleport") { - argument("soul_id", LongArgumentType.longArg(0)) { idArg -> - runs { ctx -> - SoulsCommand.Intent.TeleportToSoul( - sender = ctx.source.sender, - soulId = ctx.requireArgument(idArg) - ).run(soulsCommandExecutor::execute) - } - } - } - runs { ctx -> - SoulsCommand.Intent.List( - sender = ctx.source.sender, - page = 0 - ).run(soulsCommandExecutor::execute) - } - }.build() - } - - fun register() { - registrarContext.registerWhenReady(createNode()) - } -} diff --git a/modules/command-neoforge/build.gradle.kts b/modules/command-neoforge/build.gradle.kts deleted file mode 100644 index 07b12b3f..00000000 --- a/modules/command-neoforge/build.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -plugins { - kotlin("jvm") - kotlin("plugin.serialization") -} - -dependencies { - // Kotlin - implementation(libs.kotlin.serialization.json) - implementation(libs.kotlin.coroutines.core) - // AstraLibs - implementation(libs.minecraft.astralibs.core) - implementation(libs.minecraft.astralibs.core.neoforge) - implementation(libs.minecraft.astralibs.command) - // klibs - implementation(libs.klibs.kstorage) - implementation(libs.klibs.mikro.core) - implementation(libs.klibs.mikro.extensions) - // kyori - implementation(libs.minecraft.kyori.api) - // Test - testImplementation(libs.tests.kotlin.test) - // Local - implementation(projects.modules.core) - implementation(projects.modules.dao) - implementation(projects.modules.service) -} - -dependencies { - compileOnly( - files( - rootProject.project(projects.instances.neoforge.path) - .file(".gradle") - .resolve("repositories") - .resolve("ng_dummy_ng") - .resolve("net") - .resolve("neoforged") - .resolve("neoforge") - .resolve(libs.versions.minecraft.neoforgeversion.get()) - .resolve("neoforge-${libs.versions.minecraft.neoforgeversion.get()}.jar") - ) - ) - compileOnly(libs.minecraft.neoforgeversion) - compileOnly("org.joml:joml:1.10.8") - compileOnly("com.mojang:datafixerupper:8.0.16") - compileOnly("com.mojang:brigadier:1.3.10") - compileOnly("net.neoforged:bus:8.0.2") -} diff --git a/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/di/CommandModule.kt b/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/di/CommandModule.kt deleted file mode 100644 index 32eff6df..00000000 --- a/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/di/CommandModule.kt +++ /dev/null @@ -1,38 +0,0 @@ -package ru.astrainteractive.soulkeeper.command.di - -import ru.astrainteractive.astralibs.command.registrar.NeoForgeCommandRegistrarContext -import ru.astrainteractive.astralibs.lifecycle.Lifecycle -import ru.astrainteractive.soulkeeper.command.reload.SoulsReloadCommandRegistrar -import ru.astrainteractive.soulkeeper.command.souls.SoulsCommandExecutor -import ru.astrainteractive.soulkeeper.command.souls.SoulsListCommandRegistrar -import ru.astrainteractive.soulkeeper.core.di.CoreModule -import ru.astrainteractive.soulkeeper.module.souls.di.SoulsDaoModule - -class CommandModule( - private val coreModule: CoreModule, - private val soulsDaoModule: SoulsDaoModule, - private val plugin: Lifecycle, -) { - private val commandRegistrar = NeoForgeCommandRegistrarContext(coreModule.unconfinedScope) - val lifecycle = Lifecycle.Lambda( - onEnable = { - SoulsListCommandRegistrar( - kyoriKrate = coreModule.kyoriComponentSerializer, - registrarContext = commandRegistrar, - soulsCommandExecutor = SoulsCommandExecutor( - ioScope = coreModule.ioScope, - soulsDao = soulsDaoModule.soulsDao, - translationKrate = coreModule.translation, - kyoriKrate = coreModule.kyoriComponentSerializer - ) - ).register() - SoulsReloadCommandRegistrar( - plugin = plugin, - translationKrate = coreModule.translation, - kyoriKrate = coreModule.kyoriComponentSerializer, - registrarContext = commandRegistrar - ).register() - }, - onDisable = {} - ) -} diff --git a/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/reload/SoulsReloadCommandRegistrar.kt b/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/reload/SoulsReloadCommandRegistrar.kt deleted file mode 100644 index 8d12ea4f..00000000 --- a/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/reload/SoulsReloadCommandRegistrar.kt +++ /dev/null @@ -1,40 +0,0 @@ -package ru.astrainteractive.soulkeeper.command.reload - -import com.mojang.brigadier.builder.LiteralArgumentBuilder -import net.minecraft.commands.CommandSourceStack -import ru.astrainteractive.astralibs.command.registrar.NeoForgeCommandRegistrarContext -import ru.astrainteractive.astralibs.command.util.command -import ru.astrainteractive.astralibs.command.util.requirePermission -import ru.astrainteractive.astralibs.command.util.runs -import ru.astrainteractive.astralibs.kyori.KyoriComponentSerializer -import ru.astrainteractive.astralibs.kyori.unwrap -import ru.astrainteractive.astralibs.lifecycle.Lifecycle -import ru.astrainteractive.astralibs.server.util.toNative -import ru.astrainteractive.klibs.kstorage.api.CachedKrate -import ru.astrainteractive.klibs.kstorage.util.getValue -import ru.astrainteractive.soulkeeper.core.plugin.PluginPermission -import ru.astrainteractive.soulkeeper.core.plugin.PluginTranslation - -internal class SoulsReloadCommandRegistrar( - private val plugin: Lifecycle, - private val registrarContext: NeoForgeCommandRegistrarContext, - translationKrate: CachedKrate, - kyoriKrate: CachedKrate -) : KyoriComponentSerializer by kyoriKrate.unwrap() { - private val translation by translationKrate - - private fun createNode(): LiteralArgumentBuilder { - return command("skreload") { - runs { ctx -> - ctx.requirePermission(PluginPermission.Reload) - ctx.source.sendSystemMessage(translation.general.reload.component.toNative()) - plugin.onReload() - ctx.source.sendSystemMessage(translation.general.reloadComplete.component.toNative()) - } - } - } - - fun register() { - registrarContext.registerWhenReady(createNode()) - } -} diff --git a/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommand.kt b/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommand.kt deleted file mode 100644 index 6a744e52..00000000 --- a/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommand.kt +++ /dev/null @@ -1,16 +0,0 @@ -package ru.astrainteractive.soulkeeper.command.souls - -import net.minecraft.commands.CommandSourceStack - -internal interface SoulsCommand { - - sealed interface Intent { - data class List(val sender: CommandSourceStack, val page: Int) : Intent - data class Free(val sender: CommandSourceStack, val soulId: Long) : Intent - data class TeleportToSoul(val sender: CommandSourceStack, val soulId: Long) : Intent - } - - companion object { - internal const val PAGE_SIZE = 5 - } -} diff --git a/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommandExecutor.kt b/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommandExecutor.kt deleted file mode 100644 index df03c44c..00000000 --- a/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommandExecutor.kt +++ /dev/null @@ -1,238 +0,0 @@ -package ru.astrainteractive.soulkeeper.command.souls - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import net.kyori.adventure.text.Component -import net.minecraft.commands.CommandSourceStack -import ru.astrainteractive.astralibs.kyori.KyoriComponentSerializer -import ru.astrainteractive.astralibs.kyori.unwrap -import ru.astrainteractive.astralibs.server.location.KLocation -import ru.astrainteractive.astralibs.server.location.dist -import ru.astrainteractive.astralibs.server.util.asLocatable -import ru.astrainteractive.astralibs.server.util.asOnlineMinecraftPlayer -import ru.astrainteractive.astralibs.server.util.asPermissible -import ru.astrainteractive.astralibs.server.util.toNative -import ru.astrainteractive.astralibs.util.clickable -import ru.astrainteractive.astralibs.util.isEmpty -import ru.astrainteractive.astralibs.util.orEmpty -import ru.astrainteractive.klibs.kstorage.api.CachedKrate -import ru.astrainteractive.klibs.kstorage.util.getValue -import ru.astrainteractive.soulkeeper.core.datetime.TimeAgoFormatter -import ru.astrainteractive.soulkeeper.core.datetime.TimeAgoTranslationFormatter -import ru.astrainteractive.soulkeeper.core.plugin.PluginPermission -import ru.astrainteractive.soulkeeper.core.plugin.PluginTranslation -import ru.astrainteractive.soulkeeper.module.souls.dao.SoulsDao -import ru.astrainteractive.soulkeeper.module.souls.database.model.DatabaseSoul -import ru.astrainteractive.soulkeeper.module.souls.database.model.Soul -import kotlin.collections.filter - -@Suppress("TooManyFunctions") -internal class SoulsCommandExecutor( - private val ioScope: CoroutineScope, - private val soulsDao: SoulsDao, - translationKrate: CachedKrate, - kyoriKrate: CachedKrate -) : KyoriComponentSerializer by kyoriKrate.unwrap() { - private val translation by translationKrate - - private fun createPagingMessage(input: SoulsCommand.Intent.List, maxPages: Int): Component { - val nextPageComponent = translation.souls.nextPage.component - .clickable { execute(input.copy(page = input.page.plus(1))) } - .takeIf { input.page < maxPages } - .orEmpty() - val prevPageComponent = translation.souls.prevPage.component - .clickable { execute(input.copy(page = input.page.plus(-1))) } - .appendSpace().takeIf { input.page > 0 } - .orEmpty() - return nextPageComponent.append(prevPageComponent, true) - } - - private suspend fun getFilteredSouls(sender: CommandSourceStack): List { - return soulsDao.getSouls() - .getOrNull() - .orEmpty() - .filter { soul -> - sender.player?.asLocatable() - ?.getLocation() - ?.worldName == soul.location.worldName - } - .filter { soul -> - soul.isFree - .or(sender.player?.asPermissible()?.hasPermission(PluginPermission.ViewAllSouls) == true) - .or(sender.player?.uuid == soul.ownerUUID) - } - } - - private fun getPageSouls(souls: List, page: Int): List { - val start = page.times(SoulsCommand.PAGE_SIZE).coerceIn(0, souls.size) - val end = - (page * SoulsCommand.PAGE_SIZE + SoulsCommand.PAGE_SIZE).coerceAtMost( - souls.size - ) - return if (start == end) { - emptyList() - } else if (end == 0) { - emptyList() - } else { - souls.subList(start, end) - } - } - - private fun createListingItemComponent( - soul: Soul, - page: Int, - i: Int, - location: KLocation? - ): Component { - val timeAgo = TimeAgoFormatter.format(soul.createdAt) - val timeAgoFormatted = TimeAgoTranslationFormatter(translation) - .format(timeAgo) - - return translation.souls.listingFormat( - index = page.times(SoulsCommand.PAGE_SIZE).plus(i.plus(1)), - owner = soul.ownerLastName, - timeAgo = timeAgoFormatted.raw, - x = soul.location.x.toInt(), - y = soul.location.y.toInt(), - z = soul.location.z.toInt(), - distance = location - ?.dist(soul.location) - ?.toInt() - ?: 0 - ).component - } - - private fun CommandSourceStack.canFreeSouls(soul: DatabaseSoul): Boolean { - val sender = this - - val hasPermission = sender.player - ?.asPermissible() - ?.hasPermission(PluginPermission.FreeAllSouls) == true - val isOwner = sender.player?.uuid == soul.ownerUUID - if (soul.isFree) return false - if (!hasPermission) return false - if (!isOwner) return false - return true - } - - private fun createFreeSoulComponent(sender: CommandSourceStack, soul: DatabaseSoul): Component? { - return if (!sender.canFreeSouls(soul)) { - null - } else { - translation.souls.freeSoul - .component - .appendSpace() - .clickable { execute(SoulsCommand.Intent.Free(sender, soul.id)) } - } - } - - private fun CommandSourceStack.canTeleportToSoul(): Boolean { - val sender = this - val hasPermission = sender.player - ?.asPermissible() - ?.hasPermission(PluginPermission.TeleportToSouls) == true - return hasPermission - } - - private fun createTeleportSoulComponent(sender: CommandSourceStack, soul: DatabaseSoul): Component? { - if (!sender.canTeleportToSoul()) return null - return translation.souls.teleportToSoul - .component - .clickable { execute(SoulsCommand.Intent.TeleportToSoul(sender, soul.id)) } - } - - fun Component.append( - other: Component?, - addSpace: Boolean = false - ): Component { - return if (other == null || other.isEmpty()) { - this - } else if (addSpace) { - this.appendSpace().append(other) - } else { - this.append(other) - } - } - - fun execute(input: SoulsCommand.Intent) { - ioScope.launch { - executeInternal(input) - } - } - - @Suppress("LongMethod") - private suspend fun executeInternal(input: SoulsCommand.Intent) { - when (input) { - is SoulsCommand.Intent.List -> { - val filteredSouls = getFilteredSouls(input.sender) - val maxPages = filteredSouls.size.div(SoulsCommand.PAGE_SIZE) - val pageSouls = getPageSouls(filteredSouls, input.page) - if (pageSouls.isEmpty()) { - val title = translation.souls.noSoulsOnPage(input.page.plus(1)).component - input.sender.sendSystemMessage(title.toNative()) - return - } - - translation.souls.listSoulsTitle.component - .toNative() - .run(input.sender::sendSystemMessage) - - pageSouls.forEachIndexed { i, soul -> - createListingItemComponent( - soul = soul, - page = input.page, - i = i, - location = input.sender - .player - ?.asLocatable() - ?.getLocation() - ).append( - addSpace = true, - other = createFreeSoulComponent( - sender = input.sender, - soul = soul - ) - ).append( - addSpace = true, - other = createTeleportSoulComponent( - sender = input.sender, - soul = soul - ) - ).toNative().run(input.sender::sendSystemMessage) - } - createPagingMessage(input, maxPages) - .toNative() - .run(input.sender::sendSystemMessage) - } - - is SoulsCommand.Intent.Free -> { - val newSoul = soulsDao.getSoul(input.soulId) - .getOrNull() - ?.copy(isFree = true) - if (newSoul == null) { - input.sender.sendSystemMessage(translation.souls.soulNotFound.component.toNative()) - return - } - soulsDao.updateSoul(newSoul) - .onSuccess { - input.sender.sendSystemMessage(translation.souls.soulFreed.component.toNative()) - } - .onFailure { - input.sender.sendSystemMessage(translation.souls.couldNotFreeSoul.component.toNative()) - } - } - - is SoulsCommand.Intent.TeleportToSoul -> { - val player = input.sender.player ?: return - val location = soulsDao.getSoul(input.soulId) - .getOrNull() - ?.location - if (location == null) { - input.sender.sendSystemMessage(translation.souls.soulNotFound.component.toNative()) - return - } - player.asOnlineMinecraftPlayer().teleport(location) - } - } - } -} diff --git a/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsListCommandRegistrar.kt b/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsListCommandRegistrar.kt deleted file mode 100644 index 99dc880c..00000000 --- a/modules/command-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsListCommandRegistrar.kt +++ /dev/null @@ -1,67 +0,0 @@ -package ru.astrainteractive.soulkeeper.command.souls - -import com.mojang.brigadier.arguments.IntegerArgumentType -import com.mojang.brigadier.arguments.LongArgumentType -import com.mojang.brigadier.builder.LiteralArgumentBuilder -import net.minecraft.commands.CommandSourceStack -import ru.astrainteractive.astralibs.command.registrar.NeoForgeCommandRegistrarContext -import ru.astrainteractive.astralibs.command.util.argument -import ru.astrainteractive.astralibs.command.util.command -import ru.astrainteractive.astralibs.command.util.literal -import ru.astrainteractive.astralibs.command.util.requireArgument -import ru.astrainteractive.astralibs.command.util.runs -import ru.astrainteractive.astralibs.kyori.KyoriComponentSerializer -import ru.astrainteractive.astralibs.kyori.unwrap -import ru.astrainteractive.klibs.kstorage.api.CachedKrate - -internal class SoulsListCommandRegistrar( - kyoriKrate: CachedKrate, - private val registrarContext: NeoForgeCommandRegistrarContext, - private val soulsCommandExecutor: SoulsCommandExecutor -) : KyoriComponentSerializer by kyoriKrate.unwrap() { - private fun createNode(): LiteralArgumentBuilder { - return command("souls") { - literal("page") { - argument("page", IntegerArgumentType.integer(0)) { pageArg -> - runs { ctx -> - val page = ctx.requireArgument(pageArg) - SoulsCommand.Intent.List( - sender = ctx.source, - page = page - ).run(soulsCommandExecutor::execute) - } - } - } - literal("free") { - argument("soul_id", LongArgumentType.longArg(0)) { idArg -> - runs { ctx -> - SoulsCommand.Intent.Free( - sender = ctx.source, - soulId = ctx.requireArgument(idArg) - ).run(soulsCommandExecutor::execute) - } - } - } - literal("teleport") { - argument("soul_id", LongArgumentType.longArg(0)) { idArg -> - runs { ctx -> - SoulsCommand.Intent.TeleportToSoul( - sender = ctx.source, - soulId = ctx.requireArgument(idArg) - ).run(soulsCommandExecutor::execute) - } - } - } - runs { ctx -> - SoulsCommand.Intent.List( - sender = ctx.source, - page = 0 - ).run(soulsCommandExecutor::execute) - } - } - } - - fun register() { - registrarContext.registerWhenReady(createNode()) - } -} diff --git a/modules/command-bukkit/build.gradle.kts b/modules/command/build.gradle.kts similarity index 62% rename from modules/command-bukkit/build.gradle.kts rename to modules/command/build.gradle.kts index d1adb50a..b2851b15 100644 --- a/modules/command-bukkit/build.gradle.kts +++ b/modules/command/build.gradle.kts @@ -8,14 +8,18 @@ dependencies { implementation(libs.kotlin.serialization.json) implementation(libs.minecraft.astralibs.core) - implementation(libs.minecraft.astralibs.core.bukkit) implementation(libs.minecraft.astralibs.command) - implementation(libs.minecraft.astralibs.command.bukkit) - compileOnly(libs.minecraft.paper.api) + implementation(libs.minecraft.kyori.api) implementation(libs.klibs.mikro.core) implementation(projects.modules.core) - implementation(projects.modules.coreBukkit) implementation(projects.modules.dao) + implementation(projects.modules.service) +} + +dependencies { + compileOnly("org.joml:joml:1.10.8") + compileOnly("com.mojang:datafixerupper:8.0.16") + compileOnly("com.mojang:brigadier:1.3.10") } diff --git a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/di/CommandModule.kt b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/di/CommandModule.kt similarity index 54% rename from modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/di/CommandModule.kt rename to modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/di/CommandModule.kt index 7df56f9b..945ea1ea 100644 --- a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/di/CommandModule.kt +++ b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/di/CommandModule.kt @@ -1,47 +1,54 @@ package ru.astrainteractive.soulkeeper.command.di -import ru.astrainteractive.astralibs.command.api.registrar.PaperCommandRegistrarContext +import ru.astrainteractive.astralibs.command.api.brigadier.command.MultiplatformCommand +import ru.astrainteractive.astralibs.command.api.registrar.CommandRegistrarContext import ru.astrainteractive.astralibs.lifecycle.Lifecycle import ru.astrainteractive.soulkeeper.command.reload.SoulsReloadCommandRegistrar import ru.astrainteractive.soulkeeper.command.soulkrate.SoulKrateCommandRegistrar import ru.astrainteractive.soulkeeper.command.souls.SoulsCommandExecutor import ru.astrainteractive.soulkeeper.command.souls.SoulsListCommandRegistrar -import ru.astrainteractive.soulkeeper.core.di.BukkitCoreModule import ru.astrainteractive.soulkeeper.core.di.CoreModule +import ru.astrainteractive.soulkeeper.module.souls.di.ServiceModule import ru.astrainteractive.soulkeeper.module.souls.di.SoulsDaoModule class CommandModule( private val coreModule: CoreModule, - private val bukkitCoreModule: BukkitCoreModule, - private val soulsDaoModule: SoulsDaoModule + private val commandRegistrarContext: CommandRegistrarContext, + private val soulsDaoModule: SoulsDaoModule, + private val serviceModule: ServiceModule, + private val multiplatformCommand: MultiplatformCommand<*>, + private val lifecyclePlugin: Lifecycle ) { - private val paperCommandRegistrar = PaperCommandRegistrarContext( - mainScope = coreModule.mainScope, - plugin = bukkitCoreModule.plugin - ) val lifecycle = Lifecycle.Lambda( onEnable = { SoulsListCommandRegistrar( kyoriKrate = coreModule.kyoriComponentSerializer, - registrarContext = paperCommandRegistrar, + registrarContext = commandRegistrarContext, + multiplatformCommand = multiplatformCommand, soulsCommandExecutor = SoulsCommandExecutor( ioScope = coreModule.ioScope, soulsDao = soulsDaoModule.soulsDao, translationKrate = coreModule.translation, - kyoriKrate = coreModule.kyoriComponentSerializer - ) + kyoriKrate = coreModule.kyoriComponentSerializer, + dispatchers = coreModule.dispatchers + ), ).register() SoulKrateCommandRegistrar( - registrarContext = paperCommandRegistrar, + registrarContext = commandRegistrarContext, + multiplatformCommand = multiplatformCommand, stringFormat = coreModule.yamlFormat, dataFolder = coreModule.dataFolder, - ioScope = coreModule.ioScope + ioScope = coreModule.ioScope, + addSoulItemsIntoInventoryUseCase = serviceModule.addSoulItemsIntoInventoryUseCase, + translationKrate = coreModule.translation, + kyoriKrate = coreModule.kyoriComponentSerializer ).register() SoulsReloadCommandRegistrar( - plugin = bukkitCoreModule.plugin, + lifecyclePlugin = lifecyclePlugin, translationKrate = coreModule.translation, kyoriKrate = coreModule.kyoriComponentSerializer, - registrarContext = paperCommandRegistrar + registrarContext = commandRegistrarContext, + multiplatformCommand = multiplatformCommand, ).register() }, onDisable = { diff --git a/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/reload/SoulsReloadCommandRegistrar.kt b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/reload/SoulsReloadCommandRegistrar.kt new file mode 100644 index 00000000..729a83c3 --- /dev/null +++ b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/reload/SoulsReloadCommandRegistrar.kt @@ -0,0 +1,41 @@ +package ru.astrainteractive.soulkeeper.command.reload + +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import ru.astrainteractive.astralibs.command.api.brigadier.command.MultiplatformCommand +import ru.astrainteractive.astralibs.command.api.registrar.CommandRegistrarContext +import ru.astrainteractive.astralibs.kyori.KyoriComponentSerializer +import ru.astrainteractive.astralibs.kyori.unwrap +import ru.astrainteractive.astralibs.lifecycle.Lifecycle +import ru.astrainteractive.astralibs.server.KAudience +import ru.astrainteractive.klibs.kstorage.api.CachedKrate +import ru.astrainteractive.klibs.kstorage.util.getValue +import ru.astrainteractive.soulkeeper.core.plugin.PluginPermission +import ru.astrainteractive.soulkeeper.core.plugin.PluginTranslation + +internal class SoulsReloadCommandRegistrar( + private val lifecyclePlugin: Lifecycle, + private val registrarContext: CommandRegistrarContext, + private val multiplatformCommand: MultiplatformCommand<*>, + translationKrate: CachedKrate, + kyoriKrate: CachedKrate +) : KyoriComponentSerializer by kyoriKrate.unwrap() { + private val translation by translationKrate + + private fun createNode(): LiteralArgumentBuilder<*> { + return with(multiplatformCommand) { + command("skreload") { + runs { ctx -> + ctx.requirePermission(PluginPermission.Reload) + val audience = commands.getSender(ctx) as? KAudience + audience?.sendMessage(translation.general.reload.component) + lifecyclePlugin.onReload() + audience?.sendMessage(translation.general.reloadComplete.component) + } + } + } + } + + fun register() { + registrarContext.registerWhenReady(createNode()) + } +} diff --git a/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/soulkrate/SoulKrateCommandRegistrar.kt b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/soulkrate/SoulKrateCommandRegistrar.kt new file mode 100644 index 00000000..258c3666 --- /dev/null +++ b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/soulkrate/SoulKrateCommandRegistrar.kt @@ -0,0 +1,79 @@ +package ru.astrainteractive.soulkeeper.command.soulkrate + +import com.mojang.brigadier.arguments.IntegerArgumentType +import com.mojang.brigadier.arguments.LongArgumentType +import com.mojang.brigadier.arguments.StringArgumentType +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.serialization.StringFormat +import ru.astrainteractive.astralibs.command.api.brigadier.command.MultiplatformCommand +import ru.astrainteractive.astralibs.command.api.registrar.CommandRegistrarContext +import ru.astrainteractive.astralibs.kyori.KyoriComponentSerializer +import ru.astrainteractive.astralibs.kyori.unwrap +import ru.astrainteractive.klibs.kstorage.api.CachedKrate +import ru.astrainteractive.klibs.kstorage.util.getValue +import ru.astrainteractive.klibs.mikro.core.logging.JUtiltLogger +import ru.astrainteractive.klibs.mikro.core.logging.Logger +import ru.astrainteractive.soulkeeper.core.plugin.PluginPermission +import ru.astrainteractive.soulkeeper.core.plugin.PluginTranslation +import ru.astrainteractive.soulkeeper.module.souls.domain.AddSoulItemsIntoInventoryUseCase +import ru.astrainteractive.soulkeeper.module.souls.krate.PlayerSoulKrate +import java.io.File +import java.time.Instant +import java.util.* + +@Suppress("LongParameterList") +internal class SoulKrateCommandRegistrar( + private val registrarContext: CommandRegistrarContext, + private val multiplatformCommand: MultiplatformCommand<*>, + private val stringFormat: StringFormat, + private val dataFolder: File, + private val ioScope: CoroutineScope, + private val addSoulItemsIntoInventoryUseCase: AddSoulItemsIntoInventoryUseCase, + translationKrate: CachedKrate, + kyoriKrate: CachedKrate +) : Logger by JUtiltLogger("SoulKrateCommandRegistrar"), + KyoriComponentSerializer by kyoriKrate.unwrap() { + private val translation by translationKrate + private fun createNode(): LiteralArgumentBuilder<*> { + return with(multiplatformCommand) { + command("soulkrate") { + argument("uuid", StringArgumentType.string()) { uuidArg -> + argument("instant", LongArgumentType.longArg()) { instantArg -> + argument("index", IntegerArgumentType.integer()) { indexArg -> + runs { ctx -> + ctx.requirePermission(PluginPermission.LoadSouls) + val player = ctx.requirePlayer() + val instant = ctx.requireArgument(instantArg).let(Instant::ofEpochSecond) + val index = ctx.requireArgument(indexArg) + val uuid = ctx.requireArgument(uuidArg).let(UUID::fromString) + ioScope.launch { + val soul = PlayerSoulKrate( + stringFormat = stringFormat, + dataFolder = dataFolder, + createdAt = instant, + ownerUUID = uuid, + readIndex = index + ).getValue() + if (soul == null) { + player.sendMessage(translation.souls.soulNotFound.component) + return@launch + } + addSoulItemsIntoInventoryUseCase.invoke( + player = player, + soul = soul, + ) + } + } + } + } + } + } + } + } + + fun register() { + registrarContext.registerWhenReady(createNode()) + } +} diff --git a/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommand.kt b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommand.kt new file mode 100644 index 00000000..e2b2b1ff --- /dev/null +++ b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommand.kt @@ -0,0 +1,16 @@ +package ru.astrainteractive.soulkeeper.command.souls + +import ru.astrainteractive.astralibs.command.api.brigadier.sender.KCommandSender + +internal interface SoulsCommand { + + sealed interface Intent { + data class List(val sender: KCommandSender, val page: Int) : Intent + data class Free(val sender: KCommandSender, val soulId: Long) : Intent + data class TeleportToSoul(val sender: KCommandSender, val soulId: Long) : Intent + } + + companion object { + internal const val PAGE_SIZE = 5 + } +} diff --git a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommandExecutor.kt b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommandExecutor.kt similarity index 69% rename from modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommandExecutor.kt rename to modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommandExecutor.kt index 0a5009c3..debd1855 100644 --- a/modules/command-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommandExecutor.kt +++ b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsCommandExecutor.kt @@ -2,19 +2,24 @@ package ru.astrainteractive.soulkeeper.command.souls import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import net.kyori.adventure.text.Component -import org.bukkit.Location -import org.bukkit.command.CommandSender -import org.bukkit.entity.Player +import ru.astrainteractive.astralibs.command.api.brigadier.sender.KCommandSender import ru.astrainteractive.astralibs.kyori.KyoriComponentSerializer import ru.astrainteractive.astralibs.kyori.unwrap -import ru.astrainteractive.astralibs.server.permission.asKPermissible -import ru.astrainteractive.astralibs.server.util.asBukkitLocation +import ru.astrainteractive.astralibs.server.KAudience +import ru.astrainteractive.astralibs.server.Locatable +import ru.astrainteractive.astralibs.server.location.KLocation +import ru.astrainteractive.astralibs.server.location.dist +import ru.astrainteractive.astralibs.server.permission.KPermissible +import ru.astrainteractive.astralibs.server.player.KPlayer +import ru.astrainteractive.astralibs.server.player.OnlineKPlayer import ru.astrainteractive.astralibs.util.clickable import ru.astrainteractive.astralibs.util.isEmpty import ru.astrainteractive.astralibs.util.orEmpty import ru.astrainteractive.klibs.kstorage.api.CachedKrate import ru.astrainteractive.klibs.kstorage.util.getValue +import ru.astrainteractive.klibs.mikro.core.dispatchers.KotlinDispatchers import ru.astrainteractive.klibs.mikro.core.util.tryCast import ru.astrainteractive.soulkeeper.core.datetime.TimeAgoFormatter import ru.astrainteractive.soulkeeper.core.datetime.TimeAgoTranslationFormatter @@ -28,6 +33,7 @@ import kotlin.collections.filter internal class SoulsCommandExecutor( private val ioScope: CoroutineScope, private val soulsDao: SoulsDao, + private val dispatchers: KotlinDispatchers, translationKrate: CachedKrate, kyoriKrate: CachedKrate ) : KyoriComponentSerializer by kyoriKrate.unwrap() { @@ -45,19 +51,19 @@ internal class SoulsCommandExecutor( return nextPageComponent.append(prevPageComponent, true) } - private suspend fun getFilteredSouls(sender: CommandSender): List { + private suspend fun getFilteredSouls(sender: KCommandSender): List { return soulsDao.getSouls() .getOrNull() .orEmpty() - .filter { - (sender as? Player)?.world?.name?.let { worldName -> - it.location.worldName == worldName - } ?: true + .filter { soul -> + sender.tryCast() + ?.getLocation() + ?.worldName == soul.location.worldName } .filter { soul -> soul.isFree - .or(sender.asKPermissible().hasPermission(PluginPermission.ViewAllSouls)) - .or((sender as? Player)?.uniqueId == soul.ownerUUID) + .or(sender.tryCast()?.hasPermission(PluginPermission.ViewAllSouls) == true) + .or(sender.tryCast()?.uuid == soul.ownerUUID) } } @@ -80,7 +86,7 @@ internal class SoulsCommandExecutor( soul: Soul, page: Int, i: Int, - location: Location? + location: KLocation? ): Component { val timeAgo = TimeAgoFormatter.format(soul.createdAt) val timeAgoFormatted = TimeAgoTranslationFormatter(translation) @@ -94,23 +100,23 @@ internal class SoulsCommandExecutor( y = soul.location.y.toInt(), z = soul.location.z.toInt(), distance = location - ?.distance(soul.location.asBukkitLocation()) + ?.dist(soul.location) ?.toInt() ?: 0 ).component } - private fun CommandSender.canFreeSouls(soul: DatabaseSoul): Boolean { + private fun KCommandSender.canFreeSouls(soul: DatabaseSoul): Boolean { val sender = this - val hasPermission = sender.asKPermissible().hasPermission(PluginPermission.FreeAllSouls) - val isOwner = (sender as? Player)?.uniqueId == soul.ownerUUID + val hasPermission = sender.tryCast()?.hasPermission(PluginPermission.FreeAllSouls) == true + val isOwner = sender.tryCast()?.uuid == soul.ownerUUID if (soul.isFree) return false if (!hasPermission) return false if (!isOwner) return false return true } - private fun createFreeSoulComponent(sender: CommandSender, soul: DatabaseSoul): Component? { + private fun createFreeSoulComponent(sender: KCommandSender, soul: DatabaseSoul): Component? { return if (!sender.canFreeSouls(soul)) { null } else { @@ -121,16 +127,16 @@ internal class SoulsCommandExecutor( } } - private fun CommandSender.canTeleportToSoul(): Boolean { + private fun KCommandSender.canTeleportToSoul(): Boolean { val sender = this - if (sender !is Player) return false - if (!sender.asKPermissible().hasPermission(PluginPermission.TeleportToSouls)) { + if (sender !is OnlineKPlayer) return false + if (!sender.hasPermission(PluginPermission.TeleportToSouls)) { return false } return true } - private fun createTeleportSoulComponent(sender: CommandSender, soul: DatabaseSoul): Component? { + private fun createTeleportSoulComponent(sender: KCommandSender, soul: DatabaseSoul): Component? { if (!sender.canTeleportToSoul()) return null return translation.souls.teleportToSoul .component @@ -160,21 +166,20 @@ internal class SoulsCommandExecutor( val pageSouls = getPageSouls(filteredSouls, input.page) if (pageSouls.isEmpty()) { val title = translation.souls.noSoulsOnPage(input.page.plus(1)).component - input.sender.sendMessage(title) + input.sender.tryCast()?.sendMessage(title) return@launch } - translation.souls.listSoulsTitle.component - .run(input.sender::sendMessage) + input.sender.tryCast()?.sendMessage(translation.souls.listSoulsTitle.component) pageSouls.forEachIndexed { i, soul -> - createListingItemComponent( + val component = createListingItemComponent( soul = soul, page = input.page, i = i, location = input.sender - .tryCast() - ?.location + .tryCast() + ?.getLocation() ).append( addSpace = true, other = createFreeSoulComponent( @@ -187,10 +192,10 @@ internal class SoulsCommandExecutor( sender = input.sender, soul = soul ) - ).run(input.sender::sendMessage) + ) + input.sender.tryCast()?.sendMessage(component) } - createPagingMessage(input, maxPages) - .run(input.sender::sendMessage) + input.sender.tryCast()?.sendMessage(createPagingMessage(input, maxPages)) } } @@ -200,31 +205,32 @@ internal class SoulsCommandExecutor( .getOrNull() ?.copy(isFree = true) if (newSoul == null) { - input.sender.sendMessage(translation.souls.soulNotFound.component) + input.sender.tryCast()?.sendMessage(translation.souls.soulNotFound.component) return@launch } soulsDao.updateSoul(newSoul) .onSuccess { - input.sender.sendMessage(translation.souls.soulFreed.component) + input.sender.tryCast()?.sendMessage(translation.souls.soulFreed.component) } .onFailure { - input.sender.sendMessage(translation.souls.couldNotFreeSoul.component) + input.sender.tryCast()?.sendMessage(translation.souls.couldNotFreeSoul.component) } } } is SoulsCommand.Intent.TeleportToSoul -> { ioScope.launch { - val player = input.sender as? Player ?: return@launch + val player = input.sender.tryCast() ?: return@launch val location = soulsDao.getSoul(input.soulId) .getOrNull() ?.location - ?.asBukkitLocation() if (location == null) { - input.sender.sendMessage(translation.souls.soulNotFound.component) + player.sendMessage(translation.souls.soulNotFound.component) return@launch } - player.teleportAsync(location) + withContext(dispatchers.Main) { + player.teleport(location) + } } } } diff --git a/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsListCommandRegistrar.kt b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsListCommandRegistrar.kt new file mode 100644 index 00000000..d1d50ee0 --- /dev/null +++ b/modules/command/src/main/kotlin/ru/astrainteractive/soulkeeper/command/souls/SoulsListCommandRegistrar.kt @@ -0,0 +1,65 @@ +package ru.astrainteractive.soulkeeper.command.souls + +import com.mojang.brigadier.arguments.IntegerArgumentType +import com.mojang.brigadier.arguments.LongArgumentType +import com.mojang.brigadier.builder.LiteralArgumentBuilder +import ru.astrainteractive.astralibs.command.api.brigadier.command.MultiplatformCommand +import ru.astrainteractive.astralibs.command.api.registrar.CommandRegistrarContext +import ru.astrainteractive.astralibs.kyori.KyoriComponentSerializer +import ru.astrainteractive.astralibs.kyori.unwrap +import ru.astrainteractive.klibs.kstorage.api.CachedKrate + +internal class SoulsListCommandRegistrar( + kyoriKrate: CachedKrate, + private val registrarContext: CommandRegistrarContext, + private val multiplatformCommand: MultiplatformCommand<*>, + private val soulsCommandExecutor: SoulsCommandExecutor +) : KyoriComponentSerializer by kyoriKrate.unwrap() { + private fun createNode(): LiteralArgumentBuilder<*> { + return with(multiplatformCommand) { + command("souls") { + literal("page") { + argument("page", IntegerArgumentType.integer(0)) { pageArg -> + runs { ctx -> + val page = ctx.requireArgument(pageArg) + SoulsCommand.Intent.List( + sender = ctx.getSender(), + page = page + ).run(soulsCommandExecutor::execute) + } + } + } + literal("free") { + argument("soul_id", LongArgumentType.longArg(0)) { idArg -> + runs { ctx -> + SoulsCommand.Intent.Free( + sender = ctx.getSender(), + soulId = ctx.requireArgument(idArg) + ).run(soulsCommandExecutor::execute) + } + } + } + literal("teleport") { + argument("soul_id", LongArgumentType.longArg(0)) { idArg -> + runs { ctx -> + SoulsCommand.Intent.TeleportToSoul( + sender = ctx.getSender(), + soulId = ctx.requireArgument(idArg) + ).run(soulsCommandExecutor::execute) + } + } + } + runs { ctx -> + SoulsCommand.Intent.List( + sender = ctx.getSender(), + page = 0 + ).run(soulsCommandExecutor::execute) + } + } + } + } + + fun register() { + registrarContext.registerWhenReady(createNode()) + } +} diff --git a/modules/service-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/BukkitPlatformServiceModule.kt b/modules/service-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/BukkitPlatformServiceModule.kt index f4a1e268..cf978719 100644 --- a/modules/service-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/BukkitPlatformServiceModule.kt +++ b/modules/service-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/BukkitPlatformServiceModule.kt @@ -4,6 +4,7 @@ import ru.astrainteractive.astralibs.server.bridge.BukkitPlatformServer import ru.astrainteractive.astralibs.server.bridge.PlatformServer import ru.astrainteractive.soulkeeper.core.di.BukkitCoreModule import ru.astrainteractive.soulkeeper.core.di.CoreModule +import ru.astrainteractive.soulkeeper.module.souls.domain.BukkitAddSoulItemsIntoInventoryUseCase import ru.astrainteractive.soulkeeper.module.souls.domain.BukkitPickUpItemsUseCase import ru.astrainteractive.soulkeeper.module.souls.domain.PickUpItemsUseCase import ru.astrainteractive.soulkeeper.module.souls.domain.armorstand.ShowArmorStandUseCase @@ -35,4 +36,5 @@ class BukkitPlatformServiceModule( soulsDao = soulsDaoModule.soulsDao, dispatchers = coreModule.dispatchers ) + override val addSoulItemsIntoInventoryUseCase = BukkitAddSoulItemsIntoInventoryUseCase() } diff --git a/modules/service-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/BukkitAddSoulItemsIntoInventoryUseCase.kt b/modules/service-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/BukkitAddSoulItemsIntoInventoryUseCase.kt new file mode 100644 index 00000000..6ccd125a --- /dev/null +++ b/modules/service-bukkit/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/BukkitAddSoulItemsIntoInventoryUseCase.kt @@ -0,0 +1,32 @@ +package ru.astrainteractive.soulkeeper.module.souls.domain + +import ru.astrainteractive.astralibs.server.player.BukkitOnlineKPlayer +import ru.astrainteractive.astralibs.server.player.OnlineKPlayer +import ru.astrainteractive.klibs.mikro.core.logging.JUtiltLogger +import ru.astrainteractive.klibs.mikro.core.logging.Logger +import ru.astrainteractive.soulkeeper.core.serialization.ItemStackSerializer +import ru.astrainteractive.soulkeeper.module.souls.database.model.DefaultSoul +import ru.astrainteractive.soulkeeper.module.souls.database.model.StringFormatObject + +class BukkitAddSoulItemsIntoInventoryUseCase : + AddSoulItemsIntoInventoryUseCase, + Logger by JUtiltLogger("SoulKeeper-AddSoulItemsIntoInventoryUseCase") { + override suspend fun invoke( + player: OnlineKPlayer, + soul: DefaultSoul + ) { + require(player is BukkitOnlineKPlayer) { + "Player must be BukkitOnlineKPlayer" + } + val items = soul?.items + .orEmpty() + .map(StringFormatObject::raw) + .map(ItemStackSerializer::decodeFromString) + .mapNotNull { itemStackResult -> + itemStackResult + .onFailure { error(it) { "Failed to deserialize item stack" } } + .getOrNull() + } + player.instance.inventory.addItem(*items.toTypedArray()) + } +} diff --git a/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/NeoForgePlatformServiceModule.kt b/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/NeoForgePlatformServiceModule.kt index 9fe3112b..12492d3d 100644 --- a/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/NeoForgePlatformServiceModule.kt +++ b/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/NeoForgePlatformServiceModule.kt @@ -3,6 +3,7 @@ package ru.astrainteractive.soulkeeper.module.souls.di import ru.astrainteractive.astralibs.server.bridge.NeoForgePlatformServer import ru.astrainteractive.astralibs.server.bridge.PlatformServer import ru.astrainteractive.soulkeeper.core.di.CoreModule +import ru.astrainteractive.soulkeeper.module.souls.domain.NeoForgeAddSoulItemsIntoInventoryUseCase import ru.astrainteractive.soulkeeper.module.souls.domain.NeoForgePickUpItemsUseCase import ru.astrainteractive.soulkeeper.module.souls.domain.PickUpItemsUseCase import ru.astrainteractive.soulkeeper.module.souls.domain.armorstand.ShowArmorStandUseCase @@ -33,4 +34,5 @@ class NeoForgePlatformServiceModule( isDeadPlayerProvider = isDeadPlayerProvider, dispatchers = coreModule.dispatchers ) + override val addSoulItemsIntoInventoryUseCase = NeoForgeAddSoulItemsIntoInventoryUseCase(isDeadPlayerProvider) } diff --git a/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/NeoForgeAddSoulItemsIntoInventoryUseCase.kt b/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/NeoForgeAddSoulItemsIntoInventoryUseCase.kt new file mode 100644 index 00000000..1a772521 --- /dev/null +++ b/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/NeoForgeAddSoulItemsIntoInventoryUseCase.kt @@ -0,0 +1,37 @@ +package ru.astrainteractive.soulkeeper.module.souls.domain + +import ru.astrainteractive.astralibs.server.player.NeoForgeOnlineKPlayer +import ru.astrainteractive.astralibs.server.player.OnlineKPlayer +import ru.astrainteractive.klibs.mikro.core.logging.JUtiltLogger +import ru.astrainteractive.klibs.mikro.core.logging.Logger +import ru.astrainteractive.soulkeeper.module.souls.database.model.DefaultSoul +import ru.astrainteractive.soulkeeper.module.souls.database.model.StringFormatObject +import ru.astrainteractive.soulkeeper.module.souls.platform.IsDeadPlayerProvider +import ru.astrainteractive.soulkeeper.module.souls.platform.ItemStackSerializer +import ru.astrainteractive.soulkeeper.module.souls.util.addItems + +class NeoForgeAddSoulItemsIntoInventoryUseCase( + private val isDeadPlayerProvider: IsDeadPlayerProvider +) : + AddSoulItemsIntoInventoryUseCase, + Logger by JUtiltLogger("SoulKeeper-AddSoulItemsIntoInventoryUseCase") { + + override suspend fun invoke( + player: OnlineKPlayer, + soul: DefaultSoul + ) { + require(player is NeoForgeOnlineKPlayer) { + "Player must be BukkitOnlineKPlayer" + } + val items = soul?.items + .orEmpty() + .map(StringFormatObject::raw) + .map(ItemStackSerializer::decodeFromString) + .mapNotNull { itemStackResult -> + itemStackResult + .onFailure { error(it) { "Failed to deserialize item stack" } } + .getOrNull() + } + player.addItems(items, isDeadPlayerProvider::isDead) + } +} diff --git a/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/NeoForgePickUpItemsUseCase.kt b/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/NeoForgePickUpItemsUseCase.kt index f8a460b1..0afacda8 100644 --- a/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/NeoForgePickUpItemsUseCase.kt +++ b/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/NeoForgePickUpItemsUseCase.kt @@ -1,7 +1,6 @@ package ru.astrainteractive.soulkeeper.module.souls.domain import kotlinx.coroutines.withContext -import net.minecraft.world.item.ItemStack import ru.astrainteractive.astralibs.server.player.OnlineKPlayer import ru.astrainteractive.astralibs.server.util.NeoForgeUtil import ru.astrainteractive.astralibs.server.util.getOnlinePlayer @@ -16,6 +15,7 @@ import ru.astrainteractive.soulkeeper.module.souls.domain.PickUpItemsUseCase.Out import ru.astrainteractive.soulkeeper.module.souls.platform.EffectEmitter import ru.astrainteractive.soulkeeper.module.souls.platform.IsDeadPlayerProvider import ru.astrainteractive.soulkeeper.module.souls.platform.ItemStackSerializer +import ru.astrainteractive.soulkeeper.module.souls.util.addItems internal class NeoForgePickUpItemsUseCase( private val collectItemSoundProvider: () -> SoulsConfig.Sounds.SoundConfig, @@ -25,22 +25,6 @@ internal class NeoForgePickUpItemsUseCase( private val dispatchers: KotlinDispatchers ) : PickUpItemsUseCase, Logger by JUtiltLogger("SoulKeeper-PickUpItemsUseCase") { - /** - * @param items List of items to add to inventory - * @return not fitted items into inventory - */ - private fun OnlineKPlayer.addItems(items: List): List { - return items - .mapNotNull { stack -> - if (!isDeadPlayerProvider.isDead(this)) { - val inventory = NeoForgeUtil.getOnlinePlayer(uuid)?.inventory - inventory?.add(stack) - inventory?.setChanged() - } - stack.copy() - } - .filterNot(ItemStack::isEmpty) - } override suspend fun invoke(player: OnlineKPlayer, soul: ItemDatabaseSoul): Output { if (soul.items.isEmpty()) return Output.NoItemsPresent @@ -55,7 +39,8 @@ internal class NeoForgePickUpItemsUseCase( items = soul.items .map(StringFormatObject::raw) .map(ItemStackSerializer::decodeFromString) - .mapNotNull { result -> result.getOrNull() } + .mapNotNull { result -> result.getOrNull() }, + isDead = isDeadPlayerProvider::isDead ) } if (notAddedItems.isEmpty()) { diff --git a/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/util/OnlineKPlayerExt.kt b/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/util/OnlineKPlayerExt.kt new file mode 100644 index 00000000..5274b3b5 --- /dev/null +++ b/modules/service-neoforge/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/util/OnlineKPlayerExt.kt @@ -0,0 +1,26 @@ +package ru.astrainteractive.soulkeeper.module.souls.util + +import net.minecraft.world.item.ItemStack +import ru.astrainteractive.astralibs.server.player.OnlineKPlayer +import ru.astrainteractive.astralibs.server.util.NeoForgeUtil +import ru.astrainteractive.astralibs.server.util.getOnlinePlayer + +/** + * @param items List of items to add to inventory + * @return not fitted items into inventory + */ +internal fun OnlineKPlayer.addItems( + items: List, + isDead: (OnlineKPlayer) -> Boolean +): List { + return items + .mapNotNull { stack -> + if (!isDead.invoke(this)) { + val inventory = NeoForgeUtil.getOnlinePlayer(uuid)?.inventory + inventory?.add(stack) + inventory?.setChanged() + } + stack.copy() + } + .filterNot(ItemStack::isEmpty) +} diff --git a/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/PlatformServiceModule.kt b/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/PlatformServiceModule.kt index b2e8c045..c7b2d8d2 100644 --- a/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/PlatformServiceModule.kt +++ b/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/PlatformServiceModule.kt @@ -2,6 +2,7 @@ package ru.astrainteractive.soulkeeper.module.souls.di import ru.astrainteractive.astralibs.server.bridge.PlatformServer import ru.astrainteractive.astralibs.server.player.OnlineKPlayer +import ru.astrainteractive.soulkeeper.module.souls.domain.AddSoulItemsIntoInventoryUseCase import ru.astrainteractive.soulkeeper.module.souls.domain.PickUpItemsUseCase import ru.astrainteractive.soulkeeper.module.souls.domain.armorstand.ShowArmorStandUseCase import ru.astrainteractive.soulkeeper.module.souls.platform.EffectEmitter @@ -19,4 +20,5 @@ interface PlatformServiceModule { val showArmorStandUseCase: ShowArmorStandUseCase val pickUpItemsUseCase: PickUpItemsUseCase + val addSoulItemsIntoInventoryUseCase: AddSoulItemsIntoInventoryUseCase } diff --git a/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/ServiceModule.kt b/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/ServiceModule.kt index 75429d78..c5734a5c 100644 --- a/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/ServiceModule.kt +++ b/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/di/ServiceModule.kt @@ -24,6 +24,7 @@ class ServiceModule( ) { + val addSoulItemsIntoInventoryUseCase = platformServiceModule.addSoulItemsIntoInventoryUseCase private val armorStandRenderer = ArmorStandRenderer( soulsConfigKrate = coreModule.soulsConfigKrate, showArmorStandUseCase = platformServiceModule.showArmorStandUseCase, diff --git a/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/AddSoulItemsIntoInventoryUseCase.kt b/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/AddSoulItemsIntoInventoryUseCase.kt new file mode 100644 index 00000000..d63ae344 --- /dev/null +++ b/modules/service/src/main/kotlin/ru/astrainteractive/soulkeeper/module/souls/domain/AddSoulItemsIntoInventoryUseCase.kt @@ -0,0 +1,8 @@ +package ru.astrainteractive.soulkeeper.module.souls.domain + +import ru.astrainteractive.astralibs.server.player.OnlineKPlayer +import ru.astrainteractive.soulkeeper.module.souls.database.model.DefaultSoul + +interface AddSoulItemsIntoInventoryUseCase { + suspend fun invoke(player: OnlineKPlayer, soul: DefaultSoul) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index e5ca6fdb..2a454ff0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -54,5 +54,4 @@ include(":modules:service-bukkit") include(":modules:service-neoforge") include(":modules:event-bukkit") include(":modules:event-neoforge") -include(":modules:command-bukkit") -include(":modules:command-neoforge") +include(":modules:command")